Fix KcSamlSignedBrokerTest in FIPS. Support for choosing realm encryption key for decrypt SAML assertions instead of realm signature key

Closes #16324
This commit is contained in:
mposolda 2023-01-09 14:44:34 +01:00 committed by Marek Posolda
parent 7bd78f604a
commit ac490a666c
13 changed files with 204 additions and 56 deletions

View file

@ -32,6 +32,8 @@ import java.util.Objects;
*/ */
public class DefaultKeyProviders { public class DefaultKeyProviders {
public static final String DEFAULT_PRIORITY = "100";
public static void createProviders(RealmModel realm) { public static void createProviders(RealmModel realm) {
if (!hasProvider(realm, "rsa-generated")) { if (!hasProvider(realm, "rsa-generated")) {
createRsaKeyProvider("rsa-generated", realm); createRsaKeyProvider("rsa-generated", realm);
@ -53,7 +55,7 @@ public class DefaultKeyProviders {
generated.setProviderType(KeyProvider.class.getName()); generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100"); config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("keyUse", KeyUse.SIG.name()); config.putSingle("keyUse", KeyUse.SIG.name());
generated.setConfig(config); generated.setConfig(config);
@ -68,7 +70,7 @@ public class DefaultKeyProviders {
generated.setProviderType(KeyProvider.class.getName()); generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100"); config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("keyUse", KeyUse.ENC.name()); config.putSingle("keyUse", KeyUse.ENC.name());
config.putSingle("algorithm", JWEConstants.RSA_OAEP); config.putSingle("algorithm", JWEConstants.RSA_OAEP);
generated.setConfig(config); generated.setConfig(config);
@ -85,7 +87,7 @@ public class DefaultKeyProviders {
generated.setProviderType(KeyProvider.class.getName()); generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100"); config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("algorithm", Algorithm.HS256); config.putSingle("algorithm", Algorithm.HS256);
generated.setConfig(config); generated.setConfig(config);
@ -101,7 +103,7 @@ public class DefaultKeyProviders {
generated.setProviderType(KeyProvider.class.getName()); generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100"); config.putSingle("priority", DEFAULT_PRIORITY);
generated.setConfig(config); generated.setConfig(config);
realm.addComponentModel(generated); realm.addComponentModel(generated);
@ -121,7 +123,7 @@ public class DefaultKeyProviders {
rsa.setProviderType(KeyProvider.class.getName()); rsa.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>(); MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100"); config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("privateKey", privateKeyPem); config.putSingle("privateKey", privateKeyPem);
if (certificatePem != null) { if (certificatePem != null) {
config.putSingle("certificate", certificatePem); config.putSingle("certificate", certificatePem);

View file

@ -99,6 +99,10 @@ public interface KeyManager {
this.certificate = certificate; this.certificate = certificate;
} }
public ActiveRsaKey(KeyWrapper keyWrapper) {
this(keyWrapper.getKid(), (PrivateKey) keyWrapper.getPrivateKey(), (PublicKey) keyWrapper.getPublicKey(), keyWrapper.getCertificate());
}
public String getKid() { public String getKid() {
return kid; return kid;
} }

View file

@ -415,7 +415,7 @@ public class SAMLEndpoint {
} }
session.getContext().setAuthenticationSession(authSession); session.getContext().setAuthenticationSession(authSession);
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm); KeyManager.ActiveRsaKey keys = SamlProtocolUtils.getDecryptionKey(session, realm, config);
if (! isSuccessfulSamlResponse(responseType)) { if (! isSuccessfulSamlResponse(responseType)) {
String statusMessage = responseType.getStatus() == null || responseType.getStatus().getStatusMessage() == null ? Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR : responseType.getStatus().getStatusMessage(); String statusMessage = responseType.getStatus() == null || responseType.getStatus().getStatusMessage() == null ? Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR : responseType.getStatus().getStatusMessage();
return callback.error(statusMessage); return callback.error(statusMessage);

View file

@ -28,6 +28,7 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus; import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse; import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType; import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.assertion.NameIDType;
@ -94,6 +95,8 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/** /**
* @author Pedro Igor * @author Pedro Igor
@ -360,27 +363,16 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat(); String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
List<Element> signingKeys = new LinkedList<>(); List<Element> signingKeys = streamForExport(session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256), false)
List<Element> encryptionKeys = new LinkedList<>(); .collect(Collectors.toList());
session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256) // See also SamlProtocolUtils.getDecryptionKey
.filter(Objects::nonNull) String encAlg = getConfig().getEncryptionAlgorithm();
.filter(key -> key.getCertificate() != null) Stream<KeyWrapper> encryptionKeyWrappers = (encAlg != null && !encAlg.trim().isEmpty())
.sorted(SamlService::compareKeys) ? session.keys().getKeysStream(realm, KeyUse.ENC, encAlg)
.forEach(key -> { : session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256);
try { List<Element> encryptionKeys = streamForExport(encryptionKeyWrappers, true)
Element element = SPMetadataDescriptor .collect(Collectors.toList());
.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate()));
signingKeys.add(element);
if (key.getStatus() == KeyStatus.ACTIVE) {
encryptionKeys.add(element);
}
} catch (ParserConfigurationException e) {
logger.warn("Failed to export SAML SP Metadata!", e);
throw new RuntimeException(e);
}
});
// Prepare the metadata descriptor model // Prepare the metadata descriptor model
StringWriter sw = new StringWriter(); StringWriter sw = new StringWriter();
@ -462,6 +454,23 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
} }
} }
private Stream<Element> streamForExport(Stream<KeyWrapper> keys, boolean checkActive) {
return keys.filter(Objects::nonNull)
.filter(key -> key.getCertificate() != null)
.filter(key -> !checkActive || key.getStatus() == KeyStatus.ACTIVE)
.sorted(SamlService::compareKeys)
.map(key -> {
try {
Element element = SPMetadataDescriptor
.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate()));
return element;
} catch (ParserConfigurationException e) {
logger.warn("Failed to export SAML SP Metadata!", e);
throw new RuntimeException(e);
}
});
}
public SignatureAlgorithm getSignatureAlgorithm() { public SignatureAlgorithm getSignatureAlgorithm() {
String alg = getConfig().getSignatureAlgorithm(); String alg = getConfig().getSignatureAlgorithm();
if (alg != null) { if (alg != null) {

View file

@ -45,6 +45,7 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
public static final String POST_BINDING_LOGOUT = "postBindingLogout"; public static final String POST_BINDING_LOGOUT = "postBindingLogout";
public static final String POST_BINDING_RESPONSE = "postBindingResponse"; public static final String POST_BINDING_RESPONSE = "postBindingResponse";
public static final String SIGNATURE_ALGORITHM = "signatureAlgorithm"; public static final String SIGNATURE_ALGORITHM = "signatureAlgorithm";
public static final String ENCRYPTION_ALGORITHM = "encryptionAlgorithm";
public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate"; public static final String SIGNING_CERTIFICATE_KEY = "signingCertificate";
public static final String SINGLE_LOGOUT_SERVICE_URL = "singleLogoutServiceUrl"; public static final String SINGLE_LOGOUT_SERVICE_URL = "singleLogoutServiceUrl";
public static final String SINGLE_SIGN_ON_SERVICE_URL = "singleSignOnServiceUrl"; public static final String SINGLE_SIGN_ON_SERVICE_URL = "singleSignOnServiceUrl";
@ -204,6 +205,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
getConfig().put(SIGNATURE_ALGORITHM, signatureAlgorithm); getConfig().put(SIGNATURE_ALGORITHM, signatureAlgorithm);
} }
public String getEncryptionAlgorithm() {
return getConfig().get(ENCRYPTION_ALGORITHM);
}
public void setEncryptionAlgorithm(String encryptionAlgorithm) {
getConfig().put(ENCRYPTION_ALGORITHM, encryptionAlgorithm);
}
public String getEncryptionPublicKey() { public String getEncryptionPublicKey() {
return getConfig().get(ENCRYPTION_PUBLIC_KEY); return getConfig().get(ENCRYPTION_PUBLIC_KEY);
} }

View file

@ -86,7 +86,7 @@ public abstract class AbstractImportedRsaKeyProviderFactory extends AbstractRsaK
Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName()); Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate)); model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate));
} catch (Throwable t) { } catch (Throwable t) {
throw new ComponentValidationException("Failed to generate self-signed certificate"); throw new ComponentValidationException("Failed to generate self-signed certificate", t);
} }
} }
} }

View file

@ -153,7 +153,7 @@ public class DefaultKeyManager implements KeyManager {
@Deprecated @Deprecated
public ActiveRsaKey getActiveRsaKey(RealmModel realm) { public ActiveRsaKey getActiveRsaKey(RealmModel realm) {
KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.RS256); KeyWrapper key = getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
return new ActiveRsaKey(key.getKid(), (PrivateKey) key.getPrivateKey(), (PublicKey) key.getPublicKey(), key.getCertificate()); return new ActiveRsaKey(key);
} }
@Override @Override

View file

@ -22,13 +22,20 @@ import java.io.ByteArrayOutputStream;
import java.net.URI; import java.net.URI;
import java.security.Key; import java.security.Key;
import org.jboss.logging.Logger;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils; import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.protocol.ArtifactResponseType; import org.keycloak.dom.saml.v2.protocol.ArtifactResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType; import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusType; import org.keycloak.dom.saml.v2.protocol.StatusType;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.saml.SignatureAlgorithm; import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
@ -70,6 +77,8 @@ import org.w3c.dom.Element;
*/ */
public class SamlProtocolUtils { public class SamlProtocolUtils {
private static final Logger logger = Logger.getLogger(SamlProtocolUtils.class);
/** /**
* Verifies a signature of the given SAML document using settings for the given client. * Verifies a signature of the given SAML document using settings for the given client.
* Throws an exception if the client signature is expected to be present as per the client * Throws an exception if the client signature is expected to be present as per the client
@ -123,6 +132,22 @@ public class SamlProtocolUtils {
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE); return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
} }
/**
* Returns private key used to decrypt SAML assertions encrypted by 3rd party SAML IDP
*/
public static KeyManager.ActiveRsaKey getDecryptionKey(KeycloakSession session, RealmModel realm, SAMLIdentityProviderConfig idpConfig) {
String encryptionAlgorithm = idpConfig.getEncryptionAlgorithm();
if (encryptionAlgorithm != null && !encryptionAlgorithm.trim().isEmpty()) {
KeyWrapper kw = session.keys().getActiveKey(realm, KeyUse.ENC, encryptionAlgorithm);
return new KeyManager.ActiveRsaKey(kw);
} else {
// Backwards compatibility. Fallback to return default realm key (which is signature key, even if we're not signing anything, but decrypting stuff)
logger.debugf("Fallback to use default realm RSA key as a key for decrypt SAML documents. It is recommended to configure 'Encryption algorithm' on SAML IDP '%s' and configure encryption key of this algorithm in realm '%s'",
idpConfig.getAlias(), realm.getName());
return session.keys().getActiveRsaKey(realm);
}
}
public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException { public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
String certPem = client.getAttribute(attribute); String certPem = client.getAttribute(attribute);
return getPublicKey(certPem); return getPublicKey(certPem);

View file

@ -1,11 +1,19 @@
package org.keycloak.testsuite.broker; package org.keycloak.testsuite.broker;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig; import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.keys.Attributes;
import org.keycloak.keys.GeneratedRsaEncKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.models.IdentityProviderSyncMode; import org.keycloak.models.IdentityProviderSyncMode;
import org.keycloak.models.utils.DefaultKeyProviders;
import org.keycloak.protocol.saml.SamlConfigAttributes; import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentExportRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
@ -60,14 +68,11 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot;
public class KcSamlSignedBrokerTest extends AbstractBrokerTest { public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
private static final String PRIVATE_KEY = "MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAs46ICYPRIkmr8diECmyT59cChTWIEiXYBY3T6OLlZrF8ofVCzbEeoUOmhrtHijxxuKSoqLWP4nNOt3rINtQNBQIDAQABAkBL2nyxuFQTLhhLdPJjDPd2y6gu6ixvrjkSL5ZEHgZXWRHzhTzBT0eRxg/5rJA2NDRMBzTTegaEGkWUt7lF5wDJAiEA5pC+h9NEgqDJSw42I52BOml3II35Z6NlNwl6OMfnD1sCIQDHXUiOIJy4ZcSgv5WGue1KbdNVOT2gop1XzfuyWgtjHwIhAOCjLb9QC3PqC7Tgx8azcnDiyHojWVesTrTsuvQPcAP5AiAkX5OeQrr1NbQTNAEe7IsrmjAFi4T/6stUOsOiPaV4NwIhAJIeyh4foIXIVQ+M4To2koaDFRssxKI9/O72vnZSJ+uA";
private static final String PUBLIC_KEY = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALOOiAmD0SJJq/HYhApsk+fXAoU1iBIl2AWN0+ji5WaxfKH1Qs2xHqFDpoa7R4o8cbikqKi1j+JzTrd6yDbUDQUCAwEAAQ==";
public void withSignedEncryptedAssertions(Runnable testBody, boolean signedDocument, boolean signedAssertion, boolean encryptedAssertion) throws Exception { public void withSignedEncryptedAssertions(Runnable testBody, boolean signedDocument, boolean signedAssertion, boolean encryptedAssertion) throws Exception {
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate(); String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue()); Assert.assertThat(providerCert, Matchers.notNullValue());
String consumerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate(); String consumerCert = KeyUtils.getActiveEncKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), JWEConstants.RSA_OAEP).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue()); Assert.assertThat(consumerCert, Matchers.notNullValue());
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource) try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
@ -75,7 +80,7 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_SIGNED, Boolean.toString(signedAssertion)) .setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_SIGNED, Boolean.toString(signedAssertion))
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, Boolean.toString(encryptedAssertion)) .setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, Boolean.toString(encryptedAssertion))
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "false") .setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "false")
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_PUBLIC_KEY, PUBLIC_KEY) .setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, JWEConstants.RSA_OAEP)
.setAttribute(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, providerCert) .setAttribute(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, providerCert)
.update(); .update();
Closeable clientUpdater = ClientAttributeUpdater.forClient(adminClient, bc.providerRealmName(), bc.getIDPClientIdInProviderRealm()) Closeable clientUpdater = ClientAttributeUpdater.forClient(adminClient, bc.providerRealmName(), bc.getIDPClientIdInProviderRealm())
@ -83,7 +88,6 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, consumerCert) .setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, consumerCert)
.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(signedDocument)) .setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(signedDocument))
.setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, Boolean.toString(signedAssertion)) .setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, Boolean.toString(signedAssertion))
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_PRIVATE_KEY_ATTRIBUTE, PRIVATE_KEY)
.setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false") // Do not require client signature .setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false") // Do not require client signature
.update()) .update())
{ {
@ -260,9 +264,14 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
@Override @Override
public RealmRepresentation createConsumerRealm() { public RealmRepresentation createConsumerRealm() {
RealmRepresentation realm = super.createConsumerRealm(); RealmRepresentation realm = super.createConsumerRealm();
realm.setId(realm.getRealm());
realm.setPublicKey(REALM_PUBLIC_KEY); ComponentExportRepresentation signingKey = createKeyRepToRealm(realm,"rsa");
realm.setPrivateKey(REALM_PRIVATE_KEY); signingKey.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, REALM_PRIVATE_KEY);
ComponentExportRepresentation decryptionKey = createKeyRepToRealm(realm, GeneratedRsaEncKeyProviderFactory.ID);
decryptionKey.getConfig().putSingle(Attributes.KEY_USE, KeyUse.ENC.name());
decryptionKey.getConfig().putSingle(Attributes.ALGORITHM_KEY, JWEConstants.RSA_OAEP);
return realm; return realm;
} }
@ -506,4 +515,17 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
.execute(); .execute();
} }
} }
protected ComponentExportRepresentation createKeyRepToRealm(RealmRepresentation realmRep, String providerId) {
ComponentExportRepresentation rep = new ComponentExportRepresentation();
rep.setName(providerId);
rep.setProviderId(providerId);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, DefaultKeyProviders.DEFAULT_PRIORITY);
if (realmRep.getComponents() == null) {
realmRep.setComponents(new MultivaluedHashMap<>());
}
realmRep.getComponents().add(KeyProvider.class.getName(), rep);
return rep;
}
} }

