keycloak-aplcache
Changes
services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java 4(+3 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java 1(+1 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java 101(+90 -11)
testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml 1(+1 -0)
Details
diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
index cc94b8a..9a28137 100755
--- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
+++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java
@@ -17,21 +17,19 @@
package org.keycloak.saml;
-import org.keycloak.common.util.PemUtils;
-
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class SPMetadataDescriptor {
- public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String certificatePem) {
+ public static String getSPDescriptor(String binding, String assertionEndpoint, String logoutEndpoint, boolean wantAuthnRequestsSigned, String entityId, String nameIDPolicyFormat, String signingCerts) {
String descriptor =
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\n" +
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n";
- if (wantAuthnRequestsSigned) {
- descriptor += xmlKeyInfo(null, certificatePem, "signing", true);
+ if (wantAuthnRequestsSigned && signingCerts != null) {
+ descriptor += signingCerts;
}
descriptor +=
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
index 3ee5b93..0ef5276 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java
@@ -73,8 +73,9 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
-import java.security.PublicKey;
+import java.security.Key;
import java.security.cert.X509Certificate;
+import java.util.LinkedList;
import java.util.List;
import org.keycloak.rotation.HardcodedKeyLocator;
import org.keycloak.rotation.KeyLocator;
@@ -179,15 +180,18 @@ public class SAMLEndpoint {
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
protected KeyLocator getIDPKeyLocator() {
- // TODO !!!!!!!!!!!!!!!! Parse key from IDP's SAML descriptor
+ List<Key> keys = new LinkedList<>();
- X509Certificate certificate = null;
- try {
- certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
- } catch (ProcessingException e) {
- throw new RuntimeException(e);
+ for (String signingCertificate : config.getSigningCertificates()) {
+ try {
+ X509Certificate cert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(signingCertificate.replaceAll("\\s", ""));
+ keys.add(cert.getPublicKey());
+ } catch (ProcessingException e) {
+ throw new RuntimeException(e);
+ }
}
- return new HardcodedKeyLocator(certificate.getPublicKey());
+
+ return new HardcodedKeyLocator(keys);
}
public Response execute(String samlRequest, String samlResponse, String relayState) {
@@ -277,7 +281,7 @@ public class SAMLEndpoint {
binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
.signatureAlgorithm(provider.getSignatureAlgorithm())
.signDocument();
- if (! postBinding) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
+ if (! postBinding && config.isAddExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
}
}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
index e1f8d16..f96f15a 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java
@@ -50,6 +50,10 @@ import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.security.KeyPair;
+import java.util.Set;
+import java.util.TreeSet;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
+import org.keycloak.keys.KeyMetadata;
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
/**
@@ -106,7 +110,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
binding.signWith(keys.getKid(), keypair);
binding.signatureAlgorithm(getSignatureAlgorithm());
binding.signDocument();
- if (! postBinding) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
+ if (! postBinding && getConfig().isAddExtensionsElementWithKeyInfo()) { // Only include extension if REDIRECT binding and signing whole SAML protocol message
authnRequestBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
}
}
@@ -228,11 +232,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
String entityId = getEntityId(uriInfo, realm);
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
- String certificatePem = PemUtils.encodeCertificate(session.keys().getActiveKey(realm).getCertificate());
- String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, certificatePem);
+
+ StringBuilder keysString = new StringBuilder();
+ Set<KeyMetadata> keys = new TreeSet<>((o1, o2) -> o1.getStatus() == o2.getStatus() // Status can be only PASSIVE OR ACTIVE, push PASSIVE to end of list
+ ? (int) (o2.getProviderPriority() - o1.getProviderPriority())
+ : (o1.getStatus() == KeyMetadata.Status.PASSIVE ? 1 : -1));
+ keys.addAll(session.keys().getKeys(realm, false));
+ for (KeyMetadata key : keys) {
+ addKeyInfo(keysString, key, KeyTypes.SIGNING.value());
+ }
+ String descriptor = SPMetadataDescriptor.getSPDescriptor(authnBinding, endpoint, endpoint, wantAuthnRequestsSigned, entityId, nameIDPolicyFormat, keysString.toString());
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
}
+ private static void addKeyInfo(StringBuilder target, KeyMetadata key, String purpose) {
+ if (key == null) {
+ return;
+ }
+
+ target.append(SPMetadataDescriptor.xmlKeyInfo(" ", key.getKid(), PemUtils.encodeCertificate(key.getCertificate()), purpose, true));
+ }
+
public SignatureAlgorithm getSignatureAlgorithm() {
String alg = getConfig().getSignatureAlgorithm();
if (alg != null) {
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
index 1b2fb67..59b46ca 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java
@@ -62,14 +62,45 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
}
+ /**
+ * @deprecated Prefer {@link #getSigningCertificates()}}
+ * @param signingCertificate
+ */
public String getSigningCertificate() {
- return getConfig().get("signingCertificate");
+ return getConfig().get(SIGNING_CERTIFICATE_KEY);
}
+ /**
+ * @deprecated Prefer {@link #addSigningCertificate(String)}}
+ * @param signingCertificate
+ */
public void setSigningCertificate(String signingCertificate) {
- getConfig().put("signingCertificate", signingCertificate);
+ getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
}
+ public void addSigningCertificate(String signingCertificate) {
+ String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+ if (crt == null || crt.isEmpty()) {
+ getConfig().put(SIGNING_CERTIFICATE_KEY, signingCertificate);
+ } else {
+ // Note that "," is not coding character per PEM format specification:
+ // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+ getConfig().put(SIGNING_CERTIFICATE_KEY, crt + "," + signingCertificate);
+ }
+ }
+
+ public String[] getSigningCertificates() {
+ String crt = getConfig().get(SIGNING_CERTIFICATE_KEY);
+ if (crt == null || crt.isEmpty()) {
+ return new String[] { };
+ }
+ // Note that "," is not coding character per PEM format specification:
+ // see https://tools.ietf.org/html/rfc1421, section 4.3.2.4 Step 4: Printable Encoding
+ return crt.split(",");
+ }
+
+ public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
+
public String getNameIDPolicyFormat() {
return getConfig().get("nameIDPolicyFormat");
}
@@ -86,6 +117,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
}
+ public boolean isAddExtensionsElementWithKeyInfo() {
+ return Boolean.valueOf(getConfig().get("addExtensionsElementWithKeyInfo"));
+ }
+
+ public void setAddExtensionsElementWithKeyInfo(boolean addExtensionsElementWithKeyInfo) {
+ getConfig().put("addExtensionsElementWithKeyInfo", String.valueOf(addExtensionsElementWithKeyInfo));
+ }
+
public String getSignatureAlgorithm() {
return getConfig().get("signatureAlgorithm");
}
diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
index 9116b92..0cc72da 100755
--- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
+++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java
@@ -108,6 +108,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
+ samlIdentityProviderConfig.setAddExtensionsElementWithKeyInfo(false);
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
@@ -121,8 +122,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
- // TODO: CHECK
- samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
+ samlIdentityProviderConfig.addSigningCertificate(x509KeyInfo.getTextContent());
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
} else if (keyDescriptorType.getUse() == null) {
@@ -132,8 +132,8 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
}
if (defaultCertificate != null) {
- if (samlIdentityProviderConfig.getSigningCertificate() == null) {
- samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
+ if (samlIdentityProviderConfig.getSigningCertificates().length == 0) {
+ samlIdentityProviderConfig.addSigningCertificate(defaultCertificate);
}
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
diff --git a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
index 9d12242..ced0d7a 100755
--- a/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
+++ b/services/src/main/java/org/keycloak/protocol/saml/installation/SamlSPDescriptorClientInstallation.java
@@ -31,6 +31,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
+import org.keycloak.dom.saml.v2.metadata.KeyTypes;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -45,7 +46,8 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
if (logoutUrl == null) logoutUrl = client.getManagementUrl();
String nameIdFormat = samlClient.getNameIDFormat();
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
- return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, samlClient.getClientSigningCertificate());
+ String spCertificate = SPMetadataDescriptor.xmlKeyInfo(null, samlClient.getClientSigningCertificate(), KeyTypes.SIGNING.value(), true);
+ return SPMetadataDescriptor.getSPDescriptor(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get(), assertionUrl, logoutUrl, samlClient.requiresClientSignature(), client.getClientId(), nameIdFormat, spCertificate);
}
@Override
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
index dbf30e9..09ddb39 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/ImportIdentityProviderTest.java
@@ -193,6 +193,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
assertEquals(true, config.isPostBindingAuthnRequest());
assertEquals(true, config.isPostBindingResponse());
assertEquals(true, config.isValidateSignature());
+ assertEquals(false, config.isAddExtensionsElementWithKeyInfo());
}
private void assertOidcIdentityProviderConfig(IdentityProviderModel identityProvider) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
index e3392b3..f550c17 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/IdentityProviderTest.java
@@ -57,17 +57,43 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.xml.crypto.dsig.XMLSignature;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.hamcrest.Matchers.*;
+import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
+import org.w3c.dom.NodeList;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class IdentityProviderTest extends AbstractAdminTest {
+ // Certificate imported from
+ private static final String SIGNING_CERT_1 = "MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQY"
+ + "DVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXI"
+ + "wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGa"
+ + "cRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlI"
+ + "D6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXP"
+ + "ubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4"
+ + "RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/"
+ + "Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em"
+ + "1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNgg"
+ + "y6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/"
+ + "p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=";
+
+ private static final String SIGNING_CERT_2 = "MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAY"
+ + "DVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1"
+ + "sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQ"
+ + "u+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQ"
+ + "CAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhet"
+ + "vOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idf"
+ + "LXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=";
+
@Test
public void testFindAll() {
create(createRep("google", "google"));
@@ -303,7 +329,45 @@ public class IdentityProviderTest extends AbstractAdminTest {
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata.xml");
Map<String, String> result = realm.identityProviders().importFrom(form);
- assertSamlImport(result);
+ assertSamlImport(result, SIGNING_CERT_1);
+
+ // Create new SAML identity provider using configuration retrieved from import-config
+ create(createRep("saml", "saml", result));
+
+ IdentityProviderResource provider = realm.identityProviders().get("saml");
+ IdentityProviderRepresentation rep = provider.toRepresentation();
+ assertCreatedSamlIdp(rep);
+
+ // Now list the providers - we should see the one just created
+ List<IdentityProviderRepresentation> providers = realm.identityProviders().findAll();
+ Assert.assertNotNull("identityProviders not null", providers);
+ Assert.assertEquals("identityProviders instance count", 1, providers.size());
+ assertEqual(rep, providers.get(0));
+
+ // Perform export, and make sure some of the values are like they're supposed to be
+ Response response = realm.identityProviders().get("saml").export("xml");
+ Assert.assertEquals(200, response.getStatus());
+ body = response.readEntity(String.class);
+ response.close();
+
+ assertSamlExport(body);
+ }
+
+ @Test
+ public void testSamlImportAndExportMultipleSigningKeys() throws URISyntaxException, IOException, ParsingException {
+
+ // Use import-config to convert IDPSSODescriptor file into key value pairs
+ // to use when creating a SAML Identity Provider
+ MultipartFormDataOutput form = new MultipartFormDataOutput();
+ form.addFormData("providerId", "saml", MediaType.TEXT_PLAIN_TYPE);
+
+ URL idpMeta = getClass().getClassLoader().getResource("admin-test/saml-idp-metadata-two-signing-certs.xml");
+ byte [] content = Files.readAllBytes(Paths.get(idpMeta.toURI()));
+ String body = new String(content, Charset.forName("utf-8"));
+ form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata-two-signing-certs");
+
+ Map<String, String> result = realm.identityProviders().importFrom(form);
+ assertSamlImport(result, SIGNING_CERT_1 + "," + SIGNING_CERT_2);
// Create new SAML identity provider using configuration retrieved from import-config
create(createRep("saml", "saml", result));
@@ -464,18 +528,29 @@ public class IdentityProviderTest extends AbstractAdminTest {
// import endpoint simply converts IDPSSODescriptor into key value pairs.
// check that saml-idp-metadata.xml was properly converted into key value pairs
//System.out.println(config);
- Assert.assertEquals("Config size", 7, config.size());
- Assert.assertEquals("validateSignature", "true", config.get("validateSignature"));
- Assert.assertEquals("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleLogoutServiceUrl"));
- Assert.assertEquals("postBindingResponse", "true", config.get("postBindingResponse"));
- Assert.assertEquals("postBindingAuthnRequest", "true", config.get("postBindingAuthnRequest"));
- Assert.assertEquals("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleSignOnServiceUrl"));
- Assert.assertEquals("wantAuthnRequestsSigned", "true", config.get("wantAuthnRequestsSigned"));
- Assert.assertNotNull("signingCertificate not null", config.get("signingCertificate"));
+ assertThat(config.keySet(), containsInAnyOrder(
+ "validateSignature",
+ "singleLogoutServiceUrl",
+ "postBindingResponse",
+ "postBindingAuthnRequest",
+ "singleSignOnServiceUrl",
+ "wantAuthnRequestsSigned",
+ "signingCertificate",
+ "addExtensionsElementWithKeyInfo"
+ ));
+ assertThat(config, hasEntry("validateSignature", "true"));
+ assertThat(config, hasEntry("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
+ assertThat(config, hasEntry("postBindingResponse", "true"));
+ assertThat(config, hasEntry("postBindingAuthnRequest", "true"));
+ assertThat(config, hasEntry("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml"));
+ assertThat(config, hasEntry("wantAuthnRequestsSigned", "true"));
+ assertThat(config, hasEntry("addExtensionsElementWithKeyInfo", "false"));
+ assertThat(config, hasEntry(is("signingCertificate"), notNullValue()));
}
- private void assertSamlImport(Map<String, String> config) {
+ private void assertSamlImport(Map<String, String> config, String expectedSigningCertificates) {
assertSamlConfig(config);
+ assertThat(config, hasEntry("signingCertificate", expectedSigningCertificates));
}
private void assertSamlExport(String body) throws ParsingException, URISyntaxException {
@@ -534,7 +609,11 @@ public class IdentityProviderTest extends AbstractAdminTest {
Assert.assertNotNull("KeyDescriptor not null", desc.getKeyDescriptor());
Assert.assertEquals("KeyDescriptor.size", 1, desc.getKeyDescriptor().size());
- Assert.assertEquals("KeyDescriptor.Use", KeyTypes.SIGNING, desc.getKeyDescriptor().get(0).getUse());
+ KeyDescriptorType keyDesc = desc.getKeyDescriptor().get(0);
+ assertThat(keyDesc, notNullValue());
+ assertThat(keyDesc.getUse(), equalTo(KeyTypes.SIGNING));
+ NodeList cert = keyDesc.getKeyInfo().getElementsByTagNameNS(XMLSignature.XMLNS, "X509Certificate");
+ assertThat("KeyDescriptor.Signing.Cert existence", cert.getLength(), is(1));
}
private void assertProviderInfo(Map<String, String> info, String id, String name) {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
index 2bcfc21..f28e206 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata.xml
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
>
<IDPSSODescriptor WantAuthnRequestsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml
new file mode 100644
index 0000000..dba0d5a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/admin-test/saml-idp-metadata-two-signing-certs.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
+ xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
+>
+ <IDPSSODescriptor WantAuthnRequestsSigned="true"
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
+
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="http://localhost:8080/auth/realms/master/protocol/saml" />
+ <SingleLogoutService
+ Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="http://localhost:8080/auth/realms/master/protocol/saml" />
+ <KeyDescriptor use="signing">
+ <dsig:KeyInfo>
+ <dsig:KeyName>hAoy_sBtpu6FdRVCk7ykihF6Ug-o0pKPK3LN9RYkeqs</dsig:KeyName>
+ <dsig:X509Data>
+ <dsig:X509Certificate>
+ MIICmzCCAYMCBgFUYnC0OjANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDDAZtYXN0ZXIwHhcNMTYwNDI5MTQzMjEzWhcNMjYwNDI5MTQzMzUzWjARMQ8wDQYDVQQDDAZtYXN0ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCN25AW1poMEZRbuMAHG58AThZmCwMV6/Gcui4mjGacRFyudgqzLjQ2rxpoW41JAtLjbjeAhuWvirUcFVcOeS3gM/ZC27qCpYighAcylZz6MYocnEe1+e8rPPk4JlID6Wv62dgu+pL/vYsQpRhvD3Y2c/ytgr5D32xF+KnzDehUy5BSyzypvu12Wq9mS5vK5tzkN37EjkhpY2ZxaXPubjDIITCAL4Q8M/m5IlacBaUZbzI4AQrHnMP1O1IH2dHSWuMiBe+xSDTco72PmuYPJKTV4wQdeBUIkYbfLc4RxVmXEvgkQgyW86EoMPxlWJpj7+mTIR+l+2thZPr/VgwTs82rAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAA/Ip/Hi8RoVu5ouaFFlc5whT7ltuK8slfLGW4tM4vJXhInYwsqIRQKBNDYW/64xle3eII4u1yAH1OYRRwEs7Em1pr4QuFuTY1at+aE0sE46XDlyESI0txJjWxYoT133vM0We2pj1b2nxgU30rwjKA3whnKEfTEYT/n3JBSqNggy6l8ZGw/oPSgvPaR4+xeB1tfQFC4VrLoYKoqH6hAL530nKxL+qV8AIfL64NDEE8ankIAEDAAFe8x3CPUfXR/p4KOANKkpz8ieQaHDb1eITkAwUwjESj6UF9D1aePlhWls/HX0gujFXtWfWfrJ8CU/ogwlH8y1jgRuLjFQYZk6llc=
+ </dsig:X509Certificate>
+ </dsig:X509Data>
+ </dsig:KeyInfo>
+ </KeyDescriptor>
+ <KeyDescriptor use="signing">
+ <dsig:KeyInfo>
+ <dsig:KeyName>FJ86GcF3jTbNLOco4NvZkUCIUmfYCqoqtOQeMfbhNlE</dsig:KeyName>
+ <dsig:X509Data>
+ <dsig:X509Certificate>
+ MIIBnDCCAQUCBgFYKXKsPTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wHhcNMTYxMTAzMDkwNzEwWhcNMjYxMTAzMDkwODUwWjAUMRIwEAYDVQQDDAlzYW1sLWRlbW8wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKtWsK5O0CtuBpnMvWG+HTG0vmZzujQ2o9WdheQu+BzCILcGMsbDW0YQaglpcO5JpGWWhubnckGGPHfdQ2/7nP9QwbiTK0FbGF41UqcvoaCqU1psxoV88s8IXyQCAqeyLv00yj6foqdJjxh5SZ5z+na+M7Y2OxIBVxYRAxWEnfUvAgMBAAEwDQYJKoZIhvcNAQELBQADgYEAhetvOU8TyqfZF5jpv0IcrviLl/DoFrbjByeHR+pu/vClcAOjL/u7oQELuuTfNsBI4tpexUj5G8q/YbEz0gk7idfLXrAUVcsR73oTngrhRfwUSmPrjjK0kjcRb6HL9V/+wh3R/6mEd59U08ExT8N38rhmn0CI3ehMdebReprP7U8=
+ </dsig:X509Certificate>
+ </dsig:X509Data>
+ </dsig:KeyInfo>
+ </KeyDescriptor>
+ </IDPSSODescriptor>
+</EntityDescriptor>
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index e892501..a0b0d78 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -508,8 +508,8 @@ force-authentication=Force Authentication
identity-provider.force-authentication.tooltip=Indicates whether the identity provider must authenticate the presenter directly rather than rely on a previous security context.
validate-signature=Validate Signature
saml.validate-signature.tooltip=Enable/disable signature validation of SAML responses.
-validating-x509-certificate=Validating X509 Certificate
-validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures.
+validating-x509-certificate=Validating X509 Certificates
+validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures. Multiple certificates can be entered, separated by comma (,).
saml.import-from-url.tooltip=Import metadata from a remote IDP SAML entity descriptor.
social.client-id.tooltip=The client identifier registered with the identity provider.
social.client-secret.tooltip=The client secret registered with the identity provider.