[java-opensaml] 10/11: Enhance SAML 2 Assertion validator to validate Issuer.

Brent Putman putmanb at georgetown.edu
Thu Jan 30 02:17:35 EST 2020


This is an automated email from the git hooks/post-receive script.

putmanb pushed a commit to branch master
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=259899aec241d036c881f1cbec9cb6b7a24c8384

commit 259899aec241d036c881f1cbec9cb6b7a24c8384
Author: Brent Putman <putmanb at georgetown.edu>
AuthorDate: Thu Jan 30 01:50:26 2020 -0500

    Enhance SAML 2 Assertion validator to validate Issuer.
---
 .../saml2/assertion/SAML20AssertionValidator.java  | 63 +++++++++++++++++++
 .../SAML2AssertionValidationParameters.java        |  7 +++
 .../DefaultAssertionValidationContextBuilder.java  | 70 ++++++++++++++++++++++
 .../assertion/BaseAssertionValidationTest.java     |  2 +
 .../assertion/SAML20AssertionValidatorTest.java    | 32 ++++++++++
 5 files changed, 174 insertions(+)

diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java
index b3f9185..1b70dea 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidator.java
@@ -44,6 +44,7 @@ import org.opensaml.saml.common.assertion.ValidationResult;
 import org.opensaml.saml.saml2.core.Assertion;
 import org.opensaml.saml.saml2.core.Condition;
 import org.opensaml.saml.saml2.core.Conditions;
+import org.opensaml.saml.saml2.core.Issuer;
 import org.opensaml.saml.saml2.core.Statement;
 import org.opensaml.saml.saml2.core.Subject;
 import org.opensaml.saml.saml2.core.SubjectConfirmation;
@@ -236,6 +237,11 @@ public class SAML20AssertionValidator {
             return result;
         }
 
