You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2015/07/30 18:56:28 UTC
cxf git commit: Adding SAML SSO tests.
Repository: cxf
Updated Branches:
refs/heads/master b32c5d0cb -> 845eccb64
Adding SAML SSO tests.
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/845eccb6
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/845eccb6
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/845eccb6
Branch: refs/heads/master
Commit: 845eccb6484b43ba02875c71e824db23ae4f20c0
Parents: b32c5d0
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Thu Jul 30 17:55:32 2015 +0100
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Thu Jul 30 17:55:32 2015 +0100
----------------------------------------------------------------------
.../saml/sso/SAMLSSOResponseValidator.java | 9 +-
.../saml/sso/AbstractSAMLCallbackHandler.java | 4 +
.../saml/sso/CombinedValidatorTest.java | 218 +++++++++++++++++++
3 files changed, 226 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cxf/blob/845eccb6/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
index e89216e..abf439f 100644
--- a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
+++ b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java
@@ -92,7 +92,7 @@ public class SAMLSSOResponseValidator {
}
// Validate Assertions
- boolean foundValidSubject = false;
+ org.opensaml.saml.saml2.core.Assertion validAssertion = null;
Date sessionNotOnOrAfter = null;
for (org.opensaml.saml.saml2.core.Assertion assertion : samlResponse.getAssertions()) {
// Check the Issuer
@@ -114,7 +114,7 @@ public class SAMLSSOResponseValidator {
org.opensaml.saml.saml2.core.Subject subject = assertion.getSubject();
if (validateAuthenticationSubject(subject, assertion.getID(), postBinding)) {
validateAudienceRestrictionCondition(assertion.getConditions());
- foundValidSubject = true;
+ validAssertion = assertion;
// Store Session NotOnOrAfter
for (AuthnStatement authnStatment : assertion.getAuthnStatements()) {
if (authnStatment.getSessionNotOnOrAfter() != null) {
@@ -123,10 +123,9 @@ public class SAMLSSOResponseValidator {
}
}
}
-
}
- if (!foundValidSubject) {
+ 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");
@@ -140,7 +139,7 @@ public class SAMLSSOResponseValidator {
}
// the assumption for now is that SAMLResponse will contain only a single assertion
- Element assertionElement = samlResponse.getAssertions().get(0).getDOM();
+ Element assertionElement = validAssertion.getDOM();
Element clonedAssertionElement = (Element)assertionElement.cloneNode(true);
validatorResponse.setAssertionElement(clonedAssertionElement);
validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement));
http://git-wip-us.apache.org/repos/asf/cxf/blob/845eccb6/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java
index 61653a1..9772967 100644
--- a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java
+++ b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/AbstractSAMLCallbackHandler.java
@@ -130,6 +130,10 @@ public abstract class AbstractSAMLCallbackHandler implements CallbackHandler {
this.subjectLocalityDnsAddress = dnsAddress;
}
+ public void setSubjectName(String subjectName) {
+ this.subjectName = subjectName;
+ }
+
public void setResource(String resource) {
this.resource = resource;
}
http://git-wip-us.apache.org/repos/asf/cxf/blob/845eccb6/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java
new file mode 100644
index 0000000..53aed3e
--- /dev/null
+++ b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java
@@ -0,0 +1,218 @@
+/**
+ * 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.io.InputStream;
+import java.security.KeyStore;
+import java.util.Collections;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.Merlin;
+import org.apache.wss4j.common.saml.OpenSAMLUtil;
+import org.apache.wss4j.common.saml.SAMLCallback;
+import org.apache.wss4j.common.saml.SAMLUtil;
+import org.apache.wss4j.common.saml.SamlAssertionWrapper;
+import org.apache.wss4j.common.saml.bean.AudienceRestrictionBean;
+import org.apache.wss4j.common.saml.bean.ConditionsBean;
+import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
+import org.apache.wss4j.common.util.Loader;
+import org.apache.wss4j.dom.WSConstants;
+import org.apache.wss4j.dom.WSSConfig;
+import org.joda.time.DateTime;
+import org.opensaml.saml.common.xml.SAMLConstants;
+import org.opensaml.saml.saml2.core.Response;
+import org.opensaml.saml.saml2.core.Status;
+
+/**
+ * Some unit tests for the SAMLProtocolResponseValidator and the SAMLSSOResponseValidator
+ */
+public class CombinedValidatorTest extends org.junit.Assert {
+
+ static {
+ WSSConfig.init();
+ OpenSAMLUtil.initSamlEngine();
+ }
+
+ @org.junit.Test
+ public void testSuccessfulValidation() throws Exception {
+
+ Element responseElement = createResponse();
+ Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement);
+
+ Crypto issuerCrypto = new Merlin();
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class);
+ InputStream input = Merlin.loadInputStream(loader, "alice.jks");
+ keyStore.load(input, "password".toCharArray());
+ ((Merlin)issuerCrypto).setKeyStore(keyStore);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator();
+ validator.validateSamlResponse(
+ marshalledResponse, issuerCrypto, new KeystorePasswordCallback()
+ );
+
+ // Test SSO validation
+ SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator();
+ ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer");
+ ssoValidator.setAssertionConsumerURL("http://recipient.apache.org");
+ ssoValidator.setClientAddress("http://apache.org");
+ ssoValidator.setRequestId("12345");
+ ssoValidator.setSpIdentifier("http://service.apache.org");
+
+ // Parse the response
+ SSOValidatorResponse ssoResponse =
+ ssoValidator.validateSamlResponse(marshalledResponse, false);
+ SamlAssertionWrapper parsedAssertion =
+ new SamlAssertionWrapper(ssoResponse.getAssertionElement());
+
+ assertEquals("alice", parsedAssertion.getSubjectName());
+ }
+
+ @org.junit.Test
+ public void testWrappingAttack3() throws Exception {
+ Element responseElement = createResponse();
+
+ // Get Assertion Element
+ Element assertionElement =
+ (Element)responseElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion").item(0);
+ assertNotNull(assertionElement);
+
+ // Clone it, strip the Signature, modify the Subject, change Subj Conf
+ Element clonedAssertion = (Element)assertionElement.cloneNode(true);
+ clonedAssertion.setAttributeNS(null, "ID", "_12345623562");
+ Element sigElement =
+ (Element)clonedAssertion.getElementsByTagNameNS(WSConstants.SIG_NS, "Signature").item(0);
+ clonedAssertion.removeChild(sigElement);
+
+ Element subjElement =
+ (Element)clonedAssertion.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Subject").item(0);
+ Element subjNameIdElement =
+ (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "NameID").item(0);
+ subjNameIdElement.setTextContent("bob");
+
+ Element subjConfElement =
+ (Element)subjElement.getElementsByTagNameNS(SAMLConstants.SAML20_NS, "SubjectConfirmation").item(0);
+ subjConfElement.setAttributeNS(null, "Method", SAML2Constants.CONF_SENDER_VOUCHES);
+
+ // Now insert the modified cloned Assertion into the Response before actual assertion
+ responseElement.insertBefore(clonedAssertion, assertionElement);
+
+ // System.out.println(DOM2Writer.nodeToString(responseElement));
+
+ Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement);
+
+ Crypto issuerCrypto = new Merlin();
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class);
+ InputStream input = Merlin.loadInputStream(loader, "alice.jks");
+ keyStore.load(input, "password".toCharArray());
+ ((Merlin)issuerCrypto).setKeyStore(keyStore);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator();
+ validator.validateSamlResponse(
+ marshalledResponse, issuerCrypto, new KeystorePasswordCallback()
+ );
+
+ // Test SSO validation
+ SAMLSSOResponseValidator ssoValidator = new SAMLSSOResponseValidator();
+ ssoValidator.setIssuerIDP("http://cxf.apache.org/issuer");
+ ssoValidator.setAssertionConsumerURL("http://recipient.apache.org");
+ ssoValidator.setClientAddress("http://apache.org");
+ ssoValidator.setRequestId("12345");
+ ssoValidator.setSpIdentifier("http://service.apache.org");
+
+ // Parse the response
+ SSOValidatorResponse ssoResponse =
+ ssoValidator.validateSamlResponse(marshalledResponse, false);
+ SamlAssertionWrapper parsedAssertion =
+ new SamlAssertionWrapper(ssoResponse.getAssertionElement());
+
+ assertEquals("alice", parsedAssertion.getSubjectName());
+ }
+
+ private Element createResponse() throws Exception {
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ docBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ Document doc = docBuilder.newDocument();
+
+ Status status =
+ SAML2PResponseComponentBuilder.createStatus(
+ SAMLProtocolResponseValidator.SAML2_STATUSCODE_SUCCESS, null
+ );
+ Response response =
+ SAML2PResponseComponentBuilder.createSAMLResponse(
+ "http://cxf.apache.org/saml", "http://cxf.apache.org/issuer", status
+ );
+
+ // Create an AuthenticationAssertion
+ SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+ callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+ callbackHandler.setIssuer("http://cxf.apache.org/issuer");
+ callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+ callbackHandler.setSubjectName("alice");
+
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+ callbackHandler.setSubjectConfirmationData(subjectConfirmationData);
+
+ ConditionsBean conditions = new ConditionsBean();
+ conditions.setNotBefore(new DateTime());
+ conditions.setNotAfter(new DateTime().plusMinutes(5));
+
+ AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean();
+ audienceRestriction.setAudienceURIs(Collections.singletonList("http://service.apache.org"));
+ conditions.setAudienceRestrictions(Collections.singletonList(audienceRestriction));
+ callbackHandler.setConditions(conditions);
+
+ SAMLCallback samlCallback = new SAMLCallback();
+ SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+ SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback);
+
+ Crypto issuerCrypto = new Merlin();
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ ClassLoader loader = Loader.getClassLoader(CombinedValidatorTest.class);
+ InputStream input = Merlin.loadInputStream(loader, "alice.jks");
+ keyStore.load(input, "password".toCharArray());
+ ((Merlin)issuerCrypto).setKeyStore(keyStore);
+
+ assertion.signAssertion("alice", "password", issuerCrypto, false);
+
+ response.getAssertions().add(assertion.getSaml2());
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+ assertNotNull(policyElement);
+
+ return policyElement;
+ }
+}