KEYCLOAK-1881 Support for multiple certificates in broker (hardcoded at the moment)
This commit is contained in:
1 parent
67bb9aef3d
commit
4f9e35c0a1
11 files changed
+220
-37
No files matched your search
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user