Repository: cxf Updated Branches: refs/heads/2.6.x-fixes 7aa3f2da2 -> 72c4194a6
Adding SAML SSO tests. Conflicts: rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidator.java Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/f318ee61 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/f318ee61 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/f318ee61 Branch: refs/heads/2.6.x-fixes Commit: f318ee614c8cdc6f2c485d42ff2d670d67e6455a Parents: 7aa3f2d Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Thu Jul 30 17:55:32 2015 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Fri Jun 16 10:08:56 2017 +0100 ---------------------------------------------------------------------- .../saml/sso/SAMLSSOResponseValidator.java | 15 +- .../saml/sso/AbstractSAMLCallbackHandler.java | 4 + .../saml/sso/CombinedValidatorTest.java | 218 +++++++++++++++++++ 3 files changed, 233 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/f318ee61/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 370a3ce..096468c 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.saml2.core.Assertion assertion : samlResponse.getAssertions()) { // Check the Issuer @@ -114,7 +114,7 @@ public class SAMLSSOResponseValidator { org.opensaml.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.FAILURE, "invalidSAMLsecurity"); @@ -136,8 +135,16 @@ public class SAMLSSOResponseValidator { validatorResponse.setResponseId(samlResponse.getID()); validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter); // the assumption for now is that SAMLResponse will contain only a single assertion +<<<<<<< HEAD Element assertionElement = samlResponse.getAssertions().get(0).getDOM(); validatorResponse.setAssertion(DOM2Writer.nodeToString(assertionElement.cloneNode(true))); +======= + Element assertionElement = validAssertion.getDOM(); + Element clonedAssertionElement = (Element)assertionElement.cloneNode(true); + validatorResponse.setAssertionElement(clonedAssertionElement); + validatorResponse.setAssertion(DOM2Writer.nodeToString(clonedAssertionElement)); + +>>>>>>> 1c2a530... Adding SAML SSO tests. return validatorResponse; } http://git-wip-us.apache.org/repos/asf/cxf/blob/f318ee61/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 c09d2e3..90e9f9b 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 @@ -112,6 +112,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/f318ee61/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; + } +}