diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthnRequestBuilderTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthnRequestBuilderTests.java index 94f6637d6d5..ed55eb68327 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthnRequestBuilderTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthnRequestBuilderTests.java @@ -16,8 +16,6 @@ import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.AuthnRequest; import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.metadata.EntityDescriptor; -import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; -import org.opensaml.saml.saml2.metadata.SingleSignOnService; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; @@ -38,17 +36,7 @@ public class SamlAuthnRequestBuilderTests extends SamlTestCase { @Before public void init() throws Exception { SamlUtils.initialize(logger); - - final SingleSignOnService sso = SamlUtils.buildObject(SingleSignOnService.class, SingleSignOnService.DEFAULT_ELEMENT_NAME); - sso.setLocation(IDP_URL); - sso.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); - - final IDPSSODescriptor idpRole = SamlUtils.buildObject(IDPSSODescriptor.class, IDPSSODescriptor.DEFAULT_ELEMENT_NAME); - idpRole.getSingleSignOnServices().add(sso); - - idpDescriptor = SamlUtils.buildObject(EntityDescriptor.class, EntityDescriptor.DEFAULT_ELEMENT_NAME); - idpDescriptor.setEntityID(IDP_ENTITY_ID); - idpDescriptor.getRoleDescriptors().add(idpRole); + idpDescriptor = buildIdPDescriptor(IDP_URL, IDP_ENTITY_ID); } public void testBuildRequestWithPersistentNameAndNoForceAuth() throws Exception { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRedirectTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRedirectTests.java index fcc3e277104..4a35fcb405a 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRedirectTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlRedirectTests.java @@ -7,9 +7,11 @@ package org.elasticsearch.xpack.security.authc.saml; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; +import org.opensaml.saml.common.xml.SAMLConstants; import org.opensaml.saml.saml2.core.Issuer; import org.opensaml.saml.saml2.core.LogoutRequest; import org.opensaml.saml.saml2.core.NameID; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.opensaml.security.x509.X509Credential; import java.io.UnsupportedEncodingException; @@ -19,7 +21,9 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.Signature; import java.security.SignatureException; +import java.time.Clock; import java.util.Base64; +import java.util.Collections; import static java.util.Collections.emptySet; import static java.util.Collections.singleton; @@ -29,6 +33,9 @@ import static org.hamcrest.Matchers.startsWith; public class SamlRedirectTests extends SamlTestCase { private static final String IDP_ENTITY_ID = "https://idp.test/"; + private static final String SP_ENTITY_ID = "https://sp.example.com/"; + private static final String ACS_URL = "https://sp.example.com/saml/acs"; + private static final String IDP_URL = "https://idp.test/saml/sso/redirect"; private static final String LOGOUT_URL = "https://idp.test/saml/logout"; private static final SigningConfiguration NO_SIGNING = new SigningConfiguration(emptySet(), null); @@ -87,16 +94,32 @@ public class SamlRedirectTests extends SamlTestCase { final SamlRedirect redirect = new SamlRedirect(buildLogoutRequest(LOGOUT_URL + "?"), spConfig); final String url = redirect.getRedirectUrl(); final String queryParam = url.split("\\?")[1].split("&Signature")[0]; - final String params[] = url.split("\\?")[1].split("&"); - assert (params.length == 3); - String sigAlg = parseAndUrlDecodeParameter(params[1]); - // We currently only support signing with SHA256withRSA, this test should be updated if we add support for more - assertThat(sigAlg, equalTo("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")); - sigAlg = "SHA256withRSA"; - final String signature = parseAndUrlDecodeParameter(params[2]); - assertThat(validateSignature(queryParam, sigAlg, signature, credential), equalTo(true)); - assertThat(validateSignature(queryParam, sigAlg, signature, invalidCredential), equalTo(false)); - assertThat(validateSignature(queryParam.substring(0, queryParam.length() - 5), sigAlg, signature, credential), equalTo(false)); + final String signature = validateUrlAndGetSignature(redirect.getRedirectUrl()); + assertThat(validateSignature(queryParam, signature, credential), equalTo(true)); + assertThat(validateSignature(queryParam, signature, invalidCredential), equalTo(false)); + assertThat(validateSignature(queryParam.substring(0, queryParam.length() - 5), signature, credential), equalTo(false)); + } + + public void testAuthnRequestSigning() throws Exception { + final X509Credential credential = (X509Credential) buildOpenSamlCredential(readRandomKeyPair()).get(0); + X509Credential invalidCredential = (X509Credential) buildOpenSamlCredential(readRandomKeyPair()).get(0); + while (invalidCredential.getEntityCertificate().getSerialNumber().equals(credential.getEntityCertificate().getSerialNumber())) { + invalidCredential = (X509Credential) buildOpenSamlCredential(readRandomKeyPair()).get(0); + } + final SigningConfiguration signingConfig = new SigningConfiguration(singleton("*"), credential); + SpConfiguration sp = new SpConfiguration(SP_ENTITY_ID, ACS_URL, LOGOUT_URL, signingConfig, null, Collections.emptyList()); + + EntityDescriptor idpDescriptor = buildIdPDescriptor(IDP_URL, IDP_ENTITY_ID); + + final SamlRedirect redirect = new SamlRedirect(new SamlAuthnRequestBuilder(sp, SAMLConstants.SAML2_POST_BINDING_URI, + idpDescriptor, SAMLConstants.SAML2_REDIRECT_BINDING_URI, Clock.systemUTC()).build(), signingConfig); + final String url = redirect.getRedirectUrl(); + final String queryParam = url.split("\\?")[1].split("&Signature")[0]; + final String signature = validateUrlAndGetSignature(redirect.getRedirectUrl()); + assertThat(validateSignature(queryParam, signature, credential), equalTo(true)); + assertThat(validateSignature(queryParam, signature, invalidCredential), equalTo(false)); + assertThat(validateSignature(queryParam.substring(0, queryParam.length() - 5), signature, credential), + equalTo(false)); } private String parseAndUrlDecodeParameter(String parameter) throws UnsupportedEncodingException { @@ -104,16 +127,25 @@ public class SamlRedirectTests extends SamlTestCase { return URLDecoder.decode(value, "UTF-8"); } - private boolean validateSignature(String queryParam, String sigAlg, String signature, X509Credential credential) { + private String validateUrlAndGetSignature(String url) throws UnsupportedEncodingException { + final String params[] = url.split("\\?")[1].split("&"); + assert (params.length == 3); + String sigAlg = parseAndUrlDecodeParameter(params[1]); + // We currently only support signing with SHA256withRSA, this test should be updated if we add support for more + assertThat(sigAlg, equalTo("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256")); + return parseAndUrlDecodeParameter(params[2]); + } + + private boolean validateSignature(String queryParam, String signature, X509Credential credential) { try { - Signature sig = Signature.getInstance(sigAlg); + // We currently only support signing with SHA256withRSA, this test should be updated if we add support for more + Signature sig = Signature.getInstance("SHA256withRSA"); sig.initVerify(credential.getPublicKey()); sig.update(queryParam.getBytes(StandardCharsets.UTF_8)); return sig.verify(Base64.getDecoder().decode(signature)); } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { return false; } - } private LogoutRequest buildLogoutRequest(String logoutUrl) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java index c3556110202..cf281e404e5 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlTestCase.java @@ -16,6 +16,10 @@ import org.elasticsearch.xpack.core.ssl.CertParsingUtils; import org.elasticsearch.xpack.core.ssl.PemUtils; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.opensaml.saml.common.xml.SAMLConstants; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +import org.opensaml.saml.saml2.metadata.SingleSignOnService; import org.opensaml.security.credential.Credential; import org.opensaml.security.x509.impl.X509KeyManagerX509CredentialAdapter; @@ -135,4 +139,18 @@ public abstract class SamlTestCase extends ESTestCase { assertThat("Exception " + exception + " should be a SAML exception", SamlUtils.isSamlException(exception), is(true)); return exception; } + + protected EntityDescriptor buildIdPDescriptor(String idpUrl, String idpEntityId) { + final SingleSignOnService sso = SamlUtils.buildObject(SingleSignOnService.class, SingleSignOnService.DEFAULT_ELEMENT_NAME); + sso.setLocation(idpUrl); + sso.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI); + + final IDPSSODescriptor idpRole = SamlUtils.buildObject(IDPSSODescriptor.class, IDPSSODescriptor.DEFAULT_ELEMENT_NAME); + idpRole.getSingleSignOnServices().add(sso); + + EntityDescriptor idpDescriptor = SamlUtils.buildObject(EntityDescriptor.class, EntityDescriptor.DEFAULT_ELEMENT_NAME); + idpDescriptor.setEntityID(idpEntityId); + idpDescriptor.getRoleDescriptors().add(idpRole); + return idpDescriptor; + } } diff --git a/x-pack/qa/saml-idp-tests/build.gradle b/x-pack/qa/saml-idp-tests/build.gradle index 2d631f38cf8..ea28cfa4d89 100644 --- a/x-pack/qa/saml-idp-tests/build.gradle +++ b/x-pack/qa/saml-idp-tests/build.gradle @@ -14,7 +14,8 @@ testFixtures.useFixture ":x-pack:test:idp-fixture" String outputDir = "${project.buildDir}/generated-resources/${project.name}" def copyIdpFiles = tasks.register("copyIdpFiles", Copy) { - from idpFixtureProject.files('idp/shibboleth-idp/credentials/idp-browser.pem', 'idp/shibboleth-idp/metadata/idp-metadata.xml'); + from idpFixtureProject.files('idp/shibboleth-idp/credentials/idp-browser.pem', 'idp/shibboleth-idp/metadata/idp-metadata.xml', + 'idp/shibboleth-idp/credentials/sp-signing.key', 'idp/shibboleth-idp/credentials/sp-signing.crt'); into outputDir } project.sourceSets.test.output.dir(outputDir, builtBy: copyIdpFiles) @@ -58,6 +59,8 @@ testClusters.integTest { setting 'xpack.security.authc.realms.saml.shibboleth.sp.acs', 'http://localhost:54321/saml/acs1' setting 'xpack.security.authc.realms.saml.shibboleth.attributes.principal', 'uid' setting 'xpack.security.authc.realms.saml.shibboleth.attributes.name', 'urn:oid:2.5.4.3' + setting 'xpack.security.authc.realms.saml.shibboleth.signing.key', 'sp-signing.key' + setting 'xpack.security.authc.realms.saml.shibboleth.signing.certificate', 'sp-signing.crt' // SAML realm 2 (uses authorization_realms) setting 'xpack.security.authc.realms.saml.shibboleth_native.order', '2' setting 'xpack.security.authc.realms.saml.shibboleth_native.idp.entity_id', 'https://test.shibboleth.elastic.local/' @@ -66,6 +69,8 @@ testClusters.integTest { setting 'xpack.security.authc.realms.saml.shibboleth_native.sp.acs', 'http://localhost:54321/saml/acs2' setting 'xpack.security.authc.realms.saml.shibboleth_native.attributes.principal', 'uid' setting 'xpack.security.authc.realms.saml.shibboleth_native.authorization_realms', 'native' + setting 'xpack.security.authc.realms.saml.shibboleth_native.signing.key', 'sp-signing.key' + setting 'xpack.security.authc.realms.saml.shibboleth_native.signing.certificate', 'sp-signing.crt' // SAML realm 3 (used for negative tests with multiple realms) setting 'xpack.security.authc.realms.saml.shibboleth_negative.order', '3' setting 'xpack.security.authc.realms.saml.shibboleth_negative.idp.entity_id', 'https://test.shibboleth.elastic.local/' @@ -74,12 +79,16 @@ testClusters.integTest { setting 'xpack.security.authc.realms.saml.shibboleth_negative.sp.acs', 'http://localhost:54321/saml/acs3' setting 'xpack.security.authc.realms.saml.shibboleth_negative.attributes.principal', 'uid' setting 'xpack.security.authc.realms.saml.shibboleth_negative.authorization_realms', 'native' + setting 'xpack.security.authc.realms.saml.shibboleth_negative.signing.key', 'sp-signing.key' + setting 'xpack.security.authc.realms.saml.shibboleth_negative.signing.certificate', 'sp-signing.crt' setting 'xpack.security.authc.realms.native.native.order', '4' setting 'xpack.ml.enabled', 'false' setting 'logger.org.elasticsearch.xpack.security', 'TRACE' extraConfigFile 'idp-metadata.xml', file(outputDir + "/idp-metadata.xml") + extraConfigFile 'sp-signing.key', file(outputDir + "/sp-signing.key") + extraConfigFile 'sp-signing.crt', file(outputDir + "/sp-signing.crt") user username: "test_admin", password: 'x-pack-test-password' } diff --git a/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java b/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java index f306a4d2248..8c58576c6e0 100644 --- a/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java +++ b/x-pack/qa/saml-idp-tests/src/test/java/org/elasticsearch/xpack/security/authc/saml/SamlAuthenticationIT.java @@ -156,7 +156,6 @@ public class SamlAuthenticationIT extends ESRestTestCase { *