+        result = validateIssuer(assertion, context);
+        if (result != ValidationResult.VALID) {
+            return result;
+        }
+
         result = validateSignature(assertion, context);
         if (result != ValidationResult.VALID) {
             return result;
@@ -295,6 +301,63 @@ public class SAML20AssertionValidator {
     }
 
     /**
+     * Validates the Assertion {@link Issuer}.
+     * 
+     * @param assertion the assertion to validate
+     * @param context current validation context
+     * 
+     * @return the result of the validation evaluation
+     * 
+     * @throws AssertionValidationException if there is a problem validating the Issuer
+     */
+    protected ValidationResult validateIssuer(@Nonnull final Assertion assertion,
+            @Nonnull final ValidationContext context) throws AssertionValidationException {
+        
+        String issuer = null;
+        if (assertion.getIssuer() != null) {
+            issuer = StringSupport.trimOrNull(assertion.getIssuer().getValue());
+        }
+        if (issuer == null) {
+            log.warn("Assertion Issuer was missing and was required");
+            context.setValidationFailureMessage("Assertion Issuer was missing and was required");
+            return ValidationResult.INVALID;
+        }
+        
+        log.debug("Evaluating Assertion Issuer of : {}", issuer);
+
+        final Set<String> validIssuers;
+        try {
+            validIssuers = (Set<String>) context.getStaticParameters()
+                    .get(SAML2AssertionValidationParameters.VALID_ISSUERS);
+        } catch (final ClassCastException e) {
+            log.warn("The value of the static validation parameter '{}' was not java.util.Set<String>",
+                    SAML2AssertionValidationParameters.VALID_ISSUERS);
+            context.setValidationFailureMessage("Unable to determine list of valid issuers");
+            return ValidationResult.INDETERMINATE;
+        }
+        if (validIssuers == null || validIssuers.isEmpty()) {
+            log.warn("Set of valid issuers was not available from the validation context, unable to evaluate Issuer");
+            return ValidationResult.VALID;
+            /* TODO this should really be indeterminate, but would change the behavior for older code.
+               Need to update that first.
+            context.setValidationFailureMessage("Unable to determine list of valid issuers");
+            return ValidationResult.INDETERMINATE;
+            */
+        }
+
+        if (validIssuers.contains(issuer)) {
+            log.debug("Matched valid issuer: {}", issuer);
+            return ValidationResult.VALID;
+        }
+        
+        log.debug("Failed to match Issuer to any supplied valid issuers: {}", validIssuers);
+
+        context.setValidationFailureMessage(String.format(
+                "Issuer of Assertion '%s' did not match any valid issuers", assertion.getID()));
+        return ValidationResult.INVALID;
+    }
+
+    /**
      * Validates the signature of the assertion, if it is signed.
      * 
      * @param token assertion whose signature will be validated
diff --git a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
index 73ac07b..795977b 100644
--- a/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
+++ b/opensaml-saml-api/src/main/java/org/opensaml/saml/saml2/assertion/SAML2AssertionValidationParameters.java
@@ -47,6 +47,13 @@ public final class SAML2AssertionValidationParameters {
      * Carries the {@link org.opensaml.saml.saml2.core.SubjectConfirmation} that confirmed the subject.
      */
     public static final String CONFIRMED_SUBJECT_CONFIRMATION = STD_PREFIX + ".ConfirmedSubjectConfirmation";
+    
+    /**
+     * Carries a {@link java.util.Set}<code><</code>{@link java.lang.String}<code>></code>
+     * whose values are the acceptable 
+     * {@link org.opensaml.saml.saml2.core.Issuer} values.
+     */
+    public static final String VALID_ISSUERS = STD_PREFIX + ".ValidIssuers";
 
     /**
      * Carries a {@link java.lang.Boolean} flag which indicates whether the Assertion is required to be signed.
diff --git a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java
index b8afff4..183dd05 100644
--- a/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java
+++ b/opensaml-saml-impl/src/main/java/org/opensaml/saml/saml2/profile/impl/DefaultAssertionValidationContextBuilder.java
@@ -50,6 +50,8 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.opensaml.saml.common.assertion.ValidationContext;
 import org.opensaml.saml.common.binding.SAMLBindingSupport;
+import org.opensaml.saml.common.binding.security.impl.MessageContextEntityIDLookup;
+import org.opensaml.saml.common.binding.security.impl.MessageContextEntityIDLookup.Direction;
 import org.opensaml.saml.common.messaging.context.SAMLMetadataContext;
 import org.opensaml.saml.common.messaging.context.SAMLPeerEntityContext;
 import org.opensaml.saml.common.messaging.context.SAMLProtocolContext;
@@ -100,6 +102,9 @@ public class DefaultAssertionValidationContextBuilder
     /** Function for determining additional valid audience values. */
     private Function<ProfileRequestContext, Set<String>> additionalAudiences;
     
+    /** Function for determining additional valid Issuer values. */
+    private Function<ProfileRequestContext, Set<String>> validIssuers;
+    
     /** Function for determining the valid InResponseTo value. */
     private Function<ProfileRequestContext, String> inResponseTo;
     
@@ -138,6 +143,7 @@ public class DefaultAssertionValidationContextBuilder
         notBeforeRequired = Predicates.alwaysFalse();
         addressRequired = Predicates.alwaysFalse();
         requiredConditions = Collections.emptySet();
+        validIssuers = new DefaultValidIssuersLookupFunction();
 
         securityParametersLookupStrategy = new ChildContextLookup<>(SecurityParametersContext.class)
                 .compose(new InboundMessageContextLookup());
@@ -446,6 +452,32 @@ public class DefaultAssertionValidationContextBuilder
     }
 
     /**
+     * Get the function for determining the valid Issuer values
+     *
+     * <p>
+     * Defaults to an implementation which resolves the outbound SAML peer entityID.
+     * </p>
+     *
+     * @return the function
+     */
+    public Function<ProfileRequestContext,Set<String>> getValidIssuers() {
+        return validIssuers;
+    }
+
+    /**
+     * Set the function for determining the valid Issuer values
+     *
+     * <p>
+     * Defaults to an implementation which resolves the outbound SAML peer entityID.
+     * </p>
+     *
+     * @param function the function, may be null
+     */
+    public void setValidIssuers(final @Nonnull Function<ProfileRequestContext,Set<String>> function) {
+        validIssuers = Constraint.isNotNull(function, "Valied Issuers function was null");
+    }
+
+    /**
      * Get the function for determining the max allowed time since authentication.
      *
      * <p>
@@ -519,6 +551,10 @@ public class DefaultAssertionValidationContextBuilder
         
         final TreeMap<String, Object> staticParams = new TreeMap<>();
         
+        // For Issuer
+        staticParams.put(SAML2AssertionValidationParameters.VALID_ISSUERS,
+                getValidIssuers().apply(input.getProfileRequestContext()));
+        
         //For signature validation
         staticParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, 
                 Boolean.valueOf(getSignatureRequired().test(input.getProfileRequestContext())));
@@ -903,5 +939,39 @@ public class DefaultAssertionValidationContextBuilder
         }
         
     }
+    
+    /** 
+     * Default strategy for resolving the valid Issuers.
+     * 
+     * <p>
+     * Resolves the entityID from the {@link SAMLPeerEntityContext} child of the outbound {@link MessageContext}.
+     * </p>
+     * */
+    public static class DefaultValidIssuersLookupFunction implements Function<ProfileRequestContext, Set<String>> {
+        
+        /** The lookup delegate. */
+        private MessageContextEntityIDLookup delegate;
+
+        /** Constructor. */
+        public DefaultValidIssuersLookupFunction() {
+            delegate = new MessageContextEntityIDLookup(SAMLPeerEntityContext.class);
+            delegate.setDirection(Direction.OUTBOUND);
+        }
+
+        /** {@inheritDoc} */
+        public Set<String> apply(ProfileRequestContext prc) {
+            if (prc == null || prc.getInboundMessageContext() == null) {
+                return null;
+            }
+            
+            // Note: Doesn't matter whether we apply to inbound or outbound
+            final String entityID = delegate.apply(prc.getInboundMessageContext());
+            if (entityID != null) {
+                return Collections.singleton(entityID);
+            }
+            return Collections.emptySet();
+        }
+        
+    }
 
 }
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java
index 779f8f6..6fa4d87 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/BaseAssertionValidationTest.java
@@ -128,6 +128,8 @@ public class BaseAssertionValidationTest extends XMLObjectBaseTestCase {
         
         params.put(SAML2AssertionValidationParameters.CLOCK_SKEW, CLOCK_SKEW);
         
+        params.put(SAML2AssertionValidationParameters.VALID_ISSUERS, Collections.singleton(ISSUER));
+        
         params.put(SAML2AssertionValidationParameters.SC_VALID_IN_RESPONSE_TO, SUBJECT_CONFIRMATION_IN_RESPONSE_TO);
         
         params.put(SAML2AssertionValidationParameters.SC_VALID_RECIPIENTS, 
diff --git a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidatorTest.java b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidatorTest.java
index 8e887e5..95311f8 100644
--- a/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidatorTest.java
+++ b/opensaml-saml-impl/src/test/java/org/opensaml/saml/saml2/assertion/SAML20AssertionValidatorTest.java
@@ -550,6 +550,38 @@ public class SAML20AssertionValidatorTest extends BaseAssertionValidationTest {
         Assert.assertEquals(validator.validate(assertion, validationContext), ValidationResult.INVALID);
     }
     
+    @Test
+    public void testInvalidIssuer() throws AssertionValidationException {
+        getAssertion().getIssuer().setValue("invalid");
+        
+        validator = getCurrentValidator();
+        
+        Map<String,Object> staticParams = buildBasicStaticParameters();
+        staticParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false);
+        
+        ValidationContext validationContext = new ValidationContext(staticParams);
+        
+        Assertion assertion = getAssertion();
+        
+        Assert.assertEquals(validator.validate(assertion, validationContext), ValidationResult.INVALID);
+    }
+    
+    @Test
+    public void testNoIssuer() throws AssertionValidationException {
+        getAssertion().setIssuer(null);
+        
+        validator = getCurrentValidator();
+        
+        Map<String,Object> staticParams = buildBasicStaticParameters();
+        staticParams.put(SAML2AssertionValidationParameters.SIGNATURE_REQUIRED, false);
+        
+        ValidationContext validationContext = new ValidationContext(staticParams);
+        
+        Assertion assertion = getAssertion();
+        
+        Assert.assertEquals(validator.validate(assertion, validationContext), ValidationResult.INVALID);
+    }
+    
 
     
     

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list