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 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", "100");
config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("privateKey", privateKeyPem);
if (certificatePem != null) {
config.putSingle("certificate", certificatePem);

View file

@ -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;
}

View file

@ -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);

View file

@ -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<SAMLIdentityP
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
List<Element> signingKeys = new LinkedList<>();
List<Element> encryptionKeys = new LinkedList<>();
List<Element> 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<KeyWrapper> encryptionKeyWrappers = (encAlg != null && !encAlg.trim().isEmpty())
? session.keys().getKeysStream(realm, KeyUse.ENC, encAlg)
: session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256);
List<Element> 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<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() {
String alg = getConfig().getSignatureAlgorithm();
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_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);
}

View file

@ -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);
}
}
}

View file

@ -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

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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<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
KcSamlFirstBrokerLoginTest
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.
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

View file

@ -255,6 +255,18 @@
</div>
<kc-tooltip>{{:: 'signature-algorithm.tooltip' | translate}}</kc-tooltip>
</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'">
<label class="col-md-2 control-label" for="samlSigKeyNameTranformer">{{:: 'saml-signature-keyName-transformer' | translate}}</label>
<div class="col-md-6">