View file

@ -10,6 +10,7 @@ import org.keycloak.broker.saml.mappers.AttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.UserAttributeMapper; import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType; import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType; import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
@ -22,6 +23,10 @@ import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.crypto.dsig.XMLSignature;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -43,10 +48,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
.update()) .update())
{ {
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); SPSSODescriptorType spDescriptor = getExportedSamlProvider();
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
//attribute mappers do not exist- no AttributeConsumingService //attribute mappers do not exist- no AttributeConsumingService
assertThat(spDescriptor.getAttributeConsumingService(), empty()); assertThat(spDescriptor.getAttributeConsumingService(), empty());
@ -72,10 +74,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
identityProviderResource.addMapper(attrMapperEmail); identityProviderResource.addMapper(attrMapperEmail);
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); SPSSODescriptorType spDescriptor = getExportedSamlProvider();
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService(), not(empty()));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12)); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12));
@ -108,10 +107,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
identityProviderResource.addMapper(attrMapperEmail); identityProviderResource.addMapper(attrMapperEmail);
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); SPSSODescriptorType spDescriptor = getExportedSamlProvider();
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService(), not(empty()));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12)); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12));
@ -145,10 +141,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
identityProviderResource.addMapper(attrMapperEmail); identityProviderResource.addMapper(attrMapperEmail);
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); SPSSODescriptorType spDescriptor = getExportedSamlProvider();
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService(), not(empty()));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12)); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12));
@ -181,10 +174,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
identityProviderResource.addMapper(attrMapperRole); identityProviderResource.addMapper(attrMapperRole);
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); SPSSODescriptorType spDescriptor = getExportedSamlProvider();
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService(), not(empty()));
assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(9)); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(9));
@ -196,4 +186,75 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
assertThat(spDescriptor.getAttributeConsumingService().get(0).getServiceName().get(0).getValue(), is("My Attribute Set")); assertThat(spDescriptor.getAttributeConsumingService().get(0).getServiceName().get(0).getValue(), is("My Attribute Set"));
} }
} }
@Test
public void testKeysDescriptors() throws IOException, ParsingException, URISyntaxException {
// No keys by default
SPSSODescriptorType spDescriptor = getExportedSamlProvider();
Assert.assertNotNull("KeyDescriptor is null", spDescriptor.getKeyDescriptor());
Assert.assertEquals("KeyDescriptor.size", 0, spDescriptor.getKeyDescriptor().size());
// Enable signing for IDP. Only signing key is present
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true")
.update())
{
spDescriptor = getExportedSamlProvider();
Assert.assertEquals("KeyDescriptor.size", 1, spDescriptor.getKeyDescriptor().size());
Map<String, String> certs = convertCerts(spDescriptor);
Assert.assertEquals(1, certs.size());
Assert.assertNotNull(certs.get("signing"));
}
// Enable signing and encryption. Both keys are present and mapped to same realm key
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true")
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.update())
{
spDescriptor = getExportedSamlProvider();
Assert.assertEquals("KeyDescriptor.size", 2, spDescriptor.getKeyDescriptor().size());
Map<String, String> certs = convertCerts(spDescriptor);
Assert.assertEquals(2, certs.size());
String signingCert = certs.get("signing");
String encCert = certs.get("encryption");
Assert.assertNotNull(signingCert);
Assert.assertNotNull(encCert);
Assert.assertEquals(signingCert, encCert);
}
// Enable signing and encryption and set encryption algorithm. Both keys are present and mapped to different realm key (signing to "rsa-generated"m encryption to "rsa-enc-generated")
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true")
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, JWEConstants.RSA_OAEP)
.update())
{
spDescriptor = getExportedSamlProvider();
Assert.assertEquals("KeyDescriptor.size", 2, spDescriptor.getKeyDescriptor().size());
Map<String, String> certs = convertCerts(spDescriptor);
Assert.assertEquals(2, certs.size());
String signingCert = certs.get("signing");
String encCert = certs.get("encryption");
Assert.assertNotNull(signingCert);
Assert.assertNotNull(encCert);
Assert.assertNotEquals(signingCert, encCert);
}
}
private SPSSODescriptorType getExportedSamlProvider() throws ParsingException {
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class);
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
return o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
}
// Key is usage ("signing" or "encryption"), Value is string with X509 certificate
private Map<String, String> convertCerts(SPSSODescriptorType spDescriptor) {
return spDescriptor.getKeyDescriptor().stream()
.collect(Collectors.toMap(
keyDescriptor -> keyDescriptor.getUse().value(),
keyDescriptor -> keyDescriptor.getKeyInfo().getElementsByTagNameNS(XMLSignature.XMLNS, "X509Certificate").item(0).getTextContent()));
}
} }

