You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ws.apache.org by co...@apache.org on 2026/09/21 10:46:44 UTC

(ws-wss4j) branch 3_0_x-fixes updated: Fix SAML Conditions parsing (#721)

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

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


The following commit(s) were added to refs/heads/3_0_x-fixes by this push:
     new fdd87d5aa Fix SAML Conditions parsing (#721)
fdd87d5aa is described below

commit fdd87d5aa0216a1b1b50202113e3d1b08b024f99
Author: Colm O hEigeartaigh <co...@users.noreply.github.com>
AuthorDate: Mon Sep 21 11:46:08 2026 +0100

    Fix SAML Conditions parsing (#721)
---
 .../wss4j/common/saml/SamlAssertionWrapper.java    |  17 ++--
 .../apache/wss4j/dom/saml/SamlConditionsTest.java  | 104 +++++++++++++++++++++
 2 files changed, 115 insertions(+), 6 deletions(-)

diff --git a/ws-security-common/src/main/java/org/apache/wss4j/common/saml/SamlAssertionWrapper.java b/ws-security-common/src/main/java/org/apache/wss4j/common/saml/SamlAssertionWrapper.java
index 29e9b3277..517ac917a 100644
--- a/ws-security-common/src/main/java/org/apache/wss4j/common/saml/SamlAssertionWrapper.java
+++ b/ws-security-common/src/main/java/org/apache/wss4j/common/saml/SamlAssertionWrapper.java
@@ -817,14 +817,19 @@ public class SamlAssertionWrapper {
         Instant issueInstant = null;
         Instant validTill = null;
 
-        if (getSamlVersion().equals(SAMLVersion.VERSION_20)
-            && getSaml2().getConditions() != null) {
-            validTill = getSaml2().getConditions().getNotOnOrAfter();
+        // The IssueInstant is read whether or not the assertion carries any Conditions at all.
+        // It is the fallback bound on the assertion's lifetime, so reading it only when there
+        // are Conditions would skip that bound in precisely the case it exists for.
+        if (getSamlVersion().equals(SAMLVersion.VERSION_20)) {
             issueInstant = getSaml2().getIssueInstant();
-        } else if (getSamlVersion().equals(SAMLVersion.VERSION_11)
-            && getSaml1().getConditions() != null) {
-            validTill = getSaml1().getConditions().getNotOnOrAfter();
+            if (getSaml2().getConditions() != null) {
+                validTill = getSaml2().getConditions().getNotOnOrAfter();
+            }
+        } else if (getSamlVersion().equals(SAMLVersion.VERSION_11)) {
             issueInstant = getSaml1().getIssueInstant();
+            if (getSaml1().getConditions() != null) {
+                validTill = getSaml1().getConditions().getNotOnOrAfter();
+            }
         }
 
         // Check the IssueInstant is not in the future, subject to the future TTL
diff --git a/ws-security-dom/src/test/java/org/apache/wss4j/dom/saml/SamlConditionsTest.java b/ws-security-dom/src/test/java/org/apache/wss4j/dom/saml/SamlConditionsTest.java
index 3fd642a6d..7bfd95d1a 100644
--- a/ws-security-dom/src/test/java/org/apache/wss4j/dom/saml/SamlConditionsTest.java
+++ b/ws-security-dom/src/test/java/org/apache/wss4j/dom/saml/SamlConditionsTest.java
@@ -284,6 +284,76 @@ public class SamlConditionsTest {
         verify(unsignedDoc);
     }
 
+    /**
+     * An assertion with no Conditions element at all has no NotOnOrAfter, so the TTL on the
+     * IssueInstant is the only bound on its lifetime that exists. It has to be applied, or such
+     * an assertion is good forever.
+     */
+    @Test
+    public void testSAML2StaleIssueInstantWithNoConditions() throws Exception {
+        SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+        callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+        callbackHandler.setIssuer("www.example.com");
+
+        SAMLCallback samlCallback = new SAMLCallback();
+        SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+        SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
+
+        Instant issueInstant = Instant.now().minus(Duration.ofMinutes(31));
+        samlAssertion.getSaml2().setIssueInstant(issueInstant);
+        samlAssertion.getSaml2().setConditions(null);
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader(doc);
+        secHeader.insertSecurityHeader();
+
+        WSSecSAMLToken wsSign = new WSSecSAMLToken(secHeader);
+
+        Document unsignedDoc = wsSign.build(samlAssertion);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("SAML 2 Authn Assertion (sender vouches):");
+            String outputString =
+                XMLUtils.prettyDocumentToString(unsignedDoc);
+            LOG.debug(outputString);
+        }
+
+        try {
+            verify(unsignedDoc);
+            fail("Failure expected in processing a stale SAML Assertion");
+        } catch (WSSecurityException ex) {
+            assertTrue(ex.getMessage().contains("SAML token security failure"));
+        }
+    }
+
+    /**
+     * The same assertion within the TTL is accepted: an assertion is not required to carry
+     * Conditions, only to be recent when it does not.
+     */
+    @Test
+    public void testSAML2FreshIssueInstantWithNoConditions() throws Exception {
+        SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+        callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+        callbackHandler.setIssuer("www.example.com");
+
+        SAMLCallback samlCallback = new SAMLCallback();
+        SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+        SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
+
+        samlAssertion.getSaml2().setIssueInstant(Instant.now().minusSeconds(5));
+        samlAssertion.getSaml2().setConditions(null);
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader(doc);
+        secHeader.insertSecurityHeader();
+
+        WSSecSAMLToken wsSign = new WSSecSAMLToken(secHeader);
+
+        Document unsignedDoc = wsSign.build(samlAssertion);
+
+        verify(unsignedDoc);
+    }
+
     @Test
     public void testSAML1StaleIssueInstant() throws Exception {
         SAML1CallbackHandler callbackHandler = new SAML1CallbackHandler();
@@ -322,6 +392,40 @@ public class SamlConditionsTest {
         }
     }
 
+    /**
+     * The SAML 1.1 counterpart: no Conditions element, so the IssueInstant TTL is the only
+     * bound there is.
+     */
+    @Test
+    public void testSAML1StaleIssueInstantWithNoConditions() throws Exception {
+        SAML1CallbackHandler callbackHandler = new SAML1CallbackHandler();
+        callbackHandler.setStatement(SAML1CallbackHandler.Statement.AUTHN);
+        callbackHandler.setIssuer("www.example.com");
+
+        SAMLCallback samlCallback = new SAMLCallback();
+        SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+        SamlAssertionWrapper samlAssertion = new SamlAssertionWrapper(samlCallback);
+
+        Instant issueInstant = Instant.now().minus(Duration.ofMinutes(31));
+        samlAssertion.getSaml1().setIssueInstant(issueInstant);
+        samlAssertion.getSaml1().setConditions(null);
+
+        Document doc = SOAPUtil.toSOAPPart(SOAPUtil.SAMPLE_SOAP_MSG);
+        WSSecHeader secHeader = new WSSecHeader(doc);
+        secHeader.insertSecurityHeader();
+
+        WSSecSAMLToken wsSign = new WSSecSAMLToken(secHeader);
+
+        Document unsignedDoc = wsSign.build(samlAssertion);
+
+        try {
+            verify(unsignedDoc);
+            fail("Failure expected in processing a stale SAML Assertion");
+        } catch (WSSecurityException ex) {
+            assertTrue(ex.getMessage().contains("SAML token security failure"));
+        }
+    }
+
     /**
      * Test that creates, sends and processes an unsigned SAML 2 authentication assertion
      * with an (invalid) custom Conditions statement.