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:
parent
7bd78f604a
commit
ac490a666c
13 changed files with 204 additions and 56 deletions
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,3 +18,5 @@ SamlSignatureTest
|
||||||
KcSamlBrokerTest
|
KcSamlBrokerTest
|
||||||
KcSamlFirstBrokerLoginTest
|
KcSamlFirstBrokerLoginTest
|
||||||
KcSamlEncryptedIdTest
|
KcSamlEncryptedIdTest
|
||||||
|
KcSamlSignedBrokerTest
|
||||||
|
KcSamlSpDescriptorTest
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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">
|
||||||
|
|
Loading…
Reference in a new issue