View file

@ -18,3 +18,5 @@ SamlSignatureTest
KcSamlBrokerTest KcSamlBrokerTest
KcSamlFirstBrokerLoginTest KcSamlFirstBrokerLoginTest
KcSamlEncryptedIdTest KcSamlEncryptedIdTest
KcSamlSignedBrokerTest
KcSamlSpDescriptorTest

View file

@ -354,6 +354,8 @@ sign-assertions=Sign Assertions
sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting is not needed if document is already being signed. sign-assertions.tooltip=Should assertions inside SAML documents be signed? This setting is not needed if document is already being signed.
signature-algorithm=Signature Algorithm signature-algorithm=Signature Algorithm
signature-algorithm.tooltip=The signature algorithm to use to sign documents. Note that 'SHA1' based algorithms are deprecated and can be removed in the future. It is recommended to stick to some more secure algorithm instead of '*_SHA1' signature-algorithm.tooltip=The signature algorithm to use to sign documents. Note that 'SHA1' based algorithms are deprecated and can be removed in the future. It is recommended to stick to some more secure algorithm instead of '*_SHA1'
saml-encryption-algorithm=Encryption Algorithm
saml-encryption-algorithm.tooltip=Encryption algorithm, which is used by SAML IDP for encryption of SAML documents, assertions or IDs. The corresponding decryption key for decrypt SAML document parts will be chosen based on this configured algorithm and should be available in realm keys for the encryption (ENC) usage.
canonicalization-method=Canonicalization Method canonicalization-method=Canonicalization Method
canonicalization-method.tooltip=Canonicalization Method for XML signatures. canonicalization-method.tooltip=Canonicalization Method for XML signatures.
encrypt-assertions=Encrypt Assertions encrypt-assertions=Encrypt Assertions

