This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 2_4_x-fixes
in repository https://gitbox.apache.org/repos/asf/ws-wss4j.git

commit b2a91608e127ac05d7736e27bb7676d2d8721ae0
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Mon Sep 21 15:42:33 2026 +0100

    StAX SAML subject confirmation skipped for an empty SOAP Body (#724)
---
 .../processor/input/SAMLTokenInputHandler.java     | 12 +++++
 .../stax/test/saml/SAMLTokenNegativeTest.java      | 55 ++++++++++++++++++++++
 .../resources/testdata/empty-body-soap-1.1.xml     |  4 ++
 3 files changed, 71 insertions(+)

diff --git 
a/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
 
b/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
index e70918816..25f6dc75b 100644
--- 
a/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
+++ 
b/ws-security-stax/src/main/java/org/apache/wss4j/stax/impl/processor/input/SAMLTokenInputHandler.java
@@ -69,6 +69,7 @@ import org.apache.xml.security.stax.ext.InputProcessorChain;
 import org.apache.xml.security.stax.ext.XMLSecurityConstants;
 import org.apache.xml.security.stax.ext.XMLSecurityProperties;
 import org.apache.xml.security.stax.ext.stax.XMLSecAttribute;
+import org.apache.xml.security.stax.ext.stax.XMLSecEndElement;
 import org.apache.xml.security.stax.ext.stax.XMLSecEvent;
 import org.apache.xml.security.stax.ext.stax.XMLSecNamespace;
 import org.apache.xml.security.stax.ext.stax.XMLSecStartElement;
@@ -604,6 +605,17 @@ public class SAMLTokenInputHandler extends 
AbstractInputSecurityHeaderHandler {
                     inputProcessorChain.removeProcessor(this);
                     checkPossessionOfKey(inputProcessorChain, 
samlAssertionWrapper, subjectSecurityToken);
                 }
+            } else if (xmlSecEvent.getEventType() == 
XMLStreamConstants.END_ELEMENT) {
+                // A Body with no element child of its own never produces the 
event above, so
+                // without this the subject confirmation of the assertion 
would go unchecked for
+                // such a message. The Body end element is the last point at 
which the check can
+                // still be made, and every Signature covering the Body has 
been seen by then.
+                XMLSecEndElement xmlSecEndElement = xmlSecEvent.asEndElement();
+                List<QName> elementPath = xmlSecEndElement.getElementPath();
+                if (elementPath.size() == 2 && 
WSSUtils.isInSOAPBody(elementPath)) {
+                    inputProcessorChain.removeProcessor(this);
+                    checkPossessionOfKey(inputProcessorChain, 
samlAssertionWrapper, subjectSecurityToken);
+                }
             }
             return xmlSecEvent;
         }
diff --git 
a/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/saml/SAMLTokenNegativeTest.java
 
b/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/saml/SAMLTokenNegativeTest.java
index 4830a0945..6a2ae5682 100755
--- 
a/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/saml/SAMLTokenNegativeTest.java
+++ 
b/ws-security-stax/src/test/java/org/apache/wss4j/stax/test/saml/SAMLTokenNegativeTest.java
@@ -233,6 +233,61 @@ public class SAMLTokenNegativeTest extends 
AbstractTestBase {
         }
     }
 
+    /**
+     * The proof-of-possession check for a holder-of-key assertion used to be 
triggered by the
+     * first element inside the SOAP Body. A Body with no element child of its 
own produces no
+     * such event, so the check never ran and an assertion whose subject key 
the sender does not
+     * hold was accepted.
+     */
+    @Test
+    public void testHOKEmptyBodyInbound() throws Exception {
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        {
+            SAML1CallbackHandler callbackHandler = new SAML1CallbackHandler();
+            callbackHandler.setStatement(SAML1CallbackHandler.Statement.AUTHN);
+            
callbackHandler.setConfirmationMethod(SAML1Constants.CONF_HOLDER_KEY);
+            callbackHandler.setIssuer("www.example.com");
+            // The assertion itself is signed, so it gets past the subject 
confirmation method
+            // check; what it does not carry is any proof that the sender 
holds the subject key.
+            callbackHandler.setSignAssertion(true);
+
+            InputStream sourceDocument =
+                
this.getClass().getClassLoader().getResourceAsStream("testdata/empty-body-soap-1.1.xml");
+            String action = WSHandlerConstants.SAML_TOKEN_UNSIGNED;
+            Properties properties = new Properties();
+            properties.put(WSHandlerConstants.SAML_CALLBACK_REF, 
callbackHandler);
+            Document securedDocument = 
doOutboundSecurityWithWSS4J(sourceDocument, action, properties);
+
+            // The Body must have stayed empty, or the test is not exercising 
what it claims to
+            NodeList bodies =
+                securedDocument.getElementsByTagNameNS(
+                    "http://schemas.xmlsoap.org/soap/envelope/";, "Body");
+            assertEquals(1, bodies.getLength());
+            assertEquals(0, 
((Element)bodies.item(0)).getElementsByTagName("*").getLength());
+
+            javax.xml.transform.Transformer transformer = 
TRANSFORMER_FACTORY.newTransformer();
+            transformer.transform(new DOMSource(securedDocument), new 
StreamResult(baos));
+        }
+
+        {
+            WSSSecurityProperties securityProperties = new 
WSSSecurityProperties();
+            securityProperties.loadSignatureVerificationKeystore(
+                
this.getClass().getClassLoader().getResource("saml/issuer.jks"), 
"default".toCharArray());
+            InboundWSSec wsSecIn = WSSec.getInboundWSSec(securityProperties, 
false, true);
+            XMLStreamReader xmlStreamReader =
+                wsSecIn.processInMessage(
+                    xmlInputFactory.createXMLStreamReader(new 
ByteArrayInputStream(baos.toByteArray())));
+
+            try {
+                StAX2DOM.readDoc(documentBuilderFactory.newDocumentBuilder(), 
xmlStreamReader);
+                fail("XMLStreamException expected");
+            } catch (XMLStreamException e) {
+                assertNotNull(e.getCause());
+            }
+        }
+    }
+
     @Test
     public void testSAML2TrustFailureInbound() throws Exception {
 
diff --git 
a/ws-security-stax/src/test/resources/testdata/empty-body-soap-1.1.xml 
b/ws-security-stax/src/test/resources/testdata/empty-body-soap-1.1.xml
new file mode 100644
index 000000000..9b9596b28
--- /dev/null
+++ b/ws-security-stax/src/test/resources/testdata/empty-body-soap-1.1.xml
@@ -0,0 +1,4 @@
+<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/";>
+    <env:Header/>
+    <env:Body/>
+</env:Envelope>

Reply via email to