KEYCLOAK-1881 Support for multiple certificates in broker (hardcoded at the moment)
This commit is contained in:
parent
67bb9aef3d
commit
4f9e35c0a1
11 changed files with 220 additions and 37 deletions
|
@ -17,21 +17,19 @@
|
||||||
|
|
||||||
package org.keycloak.saml;
|
package org.keycloak.saml;
|
||||||
|
|
||||||
import org.keycloak.common.util.PemUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
*/
|
*/
|
||||||
public class SPMetadataDescriptor {
|
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 =
|
String descriptor =
|
||||||
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + entityId + "\">\n" +
|
||||||
" <SPSSODescriptor AuthnRequestsSigned=\"" + wantAuthnRequestsSigned + "\"\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";
|
" 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) {
|
if (wantAuthnRequestsSigned && signingCerts != null) {
|
||||||
descriptor += xmlKeyInfo(null, certificatePem, "signing", true);
|
descriptor += signingCerts;
|
||||||
}
|
}
|
||||||
descriptor +=
|
descriptor +=
|
||||||
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
|
" <SingleLogoutService Binding=\"" + binding + "\" Location=\"" + logoutEndpoint + "\"/>\n" +
|
||||||
|
|
|
@ -73,8 +73,9 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.PublicKey;
|
import java.security.Key;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.keycloak.rotation.HardcodedKeyLocator;
|
import org.keycloak.rotation.HardcodedKeyLocator;
|
||||||
import org.keycloak.rotation.KeyLocator;
|
import org.keycloak.rotation.KeyLocator;
|
||||||
|
@ -179,15 +180,18 @@ public class SAMLEndpoint {
|
||||||
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
||||||
|
|
||||||
protected KeyLocator getIDPKeyLocator() {
|
protected KeyLocator getIDPKeyLocator() {
|
||||||
// TODO !!!!!!!!!!!!!!!! Parse key from IDP's SAML descriptor
|
List<Key> keys = new LinkedList<>();
|
||||||
|
|
||||||
X509Certificate certificate = null;
|
for (String signingCertificate : config.getSigningCertificates()) {
|
||||||
try {
|
try {
|
||||||
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
|
X509Certificate cert = XMLSignatureUtil.getX509CertificateFromKeyInfoString(signingCertificate.replaceAll("\\s", ""));
|
||||||
} catch (ProcessingException e) {
|
keys.add(cert.getPublicKey());
|
||||||
throw new RuntimeException(e);
|
} 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) {
|
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())
|
binding.signWith(keys.getKid(), keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate())
|
||||||
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
||||||
.signDocument();
|
.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()));
|
builder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,10 @@ import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.security.KeyPair;
|
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;
|
import org.keycloak.saml.processing.core.util.KeycloakKeySamlExtensionGenerator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -106,7 +110,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
binding.signWith(keys.getKid(), keypair);
|
binding.signWith(keys.getKid(), keypair);
|
||||||
binding.signatureAlgorithm(getSignatureAlgorithm());
|
binding.signatureAlgorithm(getSignatureAlgorithm());
|
||||||
binding.signDocument();
|
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()));
|
authnRequestBuilder.addExtension(new KeycloakKeySamlExtensionGenerator(keys.getKid()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,11 +232,27 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
||||||
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
boolean wantAuthnRequestsSigned = getConfig().isWantAuthnRequestsSigned();
|
||||||
String entityId = getEntityId(uriInfo, realm);
|
String entityId = getEntityId(uriInfo, realm);
|
||||||
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
|
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();
|
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() {
|
public SignatureAlgorithm getSignatureAlgorithm() {
|
||||||
String alg = getConfig().getSignatureAlgorithm();
|
String alg = getConfig().getSignatureAlgorithm();
|
||||||
if (alg != null) {
|
if (alg != null) {
|
||||||
|
|
|
@ -62,14 +62,45 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
|
getConfig().put("forceAuthn", String.valueOf(forceAuthn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Prefer {@link #getSigningCertificates()}}
|
||||||
|
* @param signingCertificate
|
||||||
|
*/
|
||||||
public String getSigningCertificate() {
|
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) {
|
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() {
|
public String getNameIDPolicyFormat() {
|
||||||
return getConfig().get("nameIDPolicyFormat");
|
return getConfig().get("nameIDPolicyFormat");
|
||||||
}
|
}
|
||||||
|
@ -86,6 +117,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
||||||
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
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() {
|
public String getSignatureAlgorithm() {
|
||||||
return getConfig().get("signatureAlgorithm");
|
return getConfig().get("signatureAlgorithm");
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,6 +108,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
||||||
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
||||||
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
||||||
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
|
samlIdentityProviderConfig.setAddExtensionsElementWithKeyInfo(false);
|
||||||
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
||||||
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
||||||
|
@ -121,8 +122,7 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
||||||
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
||||||
|
|
||||||
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
||||||
// TODO: CHECK
|
samlIdentityProviderConfig.addSigningCertificate(x509KeyInfo.getTextContent());
|
||||||
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
|
|
||||||
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
||||||
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
||||||
} else if (keyDescriptorType.getUse() == null) {
|
} else if (keyDescriptorType.getUse() == null) {
|
||||||
|
@ -132,8 +132,8 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
if (defaultCertificate != null) {
|
if (defaultCertificate != null) {
|
||||||
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
|
if (samlIdentityProviderConfig.getSigningCertificates().length == 0) {
|
||||||
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
|
samlIdentityProviderConfig.addSigningCertificate(defaultCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @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();
|
if (logoutUrl == null) logoutUrl = client.getManagementUrl();
|
||||||
String nameIdFormat = samlClient.getNameIDFormat();
|
String nameIdFormat = samlClient.getNameIDFormat();
|
||||||
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
|
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
|
@Override
|
||||||
|
|
|
@ -57,17 +57,43 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.xml.crypto.dsig.XMLSignature;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
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>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class IdentityProviderTest extends AbstractAdminTest {
|
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
|
@Test
|
||||||
public void testFindAll() {
|
public void testFindAll() {
|
||||||
create(createRep("google", "google"));
|
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");
|
form.addFormData("file", body, MediaType.APPLICATION_XML_TYPE, "saml-idp-metadata.xml");
|
||||||
|
|
||||||
Map<String, String> result = realm.identityProviders().importFrom(form);
|
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 new SAML identity provider using configuration retrieved from import-config
|
||||||
create(createRep("saml", "saml", result));
|
create(createRep("saml", "saml", result));
|
||||||
|
@ -464,18 +528,29 @@ public class IdentityProviderTest extends AbstractAdminTest {
|
||||||
// import endpoint simply converts IDPSSODescriptor into key value pairs.
|
// import endpoint simply converts IDPSSODescriptor into key value pairs.
|
||||||
// check that saml-idp-metadata.xml was properly converted into key value pairs
|
// check that saml-idp-metadata.xml was properly converted into key value pairs
|
||||||
//System.out.println(config);
|
//System.out.println(config);
|
||||||
Assert.assertEquals("Config size", 7, config.size());
|
assertThat(config.keySet(), containsInAnyOrder(
|
||||||
Assert.assertEquals("validateSignature", "true", config.get("validateSignature"));
|
"validateSignature",
|
||||||
Assert.assertEquals("singleLogoutServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleLogoutServiceUrl"));
|
"singleLogoutServiceUrl",
|
||||||
Assert.assertEquals("postBindingResponse", "true", config.get("postBindingResponse"));
|
"postBindingResponse",
|
||||||
Assert.assertEquals("postBindingAuthnRequest", "true", config.get("postBindingAuthnRequest"));
|
"postBindingAuthnRequest",
|
||||||
Assert.assertEquals("singleSignOnServiceUrl", "http://localhost:8080/auth/realms/master/protocol/saml", config.get("singleSignOnServiceUrl"));
|
"singleSignOnServiceUrl",
|
||||||
Assert.assertEquals("wantAuthnRequestsSigned", "true", config.get("wantAuthnRequestsSigned"));
|
"wantAuthnRequestsSigned",
|
||||||
Assert.assertNotNull("signingCertificate not null", config.get("signingCertificate"));
|
"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);
|
assertSamlConfig(config);
|
||||||
|
assertThat(config, hasEntry("signingCertificate", expectedSigningCertificates));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSamlExport(String body) throws ParsingException, URISyntaxException {
|
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.assertNotNull("KeyDescriptor not null", desc.getKeyDescriptor());
|
||||||
Assert.assertEquals("KeyDescriptor.size", 1, desc.getKeyDescriptor().size());
|
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) {
|
private void assertProviderInfo(Map<String, String> info, String id, String name) {
|
||||||
|
|
|
@ -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>
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
<EntityDescriptor entityID="http://localhost:8080/auth/realms/master"
|
||||||
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
xmlns:dsig="http://www.w3.org/2000/09/xmldsig#"
|
||||||
>
|
>
|
||||||
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
<IDPSSODescriptor WantAuthnRequestsSigned="true"
|
||||||
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
|
|
@ -193,6 +193,7 @@ public class ImportIdentityProviderTest extends AbstractIdentityProviderModelTes
|
||||||
assertEquals(true, config.isPostBindingAuthnRequest());
|
assertEquals(true, config.isPostBindingAuthnRequest());
|
||||||
assertEquals(true, config.isPostBindingResponse());
|
assertEquals(true, config.isPostBindingResponse());
|
||||||
assertEquals(true, config.isValidateSignature());
|
assertEquals(true, config.isValidateSignature());
|
||||||
|
assertEquals(false, config.isAddExtensionsElementWithKeyInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertOidcIdentityProviderConfig(IdentityProviderModel identityProvider) {
|
private void assertOidcIdentityProviderConfig(IdentityProviderModel identityProvider) {
|
||||||
|
|
|
@ -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.
|
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
|
validate-signature=Validate Signature
|
||||||
saml.validate-signature.tooltip=Enable/disable signature validation of SAML responses.
|
saml.validate-signature.tooltip=Enable/disable signature validation of SAML responses.
|
||||||
validating-x509-certificate=Validating X509 Certificate
|
validating-x509-certificate=Validating X509 Certificates
|
||||||
validating-x509-certificate.tooltip=The certificate in PEM format that must be used to check for signatures.
|
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.
|
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-id.tooltip=The client identifier registered with the identity provider.
|
||||||
social.client-secret.tooltip=The client secret registered with the identity provider.
|
social.client-secret.tooltip=The client secret registered with the identity provider.
|
||||||
|
|
Loading…
Reference in a new issue