View file

@ -255,6 +255,18 @@
</div> </div>
<kc-tooltip>{{:: 'signature-algorithm.tooltip' | translate}}</kc-tooltip> <kc-tooltip>{{:: 'signature-algorithm.tooltip' | translate}}</kc-tooltip>
</div> </div>
<div class="form-group">
<label class="col-md-2 control-label" for="encryptionAlgorithm">{{:: 'saml-encryption-algorithm' | translate}}</label>
<div class="col-md-6">
<div>
<select id="encryptionAlgorithm"
ng-model="identityProvider.config.encryptionAlgorithm"
ng-options="alg for alg in serverInfo.listProviderIds('cekmanagement')">
</select>
</div>
</div>
<kc-tooltip>{{:: 'saml-encryption-algorithm.tooltip' | translate}}</kc-tooltip>
</div>
<div class="form-group clearfix block" data-ng-show="identityProvider.config.wantAuthnRequestsSigned == 'true'"> <div class="form-group clearfix block" data-ng-show="identityProvider.config.wantAuthnRequestsSigned == 'true'">
<label class="col-md-2 control-label" for="samlSigKeyNameTranformer">{{:: 'saml-signature-keyName-transformer' | translate}}</label> <label class="col-md-2 control-label" for="samlSigKeyNameTranformer">{{:: 'saml-signature-keyName-transformer' | translate}}</label>
<div class="col-md-6"> <div class="col-md-6">