You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2017/08/04 07:37:05 UTC

[1/2] syncope git commit: [SYNCOPE-1185] Ensuring that the correct SAMLSSOResponseValidator is taken into account

Repository: syncope
Updated Branches:
  refs/heads/2_0_X 1cf0d2a02 -> b652bec77
  refs/heads/master 2a76703a8 -> 7e5d38b71


[SYNCOPE-1185] Ensuring that the correct SAMLSSOResponseValidator is taken into account


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/b652bec7
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/b652bec7
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/b652bec7

Branch: refs/heads/2_0_X
Commit: b652bec77f87090cbdeb48b9e5591265e754e493
Parents: 1cf0d2a
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Aug 4 09:36:00 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Aug 4 09:36:00 2017 +0200

----------------------------------------------------------------------
 .../saml/sso/SAMLSSOResponseValidator.java      | 369 ------------------
 .../core/logic/saml2/SAML2ReaderWriter.java     |  13 +-
 .../logic/saml2/SAMLSSOResponseValidator.java   | 371 +++++++++++++++++++
 3 files changed, 374 insertions(+), 379 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/b652bec7/ext/saml2sp/logic/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java b/ext/saml2sp/logic/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
deleted file mode 100644
index bff1fd8..0000000
--- a/ext/saml2sp/logic/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
+++ /dev/null
@@ -1,369 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.cxf.rs.security.saml.sso;
-
-import java.util.Date;
-import java.util.List;
-import java.util.logging.Logger;
-
-import org.w3c.dom.Element;
-
-import org.apache.cxf.common.logging.LogUtils;
-import org.apache.wss4j.common.ext.WSSecurityException;
-import org.apache.wss4j.common.saml.builder.SAML2Constants;
-import org.apache.wss4j.common.util.DOM2Writer;
-import org.opensaml.saml.saml2.core.AudienceRestriction;
-import org.opensaml.saml.saml2.core.AuthnStatement;
-
-/**
- * Validate a SAML 2.0 Protocol Response according to the Web SSO profile. The Response
- * should be validated by the SAMLProtocolResponseValidator first.
- */
-//CHECKSTYLE:OFF
-public class SAMLSSOResponseValidator {
-    
-    private static final Logger LOG = LogUtils.getL7dLogger(SAMLSSOResponseValidator.class);
-    
-    private String issuerIDP;
-    private String assertionConsumerURL;
-    private String clientAddress;
-    private String requestId;
-    private String spIdentifier;
-    private boolean enforceResponseSigned;
-    private boolean enforceAssertionsSigned = true;
-    private boolean enforceKnownIssuer = true;
-    private TokenReplayCache<String> replayCache;
-    
-    /**
-     * Enforce that Assertions contained in the Response must be signed (if the Response itself is not
-     * signed). The default is true.
-     */
-    public void setEnforceAssertionsSigned(boolean enforceAssertionsSigned) {
-        this.enforceAssertionsSigned = enforceAssertionsSigned;
-    }
-    
-    /**
-     * Enforce that the Issuer of the received Response/Assertion is known. The default is true.
-     */
-    public void setEnforceKnownIssuer(boolean enforceKnownIssuer) {
-        this.enforceKnownIssuer = enforceKnownIssuer;
-    }
-    
-    /**
-     * Validate a SAML 2 Protocol Response
-     * @param samlResponse
-     * @param postBinding
-     * @return a SSOValidatorResponse object
-     * @throws WSSecurityException
-     */
-    public SSOValidatorResponse validateSamlResponse(
-        org.opensaml.saml.saml2.core.Response samlResponse,
-        boolean postBinding
-    ) throws WSSecurityException {
-        // Check the Issuer
-        validateIssuer(samlResponse.getIssuer());
-
-        // The Response must contain at least one Assertion.
-        if (samlResponse.getAssertions() == null || samlResponse.getAssertions().isEmpty()) {
-            LOG.fine("The Response must contain at least one Assertion");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // The Response must contain a Destination that matches the assertionConsumerURL if it is
-        // signed
-        String destination = samlResponse.getDestination();
-        if (samlResponse.isSigned()
-            && (destination == null || !destination.equals(assertionConsumerURL))) {
-            LOG.fine("The Response must contain a destination that matches the assertion consumer URL");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        if (enforceResponseSigned && !samlResponse.isSigned()) {
-            LOG.fine("The Response must be signed!");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // Validate Assertions
-        org.opensaml.saml.saml2.core.Assertion validAssertion = null;
-        Date sessionNotOnOrAfter = null;
-        for (org.opensaml.saml.saml2.core.Assertion assertion : samlResponse.getAssertions()) {
-            // Check the Issuer
-            if (assertion.getIssuer() == null) {
-                LOG.fine("Assertion Issuer must not be null");
-                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-            }
-            validateIssuer(assertion.getIssuer());
-            
-            if (!samlResponse.isSigned() && enforceAssertionsSigned && assertion.getSignature() == null) {
-                LOG.fine("The enclosed assertions in the SAML Response must be signed");
-                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-            }
-            
-            // Check for AuthnStatements and validate the Subject accordingly
-            if (assertion.getAuthnStatements() != null
-                && !assertion.getAuthnStatements().isEmpty()) {
-                org.opensaml.saml.saml2.core.Subject subject = assertion.getSubject();
-                org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf = 
-                    validateAuthenticationSubject(subject, assertion.getID(), postBinding);
-                if (subjectConf != null) {
-                    validateAudienceRestrictionCondition(assertion.getConditions());
-                    validAssertion = assertion;
-                    // Store Session NotOnOrAfter
-                    for (AuthnStatement authnStatment : assertion.getAuthnStatements()) {
-                        if (authnStatment.getSessionNotOnOrAfter() != null) {
-                            sessionNotOnOrAfter = authnStatment.getSessionNotOnOrAfter().toDate();
-                        }
-                    }
-                    // Fall back to the SubjectConfirmationData NotOnOrAfter if we have no session NotOnOrAfter
-                    if (sessionNotOnOrAfter == null) {
-                        sessionNotOnOrAfter = subjectConf.getSubjectConfirmationData().getNotOnOrAfter().toDate();
-                    }
-                }
-            }
-        }
-        
-        if (validAssertion == null) {
-            LOG.fine("The Response did not contain any Authentication Statement that matched "
-                     + "the Subject Confirmation criteria");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        SSOValidatorResponse validatorResponse = new SSOValidatorResponse();
-        validatorResponse.setResponseId(samlResponse.getID());
-        validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter);
-        if (samlResponse.getIssueInstant() != null) {
-            validatorResponse.setCreated(samlResponse.getIssueInstant().toDate());
-        }
-        
-        Element assertionElement = validAssertion.getDOM();
-        Element clonedAssertionElement = (Element)assertionElement.cloneNode(true);
-        validatorResponse.setAssertionElement(clonedAssertionElement);
-        validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement));
-        
-        return validatorResponse;
-    }
-    
-    /**
-     * Validate the Issuer (if it exists)
-     */
-    private void validateIssuer(org.opensaml.saml.saml2.core.Issuer issuer) throws WSSecurityException {
-        if (issuer == null) {
-            return;
-        }
-        
-        // Issuer value must match (be contained in) Issuer IDP
-        if (enforceKnownIssuer && !issuerIDP.startsWith(issuer.getValue())) {
-            LOG.fine("Issuer value: " + issuer.getValue() + " does not match issuer IDP: " 
-                + issuerIDP);
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // Format must be nameid-format-entity
-        if (issuer.getFormat() != null
-            && !SAML2Constants.NAMEID_FORMAT_ENTITY.equals(issuer.getFormat())) {
-            LOG.fine("Issuer format is not null and does not equal: " 
-                + SAML2Constants.NAMEID_FORMAT_ENTITY);
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-    }
-    
-    /**
-     * Validate the Subject (of an Authentication Statement).
-     */
-    private org.opensaml.saml.saml2.core.SubjectConfirmation validateAuthenticationSubject(
-        org.opensaml.saml.saml2.core.Subject subject, String id, boolean postBinding
-    ) throws WSSecurityException {
-        if (subject.getSubjectConfirmations() == null) {
-            return null;
-        }
-        
-        org.opensaml.saml.saml2.core.SubjectConfirmation validSubjectConf = null;
-        // We need to find a Bearer Subject Confirmation method
-        for (org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf 
-            : subject.getSubjectConfirmations()) {
-            if (SAML2Constants.CONF_BEARER.equals(subjectConf.getMethod())) {
-                validateSubjectConfirmation(subjectConf.getSubjectConfirmationData(), id, postBinding);
-                validSubjectConf = subjectConf;
-            }
-        }
-        
-        return validSubjectConf;
-    }
-    
-    /**
-     * Validate a (Bearer) Subject Confirmation
-     */
-    private void validateSubjectConfirmation(
-        org.opensaml.saml.saml2.core.SubjectConfirmationData subjectConfData, String id, boolean postBinding
-    ) throws WSSecurityException {
-        if (subjectConfData == null) {
-            LOG.fine("Subject Confirmation Data of a Bearer Subject Confirmation is null");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // Recipient must match assertion consumer URL
-        String recipient = subjectConfData.getRecipient();
-        if (recipient == null || !recipient.equals(assertionConsumerURL)) {
-            LOG.fine("Recipient " + recipient + " does not match assertion consumer URL "
-                + assertionConsumerURL);
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // We must have a NotOnOrAfter timestamp
-        if (subjectConfData.getNotOnOrAfter() == null
-            || subjectConfData.getNotOnOrAfter().isBeforeNow()) {
-            LOG.fine("Subject Conf Data does not contain NotOnOrAfter or it has expired");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // Need to keep bearer assertion IDs based on NotOnOrAfter to detect replay attacks
-        if (postBinding && replayCache != null) {
-            if (replayCache.getId(id) == null) {
-                Date expires = subjectConfData.getNotOnOrAfter().toDate();
-                Date currentTime = new Date();
-                long ttl = expires.getTime() - currentTime.getTime();
-                replayCache.putId(id, ttl / 1000L);
-            } else {
-                LOG.fine("Replay attack with token id: " + id);
-                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-            }
-        }
-        
-        // Check address
-        if (subjectConfData.getAddress() != null && clientAddress != null
-            && !subjectConfData.getAddress().equals(clientAddress)) {
-            LOG.fine("Subject Conf Data address " + subjectConfData.getAddress() + " does match"
-                     + " client address " + clientAddress);
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // It must not contain a NotBefore timestamp
-        if (subjectConfData.getNotBefore() != null) {
-            LOG.fine("The Subject Conf Data must not contain a NotBefore timestamp");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-        // InResponseTo must match the AuthnRequest request Id
-        if (requestId != null && !requestId.equals(subjectConfData.getInResponseTo())) {
-            LOG.fine("The InResponseTo String does match the original request id " + requestId);
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        } else if (requestId == null && subjectConfData.getInResponseTo() != null) {
-            LOG.fine("No InResponseTo String is allowed for the unsolicted case");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        
-    }
-    
-    private void validateAudienceRestrictionCondition(
-        org.opensaml.saml.saml2.core.Conditions conditions
-    ) throws WSSecurityException {
-        if (conditions == null) {
-            LOG.fine("Conditions are null");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-        List<AudienceRestriction> audienceRestrs = conditions.getAudienceRestrictions();
-        if (!matchSaml2AudienceRestriction(spIdentifier, audienceRestrs)) {
-            LOG.fine("Assertion does not contain unique subject provider identifier " 
-                     + spIdentifier + " in the audience restriction conditions");
-            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
-        }
-    }
-    
-    
-    private boolean matchSaml2AudienceRestriction(
-        String appliesTo, List<AudienceRestriction> audienceRestrictions
-    ) {
-        boolean oneMatchFound = false;
-        if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
-            for (AudienceRestriction audienceRestriction : audienceRestrictions) {
-                if (audienceRestriction.getAudiences() != null) {
-                    boolean matchFound = false;
-                    for (org.opensaml.saml.saml2.core.Audience audience : audienceRestriction.getAudiences()) {
-                        if (appliesTo.equals(audience.getAudienceURI())) {
-                            matchFound = true;
-                            oneMatchFound = true;
-                            break;
-                        }
-                    }
-                    if (!matchFound) {
-                        return false;
-                    }
-                }
-            }
-        }
-
-        return oneMatchFound;
-    }
-
-    public String getIssuerIDP() {
-        return issuerIDP;
-    }
-
-    public void setIssuerIDP(String issuerIDP) {
-        this.issuerIDP = issuerIDP;
-    }
-
-    public String getAssertionConsumerURL() {
-        return assertionConsumerURL;
-    }
-
-    public void setAssertionConsumerURL(String assertionConsumerURL) {
-        this.assertionConsumerURL = assertionConsumerURL;
-    }
-
-    public String getClientAddress() {
-        return clientAddress;
-    }
-
-    public void setClientAddress(String clientAddress) {
-        this.clientAddress = clientAddress;
-    }
-
-    public String getRequestId() {
-        return requestId;
-    }
-
-    public void setRequestId(String requestId) {
-        this.requestId = requestId;
-    }
-
-    public String getSpIdentifier() {
-        return spIdentifier;
-    }
-
-    public void setSpIdentifier(String spIdentifier) {
-        this.spIdentifier = spIdentifier;
-    }
-    
-    public void setReplayCache(TokenReplayCache<String> replayCache) {
-        this.replayCache = replayCache;
-    }
-
-    public boolean isEnforceResponseSigned() {
-        return enforceResponseSigned;
-    }
-
-    /**
-     * Enforce whether a SAML Response must be signed.
-     */
-    public void setEnforceResponseSigned(boolean enforceResponseSigned) {
-        this.enforceResponseSigned = enforceResponseSigned;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/syncope/blob/b652bec7/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
index 8e47911..6fe20e6 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
@@ -43,7 +43,6 @@ import javax.xml.transform.stream.StreamResult;
 import org.apache.commons.codec.binary.Base64;
 import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
 import org.apache.cxf.rs.security.saml.sso.SAMLProtocolResponseValidator;
-import org.apache.cxf.rs.security.saml.sso.SAMLSSOResponseValidator;
 import org.apache.cxf.staxutils.StaxUtils;
 import org.apache.syncope.common.lib.SSOConstants;
 import org.apache.syncope.common.lib.types.SAML2BindingType;
@@ -90,10 +89,6 @@ public class SAML2ReaderWriter {
 
     private String jceSigAlgo;
 
-    private SAMLProtocolResponseValidator protocolValidator;
-
-    private SAMLSSOResponseValidator ssoResponseValidator;
-
     private SAMLSPCallbackHandler callbackHandler;
 
     public void init() {
@@ -109,11 +104,6 @@ public class SAML2ReaderWriter {
             jceSigAlgo = "SHA1withDSA";
         }
 
-        protocolValidator = new SAMLProtocolResponseValidator();
-        protocolValidator.setKeyInfoMustBeAvailable(true);
-
-        ssoResponseValidator = new SAMLSSOResponseValidator();
-
         callbackHandler = new SAMLSPCallbackHandler(loader.getKeyPass());
     }
 
@@ -223,8 +213,11 @@ public class SAML2ReaderWriter {
         crypto.setKeyStore(loader.getKeyStore());
         crypto.setTrustStore(idp.getTrustStore());
 
+        SAMLProtocolResponseValidator protocolValidator = new SAMLProtocolResponseValidator();
+        protocolValidator.setKeyInfoMustBeAvailable(true);
         protocolValidator.validateSamlResponse(samlResponse, crypto, callbackHandler);
 
+        SAMLSSOResponseValidator ssoResponseValidator = new SAMLSSOResponseValidator();
         ssoResponseValidator.setAssertionConsumerURL(assertionConsumerURL);
         ssoResponseValidator.setIssuerIDP(idp.getId());
         ssoResponseValidator.setRequestId(requestId);

http://git-wip-us.apache.org/repos/asf/syncope/blob/b652bec7/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAMLSSOResponseValidator.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAMLSSOResponseValidator.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAMLSSOResponseValidator.java
new file mode 100644
index 0000000..a730140
--- /dev/null
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAMLSSOResponseValidator.java
@@ -0,0 +1,371 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.logic.saml2;
+
+import java.util.Date;
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.w3c.dom.Element;
+
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.rs.security.saml.sso.SSOValidatorResponse;
+import org.apache.cxf.rs.security.saml.sso.TokenReplayCache;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.opensaml.saml.saml2.core.AudienceRestriction;
+import org.opensaml.saml.saml2.core.AuthnStatement;
+
+/**
+ * Validate a SAML 2.0 Protocol Response according to the Web SSO profile. The Response
+ * should be validated by the SAMLProtocolResponseValidator first.
+ */
+//CHECKSTYLE:OFF
+public class SAMLSSOResponseValidator {
+    
+    private static final Logger LOG = LogUtils.getL7dLogger(SAMLSSOResponseValidator.class);
+    
+    private String issuerIDP;
+    private String assertionConsumerURL;
+    private String clientAddress;
+    private String requestId;
+    private String spIdentifier;
+    private boolean enforceResponseSigned;
+    private boolean enforceAssertionsSigned = true;
+    private boolean enforceKnownIssuer = true;
+    private TokenReplayCache<String> replayCache;
+    
+    /**
+     * Enforce that Assertions contained in the Response must be signed (if the Response itself is not
+     * signed). The default is true.
+     */
+    public void setEnforceAssertionsSigned(boolean enforceAssertionsSigned) {
+        this.enforceAssertionsSigned = enforceAssertionsSigned;
+    }
+    
+    /**
+     * Enforce that the Issuer of the received Response/Assertion is known. The default is true.
+     */
+    public void setEnforceKnownIssuer(boolean enforceKnownIssuer) {
+        this.enforceKnownIssuer = enforceKnownIssuer;
+    }
+    
+    /**
+     * Validate a SAML 2 Protocol Response
+     * @param samlResponse
+     * @param postBinding
+     * @return a SSOValidatorResponse object
+     * @throws WSSecurityException
+     */
+    public SSOValidatorResponse validateSamlResponse(
+        org.opensaml.saml.saml2.core.Response samlResponse,
+        boolean postBinding
+    ) throws WSSecurityException {
+        // Check the Issuer
+        validateIssuer(samlResponse.getIssuer());
+
+        // The Response must contain at least one Assertion.
+        if (samlResponse.getAssertions() == null || samlResponse.getAssertions().isEmpty()) {
+            LOG.fine("The Response must contain at least one Assertion");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // The Response must contain a Destination that matches the assertionConsumerURL if it is
+        // signed
+        String destination = samlResponse.getDestination();
+        if (samlResponse.isSigned()
+            && (destination == null || !destination.equals(assertionConsumerURL))) {
+            LOG.fine("The Response must contain a destination that matches the assertion consumer URL");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        if (enforceResponseSigned && !samlResponse.isSigned()) {
+            LOG.fine("The Response must be signed!");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Validate Assertions
+        org.opensaml.saml.saml2.core.Assertion validAssertion = null;
+        Date sessionNotOnOrAfter = null;
+        for (org.opensaml.saml.saml2.core.Assertion assertion : samlResponse.getAssertions()) {
+            // Check the Issuer
+            if (assertion.getIssuer() == null) {
+                LOG.fine("Assertion Issuer must not be null");
+                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+            }
+            validateIssuer(assertion.getIssuer());
+            
+            if (!samlResponse.isSigned() && enforceAssertionsSigned && assertion.getSignature() == null) {
+                LOG.fine("The enclosed assertions in the SAML Response must be signed");
+                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+            }
+            
+            // Check for AuthnStatements and validate the Subject accordingly
+            if (assertion.getAuthnStatements() != null
+                && !assertion.getAuthnStatements().isEmpty()) {
+                org.opensaml.saml.saml2.core.Subject subject = assertion.getSubject();
+                org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf = 
+                    validateAuthenticationSubject(subject, assertion.getID(), postBinding);
+                if (subjectConf != null) {
+                    validateAudienceRestrictionCondition(assertion.getConditions());
+                    validAssertion = assertion;
+                    // Store Session NotOnOrAfter
+                    for (AuthnStatement authnStatment : assertion.getAuthnStatements()) {
+                        if (authnStatment.getSessionNotOnOrAfter() != null) {
+                            sessionNotOnOrAfter = authnStatment.getSessionNotOnOrAfter().toDate();
+                        }
+                    }
+                    // Fall back to the SubjectConfirmationData NotOnOrAfter if we have no session NotOnOrAfter
+                    if (sessionNotOnOrAfter == null) {
+                        sessionNotOnOrAfter = subjectConf.getSubjectConfirmationData().getNotOnOrAfter().toDate();
+                    }
+                }
+            }
+        }
+        
+        if (validAssertion == null) {
+            LOG.fine("The Response did not contain any Authentication Statement that matched "
+                     + "the Subject Confirmation criteria");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        SSOValidatorResponse validatorResponse = new SSOValidatorResponse();
+        validatorResponse.setResponseId(samlResponse.getID());
+        validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter);
+        if (samlResponse.getIssueInstant() != null) {
+            validatorResponse.setCreated(samlResponse.getIssueInstant().toDate());
+        }
+        
+        Element assertionElement = validAssertion.getDOM();
+        Element clonedAssertionElement = (Element)assertionElement.cloneNode(true);
+        validatorResponse.setAssertionElement(clonedAssertionElement);
+        validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement));
+        
+        return validatorResponse;
+    }
+    
+    /**
+     * Validate the Issuer (if it exists)
+     */
+    private void validateIssuer(org.opensaml.saml.saml2.core.Issuer issuer) throws WSSecurityException {
+        if (issuer == null) {
+            return;
+        }
+        
+        // Issuer value must match (be contained in) Issuer IDP
+        if (enforceKnownIssuer && !issuerIDP.startsWith(issuer.getValue())) {
+            LOG.fine("Issuer value: " + issuer.getValue() + " does not match issuer IDP: " 
+                + issuerIDP);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Format must be nameid-format-entity
+        if (issuer.getFormat() != null
+            && !SAML2Constants.NAMEID_FORMAT_ENTITY.equals(issuer.getFormat())) {
+            LOG.fine("Issuer format is not null and does not equal: " 
+                + SAML2Constants.NAMEID_FORMAT_ENTITY);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+    /**
+     * Validate the Subject (of an Authentication Statement).
+     */
+    private org.opensaml.saml.saml2.core.SubjectConfirmation validateAuthenticationSubject(
+        org.opensaml.saml.saml2.core.Subject subject, String id, boolean postBinding
+    ) throws WSSecurityException {
+        if (subject.getSubjectConfirmations() == null) {
+            return null;
+        }
+        
+        org.opensaml.saml.saml2.core.SubjectConfirmation validSubjectConf = null;
+        // We need to find a Bearer Subject Confirmation method
+        for (org.opensaml.saml.saml2.core.SubjectConfirmation subjectConf 
+            : subject.getSubjectConfirmations()) {
+            if (SAML2Constants.CONF_BEARER.equals(subjectConf.getMethod())) {
+                validateSubjectConfirmation(subjectConf.getSubjectConfirmationData(), id, postBinding);
+                validSubjectConf = subjectConf;
+            }
+        }
+        
+        return validSubjectConf;
+    }
+    
+    /**
+     * Validate a (Bearer) Subject Confirmation
+     */
+    private void validateSubjectConfirmation(
+        org.opensaml.saml.saml2.core.SubjectConfirmationData subjectConfData, String id, boolean postBinding
+    ) throws WSSecurityException {
+        if (subjectConfData == null) {
+            LOG.fine("Subject Confirmation Data of a Bearer Subject Confirmation is null");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Recipient must match assertion consumer URL
+        String recipient = subjectConfData.getRecipient();
+        if (recipient == null || !recipient.equals(assertionConsumerURL)) {
+            LOG.fine("Recipient " + recipient + " does not match assertion consumer URL "
+                + assertionConsumerURL);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // We must have a NotOnOrAfter timestamp
+        if (subjectConfData.getNotOnOrAfter() == null
+            || subjectConfData.getNotOnOrAfter().isBeforeNow()) {
+            LOG.fine("Subject Conf Data does not contain NotOnOrAfter or it has expired");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // Need to keep bearer assertion IDs based on NotOnOrAfter to detect replay attacks
+        if (postBinding && replayCache != null) {
+            if (replayCache.getId(id) == null) {
+                Date expires = subjectConfData.getNotOnOrAfter().toDate();
+                Date currentTime = new Date();
+                long ttl = expires.getTime() - currentTime.getTime();
+                replayCache.putId(id, ttl / 1000L);
+            } else {
+                LOG.fine("Replay attack with token id: " + id);
+                throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+            }
+        }
+        
+        // Check address
+        if (subjectConfData.getAddress() != null && clientAddress != null
+            && !subjectConfData.getAddress().equals(clientAddress)) {
+            LOG.fine("Subject Conf Data address " + subjectConfData.getAddress() + " does match"
+                     + " client address " + clientAddress);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // It must not contain a NotBefore timestamp
+        if (subjectConfData.getNotBefore() != null) {
+            LOG.fine("The Subject Conf Data must not contain a NotBefore timestamp");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+        // InResponseTo must match the AuthnRequest request Id
+        if (requestId != null && !requestId.equals(subjectConfData.getInResponseTo())) {
+            LOG.fine("The InResponseTo String does match the original request id " + requestId);
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        } else if (requestId == null && subjectConfData.getInResponseTo() != null) {
+            LOG.fine("No InResponseTo String is allowed for the unsolicted case");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        
+    }
+    
+    private void validateAudienceRestrictionCondition(
+        org.opensaml.saml.saml2.core.Conditions conditions
+    ) throws WSSecurityException {
+        if (conditions == null) {
+            LOG.fine("Conditions are null");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+        List<AudienceRestriction> audienceRestrs = conditions.getAudienceRestrictions();
+        if (!matchSaml2AudienceRestriction(spIdentifier, audienceRestrs)) {
+            LOG.fine("Assertion does not contain unique subject provider identifier " 
+                     + spIdentifier + " in the audience restriction conditions");
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+        }
+    }
+    
+    
+    private boolean matchSaml2AudienceRestriction(
+        String appliesTo, List<AudienceRestriction> audienceRestrictions
+    ) {
+        boolean oneMatchFound = false;
+        if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
+            for (AudienceRestriction audienceRestriction : audienceRestrictions) {
+                if (audienceRestriction.getAudiences() != null) {
+                    boolean matchFound = false;
+                    for (org.opensaml.saml.saml2.core.Audience audience : audienceRestriction.getAudiences()) {
+                        if (appliesTo.equals(audience.getAudienceURI())) {
+                            matchFound = true;
+                            oneMatchFound = true;
+                            break;
+                        }
+                    }
+                    if (!matchFound) {
+                        return false;
+                    }
+                }
+            }
+        }
+
+        return oneMatchFound;
+    }
+
+    public String getIssuerIDP() {
+        return issuerIDP;
+    }
+
+    public void setIssuerIDP(String issuerIDP) {
+        this.issuerIDP = issuerIDP;
+    }
+
+    public String getAssertionConsumerURL() {
+        return assertionConsumerURL;
+    }
+
+    public void setAssertionConsumerURL(String assertionConsumerURL) {
+        this.assertionConsumerURL = assertionConsumerURL;
+    }
+
+    public String getClientAddress() {
+        return clientAddress;
+    }
+
+    public void setClientAddress(String clientAddress) {
+        this.clientAddress = clientAddress;
+    }
+
+    public String getRequestId() {
+        return requestId;
+    }
+
+    public void setRequestId(String requestId) {
+        this.requestId = requestId;
+    }
+
+    public String getSpIdentifier() {
+        return spIdentifier;
+    }
+
+    public void setSpIdentifier(String spIdentifier) {
+        this.spIdentifier = spIdentifier;
+    }
+    
+    public void setReplayCache(TokenReplayCache<String> replayCache) {
+        this.replayCache = replayCache;
+    }
+
+    public boolean isEnforceResponseSigned() {
+        return enforceResponseSigned;
+    }
+
+    /**
+     * Enforce whether a SAML Response must be signed.
+     */
+    public void setEnforceResponseSigned(boolean enforceResponseSigned) {
+        this.enforceResponseSigned = enforceResponseSigned;
+    }
+    
+}


[2/2] syncope git commit: [SYNCOPE-1185] Ensuring that the correct SAMLSSOResponseValidator is taken into account

Posted by il...@apache.org.
[SYNCOPE-1185] Ensuring that the correct SAMLSSOResponseValidator is taken into account


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/7e5d38b7
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/7e5d38b7
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/7e5d38b7

Branch: refs/heads/master
Commit: 7e5d38b7165fba5763d1c8fda2033f74f3814b13
Parents: 2a76703
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Fri Aug 4 09:36:56 2017 +0200
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Fri Aug 4 09:36:56 2017 +0200

----------------------------------------------------------------------
 .../syncope/core/logic/saml2/SAML2ReaderWriter.java     | 12 +++---------
 1 file changed, 3 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/7e5d38b7/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
----------------------------------------------------------------------
diff --git a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
index 3431a86..096dccb 100644
--- a/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
+++ b/ext/saml2sp/logic/src/main/java/org/apache/syncope/core/logic/saml2/SAML2ReaderWriter.java
@@ -90,10 +90,6 @@ public class SAML2ReaderWriter {
 
     private String jceSigAlgo;
 
-    private SAMLProtocolResponseValidator protocolValidator;
-
-    private SAMLSSOResponseValidator ssoResponseValidator;
-
     private SAMLSPCallbackHandler callbackHandler;
 
     public void init() {
@@ -109,11 +105,6 @@ public class SAML2ReaderWriter {
             jceSigAlgo = "SHA1withDSA";
         }
 
-        protocolValidator = new SAMLProtocolResponseValidator();
-        protocolValidator.setKeyInfoMustBeAvailable(true);
-
-        ssoResponseValidator = new SAMLSSOResponseValidator();
-
         callbackHandler = new SAMLSPCallbackHandler(loader.getKeyPass());
     }
 
@@ -223,8 +214,11 @@ public class SAML2ReaderWriter {
         crypto.setKeyStore(loader.getKeyStore());
         crypto.setTrustStore(idp.getTrustStore());
 
+        SAMLProtocolResponseValidator protocolValidator = new SAMLProtocolResponseValidator();
+        protocolValidator.setKeyInfoMustBeAvailable(true);
         protocolValidator.validateSamlResponse(samlResponse, crypto, callbackHandler);
 
+        SAMLSSOResponseValidator ssoResponseValidator = new SAMLSSOResponseValidator();
         ssoResponseValidator.setAssertionConsumerURL(assertionConsumerURL);
         ssoResponseValidator.setIssuerIDP(idp.getId());
         ssoResponseValidator.setRequestId(requestId);