diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java index 901da1213fe..828f4254e72 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java +++ b/server-spi-private/src/main/java/org/keycloak/models/utils/DefaultKeyProviders.java @@ -32,6 +32,8 @@ import java.util.Objects; */ public class DefaultKeyProviders { + public static final String DEFAULT_PRIORITY = "100"; + public static void createProviders(RealmModel realm) { if (!hasProvider(realm, "rsa-generated")) { createRsaKeyProvider("rsa-generated", realm); @@ -53,7 +55,7 @@ public class DefaultKeyProviders { generated.setProviderType(KeyProvider.class.getName()); MultivaluedHashMap config = new MultivaluedHashMap<>(); - config.putSingle("priority", "100"); + config.putSingle("priority", DEFAULT_PRIORITY); config.putSingle("keyUse", KeyUse.SIG.name()); generated.setConfig(config); @@ -68,7 +70,7 @@ public class DefaultKeyProviders { generated.setProviderType(KeyProvider.class.getName()); MultivaluedHashMap config = new MultivaluedHashMap<>(); - config.putSingle("priority", "100"); + config.putSingle("priority", DEFAULT_PRIORITY); config.putSingle("keyUse", KeyUse.ENC.name()); config.putSingle("algorithm", JWEConstants.RSA_OAEP); generated.setConfig(config); @@ -85,7 +87,7 @@ public class DefaultKeyProviders { generated.setProviderType(KeyProvider.class.getName()); MultivaluedHashMap config = new MultivaluedHashMap<>(); - config.putSingle("priority", "100"); + config.putSingle("priority", DEFAULT_PRIORITY); config.putSingle("algorithm", Algorithm.HS256); generated.setConfig(config); @@ -101,7 +103,7 @@ public class DefaultKeyProviders { generated.setProviderType(KeyProvider.class.getName()); MultivaluedHashMap config = new MultivaluedHashMap<>(); - config.putSingle("priority", "100"); + config.putSingle("priority", DEFAULT_PRIORITY); generated.setConfig(config); realm.addComponentModel(generated); @@ -121,7 +123,7 @@ public class DefaultKeyProviders { rsa.setProviderType(KeyProvider.class.getName()); MultivaluedHashMap config = new MultivaluedHashMap<>(); - config.putSingle("priority", "100"); + config.putSingle("priority", DEFAULT_PRIORITY); config.putSingle("privateKey", privateKeyPem); if (certificatePem != null) { config.putSingle("certificate", certificatePem); diff --git a/server-spi/src/main/java/org/keycloak/models/KeyManager.java b/server-spi/src/main/java/org/keycloak/models/KeyManager.java index 94da6e1c366..5fe6db5032f 100644 --- a/server-spi/src/main/java/org/keycloak/models/KeyManager.java +++ b/server-spi/src/main/java/org/keycloak/models/KeyManager.java @@ -99,6 +99,10 @@ public interface KeyManager { this.certificate = certificate; } + public ActiveRsaKey(KeyWrapper keyWrapper) { + this(keyWrapper.getKid(), (PrivateKey) keyWrapper.getPrivateKey(), (PublicKey) keyWrapper.getPublicKey(), keyWrapper.getCertificate()); + } + public String getKid() { return kid; } 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 8c39066ea80..042763ac69a 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLEndpoint.java @@ -415,7 +415,7 @@ public class SAMLEndpoint { } session.getContext().setAuthenticationSession(authSession); - KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm); + KeyManager.ActiveRsaKey keys = SamlProtocolUtils.getDecryptionKey(session, realm, config); if (! isSuccessfulSamlResponse(responseType)) { String statusMessage = responseType.getStatus() == null || responseType.getStatus().getStatusMessage() == null ? Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR : responseType.getStatus().getStatusMessage(); return callback.error(statusMessage); 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 7fd4b49669c..64a830cdcbc 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java @@ -28,6 +28,7 @@ import org.keycloak.common.util.PemUtils; import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.KeyStatus; 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.AuthnStatementType; import org.keycloak.dom.saml.v2.assertion.NameIDType; @@ -94,6 +95,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map.Entry; import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author Pedro Igor @@ -360,27 +363,16 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider signingKeys = new LinkedList<>(); - List encryptionKeys = new LinkedList<>(); + List signingKeys = streamForExport(session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256), false) + .collect(Collectors.toList()); - session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256) - .filter(Objects::nonNull) - .filter(key -> key.getCertificate() != null) - .sorted(SamlService::compareKeys) - .forEach(key -> { - try { - Element element = SPMetadataDescriptor - .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); - } - }); + // See also SamlProtocolUtils.getDecryptionKey + String encAlg = getConfig().getEncryptionAlgorithm(); + Stream encryptionKeyWrappers = (encAlg != null && !encAlg.trim().isEmpty()) + ? session.keys().getKeysStream(realm, KeyUse.ENC, encAlg) + : session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256); + List encryptionKeys = streamForExport(encryptionKeyWrappers, true) + .collect(Collectors.toList()); // Prepare the metadata descriptor model StringWriter sw = new StringWriter(); @@ -462,6 +454,23 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider streamForExport(Stream 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() { 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 65f89552a63..62c0d80e397 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderConfig.java @@ -45,6 +45,7 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel { public static final String POST_BINDING_LOGOUT = "postBindingLogout"; public static final String POST_BINDING_RESPONSE = "postBindingResponse"; 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 SINGLE_LOGOUT_SERVICE_URL = "singleLogoutServiceUrl"; public static final String SINGLE_SIGN_ON_SERVICE_URL = "singleSignOnServiceUrl"; @@ -204,6 +205,14 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel { 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() { return getConfig().get(ENCRYPTION_PUBLIC_KEY); } diff --git a/services/src/main/java/org/keycloak/keys/AbstractImportedRsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/AbstractImportedRsaKeyProviderFactory.java index 90574e0d38e..bbab1aa1be3 100644 --- a/services/src/main/java/org/keycloak/keys/AbstractImportedRsaKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/AbstractImportedRsaKeyProviderFactory.java @@ -86,7 +86,7 @@ public abstract class AbstractImportedRsaKeyProviderFactory extends AbstractRsaK Certificate certificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName()); model.put(Attributes.CERTIFICATE_KEY, PemUtils.encodeCertificate(certificate)); } catch (Throwable t) { - throw new ComponentValidationException("Failed to generate self-signed certificate"); + throw new ComponentValidationException("Failed to generate self-signed certificate", t); } } } diff --git a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java index 03d4cbb11dd..9129a80a968 100644 --- a/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java +++ b/services/src/main/java/org/keycloak/keys/DefaultKeyManager.java @@ -153,7 +153,7 @@ public class DefaultKeyManager implements KeyManager { @Deprecated public ActiveRsaKey getActiveRsaKey(RealmModel realm) { 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 diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java index 5e75ecadc4b..b251830122a 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocolUtils.java @@ -22,13 +22,20 @@ import java.io.ByteArrayOutputStream; import java.net.URI; import java.security.Key; +import org.jboss.logging.Logger; +import org.keycloak.broker.saml.SAMLIdentityProviderConfig; import org.keycloak.common.VerificationException; 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.protocol.ArtifactResponseType; import org.keycloak.dom.saml.v2.protocol.StatusCodeType; import org.keycloak.dom.saml.v2.protocol.StatusType; 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.common.constants.GeneralConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; @@ -70,6 +77,8 @@ import org.w3c.dom.Element; */ 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. * 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); } + /** + * 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 { String certPem = client.getAttribute(attribute); return getPublicKey(certPem); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java index 370c85f3f7b..f757c88c9b4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSignedBrokerTest.java @@ -1,11 +1,19 @@ package org.keycloak.testsuite.broker; import org.keycloak.broker.saml.SAMLIdentityProviderConfig; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.crypto.Algorithm; +import org.keycloak.crypto.KeyUse; 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.utils.DefaultKeyProviders; import org.keycloak.protocol.saml.SamlConfigAttributes; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.ComponentExportRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; @@ -60,14 +68,11 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot; 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 { String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate(); 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()); 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_ENCRYPTED, Boolean.toString(encryptedAssertion)) .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) .update(); 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_SERVER_SIGNATURE, Boolean.toString(signedDocument)) .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 .update()) { @@ -260,9 +264,14 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest { @Override public RealmRepresentation createConsumerRealm() { RealmRepresentation realm = super.createConsumerRealm(); + realm.setId(realm.getRealm()); - realm.setPublicKey(REALM_PUBLIC_KEY); - realm.setPrivateKey(REALM_PRIVATE_KEY); + ComponentExportRepresentation signingKey = createKeyRepToRealm(realm,"rsa"); + 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; } @@ -506,4 +515,17 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest { .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; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java index 1c0a8bcd927..8d58f3a1ac8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java @@ -10,6 +10,7 @@ import org.keycloak.broker.saml.mappers.AttributeToRoleMapper; import org.keycloak.broker.saml.mappers.UserAttributeMapper; import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType; import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType; +import org.keycloak.jose.jwe.JWEConstants; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperSyncMode; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; @@ -22,6 +23,10 @@ import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater; import java.io.Closeable; import java.io.IOException; 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.Matchers.is; @@ -43,10 +48,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest { .update()) { - String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); - SAMLParser parser = SAMLParser.getInstance(); - EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString)); - SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor(); + SPSSODescriptorType spDescriptor = getExportedSamlProvider(); //attribute mappers do not exist- no AttributeConsumingService assertThat(spDescriptor.getAttributeConsumingService(), empty()); @@ -72,10 +74,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest { identityProviderResource.addMapper(attrMapperEmail); - String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); - SAMLParser parser = SAMLParser.getInstance(); - EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString)); - SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor(); + SPSSODescriptorType spDescriptor = getExportedSamlProvider(); assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12)); @@ -108,10 +107,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest { identityProviderResource.addMapper(attrMapperEmail); - String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); - SAMLParser parser = SAMLParser.getInstance(); - EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString)); - SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor(); + SPSSODescriptorType spDescriptor = getExportedSamlProvider(); assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12)); @@ -145,10 +141,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest { identityProviderResource.addMapper(attrMapperEmail); - String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); - SAMLParser parser = SAMLParser.getInstance(); - EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString)); - SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor(); + SPSSODescriptorType spDescriptor = getExportedSamlProvider(); assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); assertThat(spDescriptor.getAttributeConsumingService().get(0).getIndex(), is(12)); @@ -181,10 +174,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest { identityProviderResource.addMapper(attrMapperRole); - String spDescriptorString = identityProviderResource.export(null).readEntity(String.class); - SAMLParser parser = SAMLParser.getInstance(); - EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString)); - SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor(); + SPSSODescriptorType spDescriptor = getExportedSamlProvider(); assertThat(spDescriptor.getAttributeConsumingService(), not(empty())); 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")); } } + + @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 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 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 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 convertCerts(SPSSODescriptorType spDescriptor) { + return spDescriptor.getKeyDescriptor().stream() + .collect(Collectors.toMap( + keyDescriptor -> keyDescriptor.getUse().value(), + keyDescriptor -> keyDescriptor.getKeyInfo().getElementsByTagNameNS(XMLSignature.XMLNS, "X509Certificate").item(0).getTextContent())); + } + } diff --git a/testsuite/integration-arquillian/tests/base/testsuites/fips-suite b/testsuite/integration-arquillian/tests/base/testsuites/fips-suite index fb9496d7c7b..f46d240a411 100644 --- a/testsuite/integration-arquillian/tests/base/testsuites/fips-suite +++ b/testsuite/integration-arquillian/tests/base/testsuites/fips-suite @@ -18,3 +18,5 @@ SamlSignatureTest KcSamlBrokerTest KcSamlFirstBrokerLoginTest KcSamlEncryptedIdTest +KcSamlSignedBrokerTest +KcSamlSpDescriptorTest 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 a314026cfb7..848bae55a25 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 @@ -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. 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' +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.tooltip=Canonicalization Method for XML signatures. encrypt-assertions=Encrypt Assertions diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html index 5b2fdb93cf5..2cbbf27747c 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-identity-provider-saml.html @@ -255,6 +255,18 @@ {{:: 'signature-algorithm.tooltip' | translate}} +
+ +
+
+ +
+
+ {{:: 'saml-encryption-algorithm.tooltip' | translate}} +