Repository: cxf Updated Branches: refs/heads/2.7.x-fixes c716a824b -> 6c2ab7d08
Adding more SAML SSO tests Conflicts: rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java Conflicts: rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/CombinedValidatorTest.java Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/ef06a563 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/ef06a563 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/ef06a563 Branch: refs/heads/2.7.x-fixes Commit: ef06a5634c203bd23aa9887d91db3003451c7c4a Parents: c716a82 Author: Colm O hEigeartaigh <cohei...@apache.org> Authored: Fri Jul 31 11:59:53 2015 +0100 Committer: Colm O hEigeartaigh <cohei...@apache.org> Committed: Fri Jul 31 12:06:51 2015 +0100 ---------------------------------------------------------------------- ...AbstractRequestAssertionConsumerHandler.java | 23 ++ .../saml/sso/SAMLSSOResponseValidator.java | 17 ++ .../saml/sso/CombinedValidatorTest.java | 211 ++++++++++++++++++- 3 files changed, 240 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/ef06a563/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java index f622ace..2c37543 100644 --- a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java +++ b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/AbstractRequestAssertionConsumerHandler.java @@ -65,6 +65,11 @@ public class AbstractRequestAssertionConsumerHandler extends AbstractSSOSpHandle private boolean supportBase64Encoding = true; private boolean enforceAssertionsSigned = true; private boolean enforceKnownIssuer = true; +<<<<<<< HEAD +======= + private boolean keyInfoMustBeAvailable = true; + private boolean enforceResponseSigned; +>>>>>>> a61db28... Adding more SAML SSO tests private TokenReplayCache<String> replayCache; private MessageContext messageContext; @@ -318,6 +323,7 @@ public class AbstractRequestAssertionConsumerHandler extends AbstractSSOSpHandle ssoResponseValidator.setRequestId(requestState.getSamlRequestId()); ssoResponseValidator.setSpIdentifier(requestState.getIssuerId()); ssoResponseValidator.setEnforceAssertionsSigned(enforceAssertionsSigned); + ssoResponseValidator.setEnforceResponseSigned(enforceResponseSigned); ssoResponseValidator.setEnforceKnownIssuer(enforceKnownIssuer); ssoResponseValidator.setReplayCache(getReplayCache()); @@ -334,4 +340,21 @@ public class AbstractRequestAssertionConsumerHandler extends AbstractSSOSpHandle LOG.warning(errorMsg.toString()); } +<<<<<<< HEAD +======= + public void setKeyInfoMustBeAvailable(boolean keyInfoMustBeAvailable) { + this.keyInfoMustBeAvailable = keyInfoMustBeAvailable; + } + + public boolean isEnforceResponseSigned() { + return enforceResponseSigned; + } + + /** + * Enforce that a SAML Response must be signed. + */ + public void setEnforceResponseSigned(boolean enforceResponseSigned) { + this.enforceResponseSigned = enforceResponseSigned; + } +>>>>>>> a61db28... Adding more SAML SSO tests } http://git-wip-us.apache.org/repos/asf/cxf/blob/ef06a563/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 2d864a5..30bdcd8 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 @@ -44,6 +44,7 @@ public class SAMLSSOResponseValidator { private String clientAddress; private String requestId; private String spIdentifier; + private boolean enforceResponseSigned; private boolean enforceAssertionsSigned = true; private boolean enforceKnownIssuer = true; private TokenReplayCache<String> replayCache; @@ -91,6 +92,11 @@ public class SAMLSSOResponseValidator { throw new WSSecurityException(WSSecurityException.FAILURE, "invalidSAMLsecurity"); } + if (enforceResponseSigned && !samlResponse.isSigned()) { + LOG.fine("The Response must be signed!"); + throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity"); + } + // Validate Assertions org.opensaml.saml2.core.Assertion validAssertion = null; Date sessionNotOnOrAfter = null; @@ -333,5 +339,16 @@ public class SAMLSSOResponseValidator { 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/cxf/blob/ef06a563/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 index 7b9a9c1..466f97f 100644 --- 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 @@ -22,6 +22,8 @@ package org.apache.cxf.rs.security.saml.sso; import java.io.InputStream; import java.io.StringReader; import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; import java.util.Collections; import javax.xml.parsers.DocumentBuilder; @@ -29,6 +31,7 @@ import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; +<<<<<<< HEAD import org.apache.cxf.staxutils.StaxUtils; import org.apache.ws.security.WSConstants; import org.apache.ws.security.WSSConfig; @@ -42,25 +45,65 @@ import org.apache.ws.security.saml.ext.bean.ConditionsBean; import org.apache.ws.security.saml.ext.bean.SubjectConfirmationDataBean; import org.apache.ws.security.saml.ext.builder.SAML2Constants; import org.apache.ws.security.util.Loader; +======= +import org.apache.wss4j.common.crypto.Crypto; +import org.apache.wss4j.common.crypto.CryptoType; +import org.apache.wss4j.common.crypto.Merlin; +import org.apache.wss4j.common.ext.WSSecurityException; +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; +>>>>>>> a61db28... Adding more SAML SSO tests import org.joda.time.DateTime; +<<<<<<< HEAD import org.opensaml.common.xml.SAMLConstants; import org.opensaml.saml2.core.Response; import org.opensaml.saml2.core.Status; +======= +import org.opensaml.saml.common.SignableSAMLObject; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.Status; +import org.opensaml.security.x509.BasicX509Credential; +import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory; +import org.opensaml.xmlsec.signature.KeyInfo; +import org.opensaml.xmlsec.signature.Signature; +import org.opensaml.xmlsec.signature.support.SignatureConstants; +>>>>>>> 3228637... Adding more SAML SSO tests /** * Some unit tests for the SAMLProtocolResponseValidator and the SAMLSSOResponseValidator */ public class CombinedValidatorTest extends org.junit.Assert { + private static final DocumentBuilderFactory DOC_BUILDER_FACTORY = DocumentBuilderFactory.newInstance(); + static { WSSConfig.init(); OpenSAMLUtil.initSamlEngine(); + DOC_BUILDER_FACTORY.setNamespaceAware(true); } @org.junit.Test public void testSuccessfulValidation() throws Exception { - Element responseElement = createResponse(); + DocumentBuilder docBuilder = DOC_BUILDER_FACTORY.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Response response = createResponse(doc); + + Element responseElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(responseElement); + assertNotNull(responseElement); + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); Crypto issuerCrypto = new Merlin(); @@ -96,7 +139,14 @@ public class CombinedValidatorTest extends org.junit.Assert { @org.junit.Test public void testWrappingAttack3() throws Exception { - Element responseElement = createResponse(); + DocumentBuilder docBuilder = DOC_BUILDER_FACTORY.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Response response = createResponse(doc); + + Element responseElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(responseElement); + assertNotNull(responseElement); // Get Assertion Element Element assertionElement = @@ -158,12 +208,98 @@ public class CombinedValidatorTest extends org.junit.Assert { assertEquals("alice", parsedAssertion.getSaml2().getSubject().getNameID().getValue()); } - private Element createResponse() throws Exception { - DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); - docBuilderFactory.setNamespaceAware(true); - DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + @org.junit.Test + public void testSuccessfulSignedValidation() throws Exception { + + DocumentBuilder docBuilder = DOC_BUILDER_FACTORY.newDocumentBuilder(); Document doc = docBuilder.newDocument(); + Response response = createResponse(doc); + + 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); + + signResponse(response, "alice", "password", issuerCrypto, true); + + Element responseElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(responseElement); + assertNotNull(responseElement); + + Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(responseElement); + + // 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 testEnforceResponseSigned() throws Exception { + + DocumentBuilder docBuilder = DOC_BUILDER_FACTORY.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Response response = createResponse(doc); + + Element responseElement = OpenSAMLUtil.toDom(response, doc); + doc.appendChild(responseElement); + assertNotNull(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"); + ssoValidator.setEnforceResponseSigned(true); + + // Parse the response + try { + ssoValidator.validateSamlResponse(marshalledResponse, false); + fail("Failure expected on an unsigned Response"); + } catch (WSSecurityException ex) { + // expected + } + } + + private Response createResponse(Document doc) throws Exception { Status status = SAML2PResponseComponentBuilder.createStatus( SAMLProtocolResponseValidator.SAML2_STATUSCODE_SUCCESS, null @@ -172,6 +308,7 @@ public class CombinedValidatorTest extends org.junit.Assert { SAML2PResponseComponentBuilder.createSAMLResponse( "http://cxf.apache.org/saml", "http://cxf.apache.org/issuer", status ); + response.setDestination("http://recipient.apache.org"); // Create an AuthenticationAssertion SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler(); @@ -211,10 +348,62 @@ public class CombinedValidatorTest extends org.junit.Assert { response.getAssertions().add(assertion.getSaml2()); - Element policyElement = OpenSAMLUtil.toDom(response, doc); - doc.appendChild(policyElement); - assertNotNull(policyElement); - - return policyElement; + return response; + } + + private void signResponse( + Response response, + String issuerKeyName, + String issuerKeyPassword, + Crypto issuerCrypto, + boolean useKeyInfo + ) throws Exception { + // + // Create the signature + // + Signature signature = OpenSAMLUtil.buildSignature(); + signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); + + // prepare to sign the SAML token + CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS); + cryptoType.setAlias(issuerKeyName); + X509Certificate[] issuerCerts = issuerCrypto.getX509Certificates(cryptoType); + if (issuerCerts == null) { + throw new Exception( + "No issuer certs were found to sign the SAML Assertion using issuer name: " + issuerKeyName); + } + + String sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1; + String pubKeyAlgo = issuerCerts[0].getPublicKey().getAlgorithm(); + + if (pubKeyAlgo.equalsIgnoreCase("DSA")) { + sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_DSA; + } + + PrivateKey privateKey = issuerCrypto.getPrivateKey(issuerKeyName, issuerKeyPassword); + + signature.setSignatureAlgorithm(sigAlgo); + + BasicX509Credential signingCredential = + new BasicX509Credential(issuerCerts[0], privateKey); + signature.setSigningCredential(signingCredential); + + if (useKeyInfo) { + X509KeyInfoGeneratorFactory kiFactory = new X509KeyInfoGeneratorFactory(); + kiFactory.setEmitEntityCertificate(true); + + try { + KeyInfo keyInfo = kiFactory.newInstance().generate(signingCredential); + signature.setKeyInfo(keyInfo); + } catch (org.opensaml.security.SecurityException ex) { + throw new Exception("Error generating KeyInfo from signing credential", ex); + } + } + + // add the signature to the assertion + SignableSAMLObject signableObject = (SignableSAMLObject) response; + signableObject.setSignature(signature); + signableObject.releaseDOM(); + signableObject.releaseChildrenDOM(true); } }