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/01/13 13:07:22 UTC
cxf git commit: Adding lots of SAML SSO Negative tests
Repository: cxf
Updated Branches:
refs/heads/master 3c3eaf41e -> d970de924
Adding lots of SAML SSO Negative tests
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/d970de92
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/d970de92
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/d970de92
Branch: refs/heads/master
Commit: d970de924bf5be80e39092593d79c9318003b748
Parents: 3c3eaf4
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Tue Jan 13 12:01:58 2015 +0000
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Tue Jan 13 12:07:17 2015 +0000
----------------------------------------------------------------------
.../saml/sso/AbstractSAMLCallbackHandler.java | 22 +-
.../sso/SAML2PResponseComponentBuilder.java | 15 +
.../saml/sso/SAMLResponseValidatorTest.java | 282 +++++++++++-
.../saml/sso/SAMLSSOResponseValidatorTest.java | 450 ++++++++++++++++++-
4 files changed, 764 insertions(+), 5 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cxf/blob/d970de92/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 721c42a..61653a1 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
@@ -29,7 +29,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-
import org.apache.wss4j.common.saml.SAMLCallback;
import org.apache.wss4j.common.saml.bean.ActionBean;
import org.apache.wss4j.common.saml.bean.AttributeBean;
@@ -44,6 +43,7 @@ import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean;
import org.apache.wss4j.common.saml.bean.SubjectLocalityBean;
import org.apache.wss4j.dom.WSConstants;
import org.apache.wss4j.dom.message.WSSecEncryptedKey;
+import org.joda.time.DateTime;
/**
* A base implementation of a Callback Handler for a SAML assertion. By default it creates an
@@ -70,7 +70,25 @@ public abstract class AbstractSAMLCallbackHandler implements CallbackHandler {
protected List<Object> customAttributeValues;
protected ConditionsBean conditions;
protected SubjectConfirmationDataBean subjectConfirmationData;
+ protected DateTime authnInstant;
+ protected DateTime sessionNotOnOrAfter;
+ public DateTime getSessionNotOnOrAfter() {
+ return sessionNotOnOrAfter;
+ }
+
+ public void setSessionNotOnOrAfter(DateTime sessionNotOnOrAfter) {
+ this.sessionNotOnOrAfter = sessionNotOnOrAfter;
+ }
+
+ public DateTime getAuthnInstant() {
+ return authnInstant;
+ }
+
+ public void setAuthnInstant(DateTime authnInstant) {
+ this.authnInstant = authnInstant;
+ }
+
public void setSubjectConfirmationData(SubjectConfirmationDataBean subjectConfirmationData) {
this.subjectConfirmationData = subjectConfirmationData;
}
@@ -135,6 +153,8 @@ public abstract class AbstractSAMLCallbackHandler implements CallbackHandler {
subjectLocality.setDnsAddress(subjectLocalityDnsAddress);
authBean.setSubjectLocality(subjectLocality);
}
+ authBean.setAuthenticationInstant(authnInstant);
+ authBean.setSessionNotOnOrAfter(sessionNotOnOrAfter);
authBean.setAuthenticationMethod("Password");
callback.setAuthenticationStatementData(Collections.singletonList(authBean));
} else if (statement == Statement.ATTR) {
http://git-wip-us.apache.org/repos/asf/cxf/blob/d970de92/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAML2PResponseComponentBuilder.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAML2PResponseComponentBuilder.java b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAML2PResponseComponentBuilder.java
index ddadecf..a902cbc 100644
--- a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAML2PResponseComponentBuilder.java
+++ b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAML2PResponseComponentBuilder.java
@@ -25,6 +25,7 @@ import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.common.SAMLVersion;
+import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
@@ -47,6 +48,8 @@ public final class SAML2PResponseComponentBuilder {
private static SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
+ private static SAMLObjectBuilder<AuthnContextClassRef> authnContextClassRefBuilder;
+
private static XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
private SAML2PResponseComponentBuilder() {
@@ -122,5 +125,17 @@ public final class SAML2PResponseComponentBuilder {
return status;
}
+ @SuppressWarnings("unchecked")
+ public static AuthnContextClassRef createAuthnContextClassRef(String newAuthnContextClassRef) {
+ if (authnContextClassRefBuilder == null) {
+ authnContextClassRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>)
+ builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
+ }
+
+ AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder.buildObject();
+ authnContextClassRef.setAuthnContextClassRef(newAuthnContextClassRef);
+
+ return authnContextClassRef;
+ }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/cxf/blob/d970de92/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLResponseValidatorTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLResponseValidatorTest.java b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLResponseValidatorTest.java
index be53b9c..3e20b96 100644
--- a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLResponseValidatorTest.java
+++ b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLResponseValidatorTest.java
@@ -23,13 +23,15 @@ import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
-
+import org.apache.cxf.helpers.DOMUtils;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.CryptoType;
import org.apache.wss4j.common.crypto.Merlin;
@@ -38,10 +40,16 @@ 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.WSSConfig;
+import org.joda.time.DateTime;
+import org.opensaml.common.SAMLVersion;
import org.opensaml.common.SignableSAMLObject;
+import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
import org.opensaml.xml.security.x509.BasicX509Credential;
@@ -144,6 +152,50 @@ public class SAMLResponseValidatorTest extends org.junit.Assert {
}
@org.junit.Test
+ public void testRequestDeniedStatusCode() throws Exception {
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ docBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ Document doc = docBuilder.newDocument();
+
+ Status status =
+ SAML2PResponseComponentBuilder.createStatus(
+ "urn:oasis:names:tc:SAML:2.0:status:RequestDenied", 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_SENDER_VOUCHES);
+
+ SAMLCallback samlCallback = new SAMLCallback();
+ SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+ SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback);
+
+ response.getAssertions().add(assertion.getSaml2());
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+ assertNotNull(policyElement);
+
+ Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(policyElement);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator();
+ try {
+ validator.validateSamlResponse(marshalledResponse, null, null);
+ fail("Expected failure on an invalid SAML code");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
public void testResponseSignedAssertion() throws Exception {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
@@ -202,6 +254,69 @@ public class SAMLResponseValidatorTest extends org.junit.Assert {
}
@org.junit.Test
+ public void testResponseModifiedSignedAssertion() 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_SENDER_VOUCHES);
+
+ 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(SAMLResponseValidatorTest.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);
+
+ List<Element> assertions =
+ DOMUtils.findAllElementsByTagNameNS(policyElement, SAMLConstants.SAML20_NS, "Assertion");
+ assertNotNull(assertions);
+ assertTrue(assertions.size() == 1);
+ assertions.get(0).setAttributeNS(null, "newattr", "http://apache.org");
+
+ Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(policyElement);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator();
+ try {
+ // Validate the Response
+ validator.validateSamlResponse(
+ marshalledResponse, issuerCrypto, new KeystorePasswordCallback()
+ );
+ fail("Expected failure on a bad signature");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+
+ }
+
+ @org.junit.Test
public void testSignedResponse() throws Exception {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
@@ -259,6 +374,63 @@ public class SAMLResponseValidatorTest extends org.junit.Assert {
}
@org.junit.Test
+ public void testModifiedSignedResponse() 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_SENDER_VOUCHES);
+
+ 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(SAMLResponseValidatorTest.class);
+ InputStream input = Merlin.loadInputStream(loader, "alice.jks");
+ keyStore.load(input, "password".toCharArray());
+ ((Merlin)issuerCrypto).setKeyStore(keyStore);
+
+ response.getAssertions().add(assertion.getSaml2());
+ signResponse(response, "alice", "password", issuerCrypto, true);
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+ assertNotNull(policyElement);
+
+ policyElement.setAttributeNS(null, "newattr", "http://apache.org");
+
+ Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(policyElement);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator validator = new SAMLProtocolResponseValidator();
+ try {
+ // Validate the Response
+ validator.validateSamlResponse(
+ marshalledResponse, issuerCrypto, new KeystorePasswordCallback()
+ );
+ fail("Expected failure on a bad signature");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
public void testSignedResponseNoKeyInfo() throws Exception {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
@@ -316,6 +488,83 @@ public class SAMLResponseValidatorTest extends org.junit.Assert {
);
}
+ @org.junit.Test
+ public void testResponseInvalidVersion() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ // Create a AuthenticationAssertion
+ SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+ callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+ callbackHandler.setIssuer("http://cxf.apache.org/issuer");
+ callbackHandler.setConfirmationMethod(SAML2Constants.CONF_SENDER_VOUCHES);
+
+ 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);
+
+ Response response = createResponse(subjectConfirmationData, callbackHandler);
+ response.setVersion(SAMLVersion.VERSION_10);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator protocolValidator = new SAMLProtocolResponseValidator();
+
+ try {
+ protocolValidator.validateSamlResponse(response, null, null);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testAssertionBadSubjectConfirmationMethod() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ // Create a AuthenticationAssertion
+ SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+ callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+ callbackHandler.setIssuer("http://cxf.apache.org/issuer");
+ callbackHandler.setConfirmationMethod("xyz");
+
+ 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);
+
+ Response response = createResponse(subjectConfirmationData, callbackHandler);
+
+ // Validate the Response
+ SAMLProtocolResponseValidator protocolValidator = new SAMLProtocolResponseValidator();
+
+ try {
+ protocolValidator.validateSamlResponse(response, null, null);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
/**
* Sign a SAML Response
@@ -381,4 +630,35 @@ public class SAMLResponseValidatorTest extends org.junit.Assert {
signableObject.releaseChildrenDOM(true);
}
+ private Response createResponse(
+ SubjectConfirmationDataBean subjectConfirmationData,
+ SAML2CallbackHandler callbackHandler
+ ) 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
+ SAMLCallback samlCallback = new SAMLCallback();
+ SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+ SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback);
+
+ response.getAssertions().add(assertion.getSaml2());
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+ assertNotNull(policyElement);
+
+ return (Response)OpenSAMLUtil.fromDom(policyElement);
+ }
}
http://git-wip-us.apache.org/repos/asf/cxf/blob/d970de92/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidatorTest.java
----------------------------------------------------------------------
diff --git a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidatorTest.java b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidatorTest.java
index ebe08d5..6235532 100644
--- a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidatorTest.java
+++ b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SAMLSSOResponseValidatorTest.java
@@ -19,13 +19,22 @@
package org.apache.cxf.rs.security.saml.sso;
+import java.io.InputStream;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
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.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;
@@ -35,9 +44,17 @@ 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.joda.time.DateTime;
+import org.opensaml.common.SignableSAMLObject;
+import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
+import org.opensaml.xml.security.x509.BasicX509Credential;
+import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory;
+import org.opensaml.xml.signature.KeyInfo;
+import org.opensaml.xml.signature.Signature;
+import org.opensaml.xml.signature.SignatureConstants;
/**
* Some unit tests for the SAMLSSOResponseValidator.
@@ -194,9 +211,8 @@ public class SAMLSSOResponseValidatorTest extends org.junit.Assert {
}
}
- private Response createResponse(
- SubjectConfirmationDataBean subjectConfirmationData
- ) throws Exception {
+ @org.junit.Test
+ public void testSignedResponseInvalidDestination() throws Exception {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
@@ -217,6 +233,268 @@ public class SAMLSSOResponseValidatorTest extends org.junit.Assert {
callbackHandler.setIssuer("http://cxf.apache.org/issuer");
callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+ 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);
+
+ 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);
+
+ SAMLCallback samlCallback = new SAMLCallback();
+ SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+ SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback);
+
+ response.getAssertions().add(assertion.getSaml2());
+ response.setDestination("xyz");
+
+ Crypto issuerCrypto = new Merlin();
+ KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ ClassLoader loader = Loader.getClassLoader(SAMLResponseValidatorTest.class);
+ InputStream input = Merlin.loadInputStream(loader, "alice.jks");
+ keyStore.load(input, "password".toCharArray());
+ ((Merlin)issuerCrypto).setKeyStore(keyStore);
+
+ signResponse(response, "alice", "password", issuerCrypto, true);
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+ assertNotNull(policyElement);
+
+ Response marshalledResponse = (Response)OpenSAMLUtil.fromDom(policyElement);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+ try {
+ validator.validateSamlResponse(marshalledResponse, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testResponseInvalidIssuer() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ Response response = createResponse(subjectConfirmationData);
+ response.setIssuer(SAML2PResponseComponentBuilder.createIssuer("xyz"));
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+ try {
+ validator.validateSamlResponse(response, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testMissingAuthnStatement() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ Response response = createResponse(subjectConfirmationData);
+ response.getAssertions().get(0).getAuthnStatements().clear();
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+ try {
+ validator.validateSamlResponse(response, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testNoSubjectConfirmationData() throws Exception {
+ Response response = createResponse(null);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+ try {
+ validator.validateSamlResponse(response, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testEmptyAudienceRestriction() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean();
+ Response response =
+ createResponse(subjectConfirmationData,
+ Collections.singletonList(audienceRestriction),
+ null);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+ try {
+ validator.validateSamlResponse(response, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testBadAudienceRestriction() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean();
+ audienceRestriction.setAudienceURIs(
+ Collections.singletonList("http://unknown-service.apache.org"));
+ Response response =
+ createResponse(subjectConfirmationData,
+ Collections.singletonList(audienceRestriction),
+ null);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+ try {
+ validator.validateSamlResponse(response, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ @org.junit.Test
+ public void testAudienceRestrictionMultipleValues() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ List<String> values = new ArrayList<String>();
+ values.add("http://unknown-service.apache.org");
+ values.add("http://service.apache.org");
+
+ AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean();
+ audienceRestriction.setAudienceURIs(values);
+ Response response =
+ createResponse(subjectConfirmationData,
+ Collections.singletonList(audienceRestriction),
+ null);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+
+ validator.validateSamlResponse(response, false);
+ }
+
+ @org.junit.Test
+ public void testMultipleAudienceRestrictions() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ List<AudienceRestrictionBean> audienceRestrictions =
+ new ArrayList<AudienceRestrictionBean>();
+
+ AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean();
+ audienceRestriction.setAudienceURIs(
+ Collections.singletonList("http://unknown-service.apache.org"));
+ audienceRestrictions.add(audienceRestriction);
+
+ audienceRestriction = new AudienceRestrictionBean();
+ audienceRestriction.setAudienceURIs(
+ Collections.singletonList("http://service.apache.org"));
+ audienceRestrictions.add(audienceRestriction);
+
+ Response response =
+ createResponse(subjectConfirmationData, audienceRestrictions, null);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+
+ validator.validateSamlResponse(response, false);
+ }
+
+ @org.junit.Test
+ public void testAssertionBadIssuer() throws Exception {
+ SubjectConfirmationDataBean subjectConfirmationData = new SubjectConfirmationDataBean();
+ subjectConfirmationData.setAddress("http://apache.org");
+ subjectConfirmationData.setInResponseTo("12345");
+ subjectConfirmationData.setNotAfter(new DateTime().plusMinutes(5));
+ subjectConfirmationData.setRecipient("http://recipient.apache.org");
+
+ // Create a AuthenticationAssertion
+ SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
+ callbackHandler.setStatement(SAML2CallbackHandler.Statement.AUTHN);
+ callbackHandler.setIssuer("http://cxf.apache.org/bad-issuer");
+ callbackHandler.setConfirmationMethod(SAML2Constants.CONF_BEARER);
+
callbackHandler.setSubjectConfirmationData(subjectConfirmationData);
ConditionsBean conditions = new ConditionsBean();
@@ -228,17 +506,183 @@ public class SAMLSSOResponseValidatorTest extends org.junit.Assert {
conditions.setAudienceRestrictions(Collections.singletonList(audienceRestriction));
callbackHandler.setConditions(conditions);
+ Response response = createResponse(subjectConfirmationData, callbackHandler);
+
+ // Validate the Response
+ SAMLSSOResponseValidator validator = new SAMLSSOResponseValidator();
+ validator.setIssuerIDP("http://cxf.apache.org/issuer");
+ validator.setAssertionConsumerURL("http://recipient.apache.org");
+ validator.setClientAddress("http://apache.org");
+ validator.setRequestId("12345");
+ validator.setSpIdentifier("http://service.apache.org");
+
+ try {
+ validator.validateSamlResponse(response, false);
+ fail("Expected failure on bad response");
+ } catch (WSSecurityException ex) {
+ // expected
+ }
+ }
+
+ private Response createResponse(
+ SubjectConfirmationDataBean subjectConfirmationData
+ ) throws Exception {
+ return createResponse(subjectConfirmationData, null, null);
+ }
+
+ private Response createResponse(
+ SubjectConfirmationDataBean subjectConfirmationData,
+ List<AudienceRestrictionBean> audienceRestrictions,
+ String authnClassRef
+ ) 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.setSubjectConfirmationData(subjectConfirmationData);
+
+ ConditionsBean conditions = new ConditionsBean();
+ conditions.setNotBefore(new DateTime());
+ conditions.setNotAfter(new DateTime().plusMinutes(5));
+
+ if (audienceRestrictions == null) {
+ AudienceRestrictionBean audienceRestriction = new AudienceRestrictionBean();
+ audienceRestriction.setAudienceURIs(Collections.singletonList("http://service.apache.org"));
+ conditions.setAudienceRestrictions(Collections.singletonList(audienceRestriction));
+ } else {
+ conditions.setAudienceRestrictions(audienceRestrictions);
+ }
+ callbackHandler.setConditions(conditions);
+
SAMLCallback samlCallback = new SAMLCallback();
SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback);
response.getAssertions().add(assertion.getSaml2());
+ if (authnClassRef != null) {
+ AuthnStatement authnStatement =
+ response.getAssertions().get(0).getAuthnStatements().get(0);
+ authnStatement.getAuthnContext().setAuthnContextClassRef(
+ SAML2PResponseComponentBuilder.createAuthnContextClassRef(authnClassRef));
+ }
+
Element policyElement = OpenSAMLUtil.toDom(response, doc);
doc.appendChild(policyElement);
assertNotNull(policyElement);
return (Response)OpenSAMLUtil.fromDom(policyElement);
}
+
+ private Response createResponse(
+ SubjectConfirmationDataBean subjectConfirmationData,
+ SAML2CallbackHandler callbackHandler
+ ) 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
+ SAMLCallback samlCallback = new SAMLCallback();
+ SAMLUtil.doSAMLCallback(callbackHandler, samlCallback);
+ SamlAssertionWrapper assertion = new SamlAssertionWrapper(samlCallback);
+
+ response.getAssertions().add(assertion.getSaml2());
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+ assertNotNull(policyElement);
+
+ return (Response)OpenSAMLUtil.fromDom(policyElement);
+ }
+ /**
+ * Sign a SAML Response
+ * @throws Exception
+ */
+ 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();
+ signingCredential.setEntityCertificate(issuerCerts[0]);
+ signingCredential.setPrivateKey(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.xml.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);
+ }
}