/*
* Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
*
* Licensed 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.opensaml.saml2.binding.security;
import java.security.KeyException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.common.binding.BasicSAMLMessageContext;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.binding.security.BaseSAMLSecurityPolicyRuleTestCase;
import org.opensaml.saml2.binding.encoding.HTTPRedirectDeflateEncoder;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.util.URLBuilder;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.ws.transport.InTransport;
import org.opensaml.ws.transport.http.HTTPInTransport;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
import org.opensaml.xml.security.SecurityTestHelper;
import org.opensaml.xml.security.credential.CollectionCredentialResolver;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xml.security.x509.BasicX509Credential;
import org.opensaml.xml.signature.SignatureTrustEngine;
import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine;
import org.opensaml.xml.util.Pair;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
/**
* Test SAML simple signature for HTTP Redirect DEFLATE binding.
*/
public class SAML2HTTPRedirectDeflateSignatureSecurityPolicyRuleTest
extends BaseSAMLSecurityPolicyRuleTestCase<AuthnRequest, Response, NameID> {
private X509Certificate signingCert;
private String signingCertBase64 =
"MIIDzjCCAragAwIBAgIBMTANBgkqhkiG9w0BAQUFADAtMRIwEAYDVQQKEwlJbnRl" +
"cm5ldDIxFzAVBgNVBAMTDmNhLmV4YW1wbGUub3JnMB4XDTA3MDUyMTE4MjM0MFoX" +
"DTE3MDUxODE4MjM0MFowMTESMBAGA1UEChMJSW50ZXJuZXQyMRswGQYDVQQDExJm" +
"b29iYXIuZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB" +
"AQDNWnkFmhy1vYa6gN/xBRKkZxFy3sUq2V0LsYb6Q3pe9Qlb6+BzaM5DrN8uIqqr" +
"oBE3Wp0LtrgKuQTpDpNFBdS2p5afiUtOYLWBDtizTOzs3Z36MGMjIPUYQ4s03IP3" +
"yPh2ud6EKpDPiYqzNbkRaiIwmYSit5r+RMYvd6fuKvTOn6h7PZI5AD7Rda7VWh5O" +
"VSoZXlRx3qxFho+mZhW0q4fUfTi5lWwf4EhkfBlzgw/k5gf4cOi6rrGpRS1zxmbt" +
"X1RAg+I20z6d04g0N2WsK5stszgYKoIROJCiXwjraa8/SoFcILolWQpttVHBIUYl" +
"yDlm8mIFleZf4ReFpfm+nUYxAgMBAAGjgfQwgfEwCQYDVR0TBAIwADAsBglghkgB" +
"hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE" +
"FDgRgTkjaKoK6DoZfUZ4g9LDJUWuMFUGA1UdIwROMEyAFNXuZVPeUdqHrULqQW7y" +
"r9buRpQLoTGkLzAtMRIwEAYDVQQKEwlJbnRlcm5ldDIxFzAVBgNVBAMTDmNhLmV4" +
"YW1wbGUub3JnggEBMEAGA1UdEQQ5MDeCEmFzaW1vdi5leGFtcGxlLm9yZ4YbaHR0" +
"cDovL2hlaW5sZWluLmV4YW1wbGUub3JnhwQKAQIDMA0GCSqGSIb3DQEBBQUAA4IB" +
"AQBLiDMyQ60ldIytVO1GCpp1S1sKJyTF56GVxHh/82hiRFbyPu+2eSl7UcJfH4ZN" +
"bAfHL1vDKTRJ9zoD8WRzpOCUtT0IPIA/Ex+8lFzZmujO10j3TMpp8Ii6+auYwi/T" +
"osrfw1YCxF+GI5KO49CfDRr6yxUbMhbTN+ssK4UzFf36UbkeJ3EfDwB0WU70jnlk" +
"yO8f97X6mLd5QvRcwlkDMftP4+MB+inTlxDZ/w8NLXQoDW6p/8r91bupXe0xwuyE" +
"vow2xjxlzVcux2BZsUZYjBa07ZmNNBtF7WaQqH7l2OBCAdnBhvme5i/e0LK3Ivys" +
"+hcVyvCXs5XtFTFWDAVYvzQ6";
private PrivateKey signingPrivateKey;
private String signingPrivateKeyBase64 =
"MIIEogIBAAKCAQEAzVp5BZoctb2GuoDf8QUSpGcRct7FKtldC7GG+kN6XvUJW+vg" +
"c2jOQ6zfLiKqq6ARN1qdC7a4CrkE6Q6TRQXUtqeWn4lLTmC1gQ7Ys0zs7N2d+jBj" +
"IyD1GEOLNNyD98j4drnehCqQz4mKszW5EWoiMJmEorea/kTGL3en7ir0zp+oez2S" +
"OQA+0XWu1VoeTlUqGV5Ucd6sRYaPpmYVtKuH1H04uZVsH+BIZHwZc4MP5OYH+HDo" +
"uq6xqUUtc8Zm7V9UQIPiNtM+ndOINDdlrCubLbM4GCqCETiQol8I62mvP0qBXCC6" +
"JVkKbbVRwSFGJcg5ZvJiBZXmX+EXhaX5vp1GMQIDAQABAoIBAC1P4lZvHBiqGll6" +
"6G8pXGS0bXA4Ya9DyTk0UgFU9GKRlSAYWy18Gc9rDNAETD6Uklfxgae9CL0s+D1o" +
"vuxDDh3DuwO26sv/oO06Vmyx87GMcThshuOQeSSCeuwOIHyDdvfTqZrmPY/d3KIQ" +
"n6aNEcBBj7fL5cJncIe20nJGPkB9KuTAaGVnaKoOesxgWBr7SvjGq/SB7bRE1B3c" +
"QxwUDWHkF0LljSIkXaV9ehKJcgBY2fV0rc8pI53WsUXEXk5HoqYZnQ5QjAZ4Hf2s" +
"bRKevq+D2ENK+OuKNuCAS/oJbGSdS7q0/6jgHZ6cUGXi1r2qEEG7PIorCoSMkWQS" +
"M1wMX0ECgYEA9c6/s9lKDrjzyjO9rlxzufGVRDjffiUZ1o8F3RD3JltdPLVcd429" +
"CvGSNV730Yr/wSyRAum4vkGnmOR9tuQdi3PJHt3xGRsymTT5ym/5fnC4SvXVSR6v" +
"LFPUY80yj+D6/0lwIaGE7x4JOclMXnHjqcpRl14onOjY844WORhxgjkCgYEA1d5N" +
"Tqp938UbZYKX4Q9UvLf/pVR9xOFOCYnMywAFk0WnkUBPHmPoJuFgeNGeQ7gCmHi7" +
"JFzwBjkj6DcGMdbXKWiUij1BoRxf9Mof+fZBWVSKw+/yVLbJkyK951+nywyiq3HC" +
"NBti1eK/h/hXQd8t+dCBmDGj1ba1C2/3JZqLg7kCgYArxD1D85uJFYtq5F2Qryt3" +
"3zj5pbq9hjOcjWi43O10qe3nAk/NhbI0QaEL2bX8XGh/Z8UGJMFdNul1grGTn/hW" +
"vS4BTflAxCP1PYaAcgGVbtKRnkX0t/7uwJpfjsjC74chb10Ez/KQdOOlo17yrgqg" +
"T8LJVd2bWqZOb20ri1uimQKBgFfJYSg6OWLh0IYRXfBmz5yLVmdx0BJBfTvTEXn+" +
"L0utWsP3hsJttfxHpMbTHEilvoMBg6fAclHLoJ6P/33ztuvrXpWD4W2VbRnY4dlD" +
"qL1XQ4J7+pelVAaOSy8vB3wEWr1O+61R1HcBFSdl28NRLdkOKjPjpGF0Fsp0Ehmg" +
"X0YZAoGAXrM4+BUvcx2PLaeneTJoRdOi3GQbdAte03maDU6C474IdgR8IUygfspv" +
"3fiGue9Wmk5ybUBlv/D6sIWVhnnedWsg2zAgZPfZ78HLLNhWeEx33wPFiK0wV5MJ" +
"XQ224gQ5t9D3WXdZtmAxXIFoopj4zToCMBjXyep0u7zl3s7s00U=";
private X509Certificate otherCert1;
private String otherCert1Base64 =
"MIIECTCCAvGgAwIBAgIBMzANBgkqhkiG9w0BAQUFADAtMRIwEAYDVQQKEwlJbnRl" +
"cm5ldDIxFzAVBgNVBAMTDmNhLmV4YW1wbGUub3JnMB4XDTA3MDUyNTIwMTYxMVoX" +
"DTE3MDUyMjIwMTYxMVowGjEYMBYGA1UEAxMPaWRwLmV4YW1wbGUub3JnMIIBtjCC" +
"ASsGByqGSM44BAEwggEeAoGBAI+ktw7R9m7TxjaCrT2MHwWNQUAyXPrqbFCcu+DC" +
"irr861U6R6W/GyqWdcy8/D1Hh/I1U94POQn5yfqVPpVH2ZRS4OMFndHWaoo9V5LJ" +
"oXTXHiDYB3W4t9tn0fm7It0n7VoUI5C4y9LG32Hq+UIGF/ktNTmo//mEqLS6aJNd" +
"bMFpAhUArmKGh0hcpmjukYArWcMRvipB4CMCgYBuCiCrUaHBRRtqrk0P/Luq0l2M" +
"2718GwSGeLPZip06gACDG7IctMrgH1J+ZIjsx6vffi977wnMDiktqacmaobV+SCR" +
"W9ijJRdkYpUHmlLvuJGnDPjkvewpbGWJsCabpWEvWdYw3ma8RuHOPj4Jkrdd4VcR" +
"aFwox/fPJ7cG6kBydgOBhAACgYBxQIPv9DCsmiMHG1FAxSARX0GcRiELJPJ+MtaS" +
"tdTrVobNa2jebwc3npLiTvUR4U/CDo1mSZb+Sp/wian8kNZHmGcR6KbtJs9UDsa3" +
"V0pbbgpUar4HcxV+NQJBbhn9RGu85g3PDILUrINiUAf26mhPN5Y0paM+HbM68nUf" +
"1OLv16OBsjCBrzAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdl" +
"bmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUIHFAEB/3jIIZzJEJ/qdsuI8v" +
"N3kwVQYDVR0jBE4wTIAU1e5lU95R2oetQupBbvKv1u5GlAuhMaQvMC0xEjAQBgNV" +
"BAoTCUludGVybmV0MjEXMBUGA1UEAxMOY2EuZXhhbXBsZS5vcmeCAQEwDQYJKoZI" +
"hvcNAQEFBQADggEBAJt4Q34+pqjW5tHHhkdzTITSBjOOf8EvYMgxTMRzhagLSHTt" +
"9RgO5i/G7ELvnwe1j6187m1XD9iEAWKeKbB//ljeOpgnwzkLR9Er5tr1RI3cbil0" +
"AX+oX0c1jfRaQnR50Rfb5YoNX6G963iphlxp9C8VLB6eOk/S270XoWoQIkO1ioQ8" +
"JY4HE6AyDsOpJaOmHpBaxjgsiko52ZWZeZyaCyL98BXwVxeml7pYnHlXWWidB0N/" +
"Zy+LbvWg3urUkiDjMcB6nGImmEfDSxRdybitcMwbwL26z2WOpwL3llm3mcCydKXg" +
"Xt8IQhfDhOZOHWckeD2tStnJRP/cqBgO62/qirw=";
private CollectionCredentialResolver credResolver;
private List<Credential> trustedCredentials;
private BasicX509Credential signingX509Cred;
private BasicX509Credential otherCred1;
private String issuer;
private String expectedRelayValue = "cookieMonster";
/** Constructor.
* @throws CertificateException
* @throws KeyException */
public SAML2HTTPRedirectDeflateSignatureSecurityPolicyRuleTest() throws CertificateException, KeyException {
signingCert = SecurityTestHelper.buildJavaX509Cert(signingCertBase64);
signingPrivateKey = SecurityTestHelper.buildJavaRSAPrivateKey(signingPrivateKeyBase64);
signingX509Cred = new BasicX509Credential();
signingX509Cred.setEntityCertificate(signingCert);
signingX509Cred.setPrivateKey(signingPrivateKey);
signingX509Cred.setEntityId(issuer);
otherCert1 = SecurityTestHelper.buildJavaX509Cert(otherCert1Base64);
otherCred1 = new BasicX509Credential();
otherCred1.setEntityCertificate(otherCert1);
otherCred1.setEntityId("other-1");
}
/** {@inheritDoc} */
protected void setUp() throws Exception {
super.setUp();
// Trust engine setup
issuer = "SomeCoolIssuer";
trustedCredentials = new ArrayList<Credential>();
trustedCredentials.add(otherCred1);
credResolver = new CollectionCredentialResolver(trustedCredentials);
KeyInfoCredentialResolver kiResolver = SecurityTestHelper.buildBasicInlineKeyInfoResolver();
SignatureTrustEngine engine = new ExplicitKeySignatureTrustEngine(credResolver, kiResolver);
rule = new SAML2HTTPRedirectDeflateSignatureRule(engine);
messageContext.setInboundMessageIssuer(issuer);
((SAMLMessageContext) messageContext).setInboundSAMLMessageAuthenticated(false);
messageContext.setPeerEntityRole(SPSSODescriptor.DEFAULT_ELEMENT_NAME);
}
/**
* Test context issuer set, valid signature with trusted credential.
*/
public void testSuccess() {
trustedCredentials.add(signingX509Cred);
assertRuleSuccess("Protocol message was signed with trusted credential known to trust engine resolver");
SAMLMessageContext samlContext = messageContext;
assertEquals("Unexpected value for Issuer found", issuer, samlContext.getInboundMessageIssuer());
assertTrue("Unexpected value for context authentication state",
samlContext.isInboundSAMLMessageAuthenticated());
}
/**
* Test context issuer set, valid signature with untrusted credential.
*/
public void testUntrustedCredential() {
assertRuleFailure("Protocol message was signed with credential unknown to trust engine resolver");
}
/**
* Test context issuer set, invalid signature with trusted credential.
*/
public void testInvalidSignature() {
trustedCredentials.add(signingX509Cred);
HttpServletRequestAdapter inTransport = (HttpServletRequestAdapter) messageContext.getInboundMessageTransport();
MockHttpServletRequest request = (MockHttpServletRequest) inTransport.getWrappedRequest();
String queryString = request.getQueryString();
request.setQueryString( queryString.replaceFirst("RelayState=", "RelayState=AlteredData") );
// Really only the query string is necessary to cause failure, but just to be safe...
request.setParameter("RelayState", "AlteredData" + request.getParameter("RelayState") );
assertRuleFailure("Protocol message signature was invalid due to modification of the signed content");
}
/**
* Test context issuer set, valid signature with untrusted credential.
*/
public void testNoContextIssuer() {
messageContext.setInboundMessageIssuer(null);
assertRuleFailure("Protocol message signature should have been unevaluable due to absence of context issuer");
}
/** {@inheritDoc} */
protected AuthnRequest buildInboundSAMLMessage() {
AuthnRequest request =
(AuthnRequest) unmarshallElement("/data/org/opensaml/saml2/binding/AuthnRequest.xml");
return request;
}
/**
* Build an Issuer with entity format.
*
* @return a new Issuer
*/
private Issuer buildIssuer() {
Issuer issuerXO = (Issuer) buildXMLObject(Issuer.DEFAULT_ELEMENT_NAME);
issuerXO.setValue(issuer);
issuerXO.setFormat(NameIDType.ENTITY);
return issuerXO;
}
/** {@inheritDoc} */
protected InTransport buildInTransport() {
//
// Encode the "outbound" message context, with simple signature
//
MockHttpServletResponse response = new MockHttpServletResponse();
HttpServletResponseAdapter outTransport = new HttpServletResponseAdapter(response, false);
SAMLObjectBuilder<Endpoint> endpointBuilder = (SAMLObjectBuilder<Endpoint>) builderFactory
.getBuilder(AssertionConsumerService.DEFAULT_ELEMENT_NAME);
Endpoint samlEndpoint = endpointBuilder.buildObject();
samlEndpoint.setLocation("http://example.org");
samlEndpoint.setResponseLocation("http://example.org/response");
BasicSAMLMessageContext outboundMessgeContext = new BasicSAMLMessageContext();
outboundMessgeContext.setOutboundMessageTransport(outTransport);
outboundMessgeContext.setOutboundSAMLMessage(buildInboundSAMLMessage());
outboundMessgeContext.setRelayState(expectedRelayValue);
outboundMessgeContext.setPeerEntityEndpoint(samlEndpoint);
outboundMessgeContext.setOutboundSAMLMessageSigningCredential(signingX509Cred);
HTTPRedirectDeflateEncoder encoder = new HTTPRedirectDeflateEncoder();
try {
encoder.encode(outboundMessgeContext);
} catch (MessageEncodingException e) {
fail("Could not encode outbound message context");
}
// Now populate the new "inbound" message context with the "outbound" encoded info
MockHttpServletRequest request = new MockHttpServletRequest();
HTTPInTransport inTransport = new HttpServletRequestAdapter(request);
request.setMethod("GET");
// The Spring mock object doesn't convert between the query params and the getParameter apparently,
// so have to set them both ways.
URLBuilder urlBuilder = new URLBuilder(response.getRedirectedUrl());
request.setQueryString(urlBuilder.buildQueryString());
for (Pair<String, String> param : urlBuilder.getQueryParams()) {
request.setParameter(param.getFirst(), param.getSecond());
}
return inTransport;
}
}