Adding support for rsa-oaep for SAML encryption
Closes https://github.com/keycloak/keycloak/issues/19689
This commit is contained in:
parent
035fdc4047
commit
04ac3a64ee
10 changed files with 292 additions and 45 deletions
|
@ -73,6 +73,9 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
|||
protected String encryptionAlgorithm = "AES";
|
||||
protected boolean encrypt;
|
||||
protected String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
|
||||
protected String keyEncryptionAlgorithm;
|
||||
protected String keyEncryptionDigestMethod;
|
||||
protected String keyEncryptionMgfAlgorithm;
|
||||
|
||||
public T canonicalizationMethod(String method) {
|
||||
this.canonicalizationMethodType = method;
|
||||
|
@ -136,6 +139,21 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
|||
return (T)this;
|
||||
}
|
||||
|
||||
public T keyEncryptionAlgorithm(String keyEncryptionAlgorithm) {
|
||||
this.keyEncryptionAlgorithm = keyEncryptionAlgorithm;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T keyEncryptionDigestMethod(String keyEncryptionDigestMethod) {
|
||||
this.keyEncryptionDigestMethod = keyEncryptionDigestMethod;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T keyEncryptionMgfAlgorithm(String keyEncryptionMgfAlgorithm) {
|
||||
this.keyEncryptionMgfAlgorithm = keyEncryptionMgfAlgorithm;
|
||||
return (T)this;
|
||||
}
|
||||
|
||||
public T relayState(String relayState) {
|
||||
this.relayState = relayState;
|
||||
return (T)this;
|
||||
|
@ -271,8 +289,8 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
|
|||
|
||||
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
|
||||
XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
||||
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey,
|
||||
secretKey, encryptionKeySize, encryptedAssertionElementQName, true);
|
||||
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey, secretKey, encryptionKeySize,
|
||||
encryptedAssertionElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||
} catch (Exception e) {
|
||||
throw new ProcessingException("failed to encrypt", e);
|
||||
}
|
||||
|
|
|
@ -79,14 +79,16 @@ public class SPMetadataDescriptor {
|
|||
return entityDescriptor;
|
||||
}
|
||||
|
||||
public static KeyDescriptorType buildKeyDescriptorType(Element keyInfo, KeyTypes use, String algorithm) {
|
||||
public static KeyDescriptorType buildKeyDescriptorType(Element keyInfo, KeyTypes use, String... algorithm) {
|
||||
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
|
||||
keyDescriptor.setUse(use);
|
||||
keyDescriptor.setKeyInfo(keyInfo);
|
||||
|
||||
if (algorithm != null) {
|
||||
EncryptionMethodType encMethod = new EncryptionMethodType(algorithm);
|
||||
keyDescriptor.addEncryptionMethod(encMethod);
|
||||
for (String alg : algorithm) {
|
||||
EncryptionMethodType encMethod = new EncryptionMethodType(alg);
|
||||
keyDescriptor.addEncryptionMethod(encMethod);
|
||||
}
|
||||
}
|
||||
|
||||
return keyDescriptor;
|
||||
|
|
|
@ -99,14 +99,15 @@ public class XMLEncryptionUtil {
|
|||
* @throws org.keycloak.saml.common.exceptions.ProcessingException
|
||||
*/
|
||||
private static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
|
||||
int keySize, String encryptionUrlForKeyUnwrap) throws ProcessingException {
|
||||
int keySize, String keyEncryptionAlgorithm, String keyEncryptionDigestMethod,
|
||||
String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
||||
XMLCipher keyCipher;
|
||||
|
||||
try {
|
||||
keyCipher = XMLCipher.getInstance(encryptionUrlForKeyUnwrap);
|
||||
keyCipher = XMLCipher.getInstance(keyEncryptionAlgorithm, null, keyEncryptionDigestMethod);
|
||||
|
||||
keyCipher.init(XMLCipher.WRAP_MODE, keyUsedToEncryptSecretKey);
|
||||
return keyCipher.encryptKey(document, keyToBeEncrypted);
|
||||
return keyCipher.encryptKey(document, keyToBeEncrypted, keyEncryptionMgfAlgorithm, null);
|
||||
} catch (XMLEncryptionException e) {
|
||||
throw logger.processingError(e);
|
||||
}
|
||||
|
@ -115,7 +116,14 @@ public class XMLEncryptionUtil {
|
|||
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo) throws ProcessingException {
|
||||
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName, addEncryptedKeyInKeyInfo,
|
||||
getXMLEncryptionURLForKeyUnwrap(publicKey.getAlgorithm(), keySize));
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo,
|
||||
String keyEncryptionAlgorithm) throws ProcessingException {
|
||||
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName,
|
||||
addEncryptedKeyInKeyInfo, keyEncryptionAlgorithm, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -123,17 +131,21 @@ public class XMLEncryptionUtil {
|
|||
* data
|
||||
*
|
||||
* @param elementQName QName of the element that we like to encrypt
|
||||
* @param document
|
||||
* @param publicKey
|
||||
* @param secretKey
|
||||
* @param keySize
|
||||
* @param document The document with the element to encrypt
|
||||
* @param publicKey The public Key to wrap the secret key
|
||||
* @param secretKey The secret key to use for encryption
|
||||
* @param keySize The size of the public key
|
||||
* @param wrappingElementQName A QName of an element that will wrap the encrypted element
|
||||
* @param addEncryptedKeyInKeyInfo Need for the EncryptedKey to be placed in ds:KeyInfo
|
||||
* @param keyEncryptionAlgorithm The wrap algorithm for the secret key (can be null, default is used depending the publicKey type)
|
||||
* @param keyEncryptionDigestMethod An optional digestMethod to use (can be null)
|
||||
* @param keyEncryptionMgfAlgorithm The xenc11 MGF Algorithm to use (can be null)
|
||||
*
|
||||
* @throws ProcessingException
|
||||
*/
|
||||
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
|
||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo, String encryptionUrlForKeyUnwrap) throws ProcessingException {
|
||||
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo, String keyEncryptionAlgorithm,
|
||||
String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm) throws ProcessingException {
|
||||
if (elementQName == null)
|
||||
throw logger.nullArgumentError("elementQName");
|
||||
if (document == null)
|
||||
|
@ -148,7 +160,11 @@ public class XMLEncryptionUtil {
|
|||
throw logger.domMissingDocElementError(elementQName.toString());
|
||||
|
||||
XMLCipher cipher = null;
|
||||
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize, encryptionUrlForKeyUnwrap);
|
||||
if (keyEncryptionAlgorithm == null) {
|
||||
// get default one for the public key
|
||||
keyEncryptionAlgorithm = getXMLEncryptionURLForKeyUnwrap(publicKey.getAlgorithm(), keySize);
|
||||
}
|
||||
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||
|
||||
String encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
|
||||
// Encrypt the Document
|
||||
|
|
|
@ -397,7 +397,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return SPMetadataDescriptor.buildKeyDescriptorType(keyInfo, KeyTypes.ENCRYPTION, SAMLEncryptionAlgorithms.forKeycloakIdentifier(key.getAlgorithm()).getXmlEncIdentifier());
|
||||
return SPMetadataDescriptor.buildKeyDescriptorType(keyInfo, KeyTypes.ENCRYPTION, SAMLEncryptionAlgorithms.forKeycloakIdentifier(key.getAlgorithm()).getXmlEncIdentifiers());
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
|
|
|
@ -20,40 +20,73 @@ package org.keycloak.protocol.saml;
|
|||
import org.apache.xml.security.encryption.XMLCipher;
|
||||
import org.keycloak.crypto.Algorithm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This enum provides mapping between Keycloak provided encryption algorithms and algorithms from xmlsec.
|
||||
* It is used to make sure we are using keys generated for given algorithm only with that algorithm.
|
||||
*/
|
||||
public enum SAMLEncryptionAlgorithms {
|
||||
RSA_OAEP(XMLCipher.RSA_OAEP, Algorithm.RSA_OAEP),
|
||||
RSA1_5(XMLCipher.RSA_v1dot5, Algorithm.RSA1_5);
|
||||
RSA_OAEP(Algorithm.RSA_OAEP, XMLCipher.RSA_OAEP, XMLCipher.RSA_OAEP_11),
|
||||
RSA1_5(Algorithm.RSA1_5, XMLCipher.RSA_v1dot5);
|
||||
|
||||
private String xmlEncIdentifier;
|
||||
private String keycloakIdentifier;
|
||||
private static final Map<String, SAMLEncryptionAlgorithms> forXMLEncIdentifier = Arrays.stream(values()).collect(Collectors.toMap(SAMLEncryptionAlgorithms::getXmlEncIdentifier, Function.identity()));
|
||||
private static final Map<String, SAMLEncryptionAlgorithms> forKeycloakIdentifier = Arrays.stream(values()).collect(Collectors.toMap(SAMLEncryptionAlgorithms::getKeycloakIdentifier, Function.identity()));
|
||||
private final String[] xmlEncIdentifier;
|
||||
private final String keycloakIdentifier;
|
||||
private static final Map<String, SAMLEncryptionAlgorithms> forKeycloakIdentifier;
|
||||
private static final Map<String, SAMLEncryptionAlgorithms> forXMLEncIdentifier;
|
||||
|
||||
SAMLEncryptionAlgorithms(String xmlEncIdentifier, String keycloakIdentifier) {
|
||||
static {
|
||||
Map<String, SAMLEncryptionAlgorithms> forKeycloakIdentifierTmp = new HashMap<>();
|
||||
Map<String, SAMLEncryptionAlgorithms> forXMLEncIdentifierTmp = new HashMap<>();
|
||||
for (SAMLEncryptionAlgorithms alg: values()) {
|
||||
forKeycloakIdentifierTmp.put(alg.getKeycloakIdentifier(), alg);
|
||||
for (String xmlAlg : alg.getXmlEncIdentifiers()) {
|
||||
forXMLEncIdentifierTmp.put(xmlAlg, alg);
|
||||
}
|
||||
}
|
||||
forKeycloakIdentifier = Collections.unmodifiableMap(forKeycloakIdentifierTmp);
|
||||
forXMLEncIdentifier = Collections.unmodifiableMap(forXMLEncIdentifierTmp);
|
||||
}
|
||||
|
||||
SAMLEncryptionAlgorithms(String keycloakIdentifier, String... xmlEncIdentifier) {
|
||||
assert xmlEncIdentifier.length > 0 : "xmlEncIdentifier should contain at least one identifier";
|
||||
this.xmlEncIdentifier = xmlEncIdentifier;
|
||||
this.keycloakIdentifier = keycloakIdentifier;
|
||||
}
|
||||
|
||||
public String getXmlEncIdentifier() {
|
||||
/**
|
||||
* Getter for all the XML encoding identifiers.
|
||||
* There should be at least one.
|
||||
* @return The array of XML encoding identifiers
|
||||
*/
|
||||
public String[] getXmlEncIdentifiers() {
|
||||
return xmlEncIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the keycloak identifier.
|
||||
* @return The keycloak identifier.
|
||||
*/
|
||||
public String getKeycloakIdentifier() {
|
||||
return keycloakIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SAMLEncryptionAlgorithms that contains the xml enc identifier.
|
||||
* @param xmlEncIdentifier The Xml encoding identifier
|
||||
* @return The associated SAMLEncryptionAlgorithms or null
|
||||
*/
|
||||
public static SAMLEncryptionAlgorithms forXMLEncIdentifier(String xmlEncIdentifier) {
|
||||
return forXMLEncIdentifier.get(xmlEncIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the SAMLEncryptionAlgorithms for the keycloak identifier.
|
||||
* @param keycloakIdentifier The keycloak identifier
|
||||
* @return The associated SAMLEncryptionAlgorithms or null
|
||||
*/
|
||||
public static SAMLEncryptionAlgorithms forKeycloakIdentifier(String keycloakIdentifier) {
|
||||
return forKeycloakIdentifier.get(keycloakIdentifier);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.protocol.saml;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import org.apache.xml.security.encryption.XMLCipher;
|
||||
import org.apache.xml.security.utils.EncryptionConstants;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
||||
import org.keycloak.protocol.saml.SAMLEncryptionAlgorithms;
|
||||
import org.keycloak.saml.SAML2LoginResponseBuilder;
|
||||
import org.keycloak.saml.SAMLRequestParser;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.util.DocumentUtil;
|
||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
||||
import org.keycloak.services.DefaultKeycloakSession;
|
||||
import org.keycloak.services.DefaultKeycloakSessionFactory;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
/**
|
||||
* <p>Simple test class that checks SAML encryption with different algorithms.
|
||||
* No server needed.</p>
|
||||
*
|
||||
* @author rmartinc
|
||||
*/
|
||||
public class SamlEncryptionTest {
|
||||
|
||||
private static final KeyPair rsaKeyPair;
|
||||
|
||||
static {
|
||||
try {
|
||||
KeyPairGenerator rsa = KeyPairGenerator.getInstance("RSA");
|
||||
rsa.initialize(2048);
|
||||
rsaKeyPair = rsa.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
Cipher cipher = null;
|
||||
SecureRandom random = null;
|
||||
try {
|
||||
// Apache santuario 2.2.3 needs to have SHA1PRNG (fixed in 3.0.2)
|
||||
// see: https://issues.apache.org/jira/browse/SANTUARIO-589
|
||||
random = SecureRandom.getInstance("SHA1PRNG");
|
||||
// FIPS mode removes needed ciphers like "RSA/ECB/OAEPPadding"
|
||||
cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
|
||||
} catch (NoSuchAlgorithmException|NoSuchPaddingException e) {
|
||||
// ignore
|
||||
}
|
||||
Assume.assumeNotNull("OAEPPadding not supported", cipher);
|
||||
Assume.assumeNotNull("SHA1PRNG required for Apache santuario xmlsec", random);
|
||||
}
|
||||
|
||||
private void testEncryption(KeyPair pair, String alg, int keySize, String keyWrapAlg, String keyWrapHashMethod, String keyWrapMgf) throws Exception {
|
||||
SAML2LoginResponseBuilder builder = new SAML2LoginResponseBuilder();
|
||||
builder.requestID("requestId")
|
||||
.destination("http://localhost")
|
||||
.issuer("issuer")
|
||||
.assertionExpiration(300)
|
||||
.subjectExpiration(300)
|
||||
.sessionExpiration(300)
|
||||
.requestIssuer("clientId")
|
||||
.authMethod(JBossSAMLURIConstants.AC_UNSPECIFIED.get())
|
||||
.sessionIndex("sessionIndex")
|
||||
.nameIdentifier(JBossSAMLURIConstants.NAMEID_FORMAT_UNSPECIFIED.get(), "nameId");
|
||||
ResponseType samlModel = builder.buildModel();
|
||||
|
||||
KeycloakSession session = new DefaultKeycloakSession(new DefaultKeycloakSessionFactory());
|
||||
JaxrsSAML2BindingBuilder bindingBuilder = new JaxrsSAML2BindingBuilder(session);
|
||||
if (alg != null) {
|
||||
bindingBuilder.encryptionAlgorithm(alg);
|
||||
}
|
||||
if (keySize > 0) {
|
||||
bindingBuilder.encryptionKeySize(keySize);
|
||||
}
|
||||
if (keyWrapAlg != null) {
|
||||
bindingBuilder.keyEncryptionAlgorithm(keyWrapAlg);
|
||||
}
|
||||
if (keyWrapHashMethod != null) {
|
||||
bindingBuilder.keyEncryptionDigestMethod(keyWrapHashMethod);
|
||||
}
|
||||
if (keyWrapMgf != null) {
|
||||
bindingBuilder.keyEncryptionMgfAlgorithm(keyWrapMgf);
|
||||
}
|
||||
bindingBuilder.encrypt(pair.getPublic());
|
||||
Document samlDocument = builder.buildDocument(samlModel);
|
||||
bindingBuilder.postBinding(samlDocument);
|
||||
|
||||
String samlResponse = DocumentUtil.getDocumentAsString(samlDocument);
|
||||
|
||||
SAMLDocumentHolder holder = SAMLRequestParser.parseResponseDocument(samlResponse.getBytes(StandardCharsets.UTF_8));
|
||||
ResponseType responseType = (ResponseType) holder.getSamlObject();
|
||||
Assert.assertTrue("Assertion is not encrypted", AssertionUtil.isAssertionEncrypted(responseType));
|
||||
AssertionType assertion = AssertionUtil.getAssertion(holder, responseType, pair.getPrivate());
|
||||
Assert.assertEquals("issuer", assertion.getIssuer().getValue());
|
||||
MatcherAssert.assertThat(assertion.getSubject().getSubType().getBaseID(), Matchers.instanceOf(NameIDType.class));
|
||||
NameIDType nameId = (NameIDType) assertion.getSubject().getSubType().getBaseID();
|
||||
Assert.assertEquals("nameId", nameId.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefault() throws Exception {
|
||||
testEncryption(rsaKeyPair, null, -1, null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAES256() throws Exception {
|
||||
testEncryption(rsaKeyPair, "AES", 256, null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultKeyWraps() throws Exception {
|
||||
for (SAMLEncryptionAlgorithms alg : SAMLEncryptionAlgorithms.values()) {
|
||||
for (String keyWrapAlg : alg.getXmlEncIdentifiers()) {
|
||||
testEncryption(rsaKeyPair, null, -1, keyWrapAlg, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyWrapsWithSha512() throws Exception {
|
||||
for (SAMLEncryptionAlgorithms alg : SAMLEncryptionAlgorithms.values()) {
|
||||
for (String keyWrapAlg : alg.getXmlEncIdentifiers()) {
|
||||
testEncryption(rsaKeyPair, null, -1, keyWrapAlg, XMLCipher.SHA512, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRsaOaep11WithSha512AndMgfSha512() throws Exception {
|
||||
testEncryption(rsaKeyPair, "AES", 256, XMLCipher.RSA_OAEP_11, XMLCipher.SHA512, EncryptionConstants.MGF1_SHA512);
|
||||
}
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.keycloak.testsuite.broker;
|
||||
|
||||
import org.apache.xml.security.encryption.XMLCipher;
|
||||
import org.apache.xml.security.utils.EncryptionConstants;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
@ -25,7 +27,6 @@ import org.keycloak.common.util.PemUtils;
|
|||
import org.keycloak.crypto.Algorithm;
|
||||
import org.keycloak.crypto.KeyUse;
|
||||
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
|
||||
import org.keycloak.protocol.saml.SAMLEncryptionAlgorithms;
|
||||
import org.keycloak.representations.idm.KeysMetadataRepresentation;
|
||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||
|
@ -75,14 +76,14 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
|
|||
public void testEncryptedElementIsReadable() throws ConfigurationException, ParsingException, ProcessingException {
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation activeEncryptingKey = KeyUtils.findActiveEncryptingKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RSA_OAEP);
|
||||
assertThat(activeEncryptingKey.getProviderId(), equalTo(encProviderId));
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeEncryptingKey.getPublicKey()), SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier(), true);
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeEncryptingKey.getPublicKey()), XMLCipher.RSA_OAEP, null, null, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureKeyEncryptedElementIsNotReadableWithoutDeprecatedMode() throws ConfigurationException, ParsingException, ProcessingException {
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()));
|
||||
assertThat(activeSignatureKey.getProviderId(), equalTo(sigProviderId));
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeSignatureKey.getPublicKey()), SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier(), false);
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeSignatureKey.getPublicKey()), XMLCipher.RSA_OAEP, null, null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -94,7 +95,7 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
|
|||
});
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()));
|
||||
assertThat(activeSignatureKey.getProviderId(), equalTo(sigProviderId));
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeSignatureKey.getPublicKey()), SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier(), true);
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeSignatureKey.getPublicKey()), XMLCipher.RSA_OAEP, null, null, true);
|
||||
} finally {
|
||||
// Clear flag
|
||||
testingClient.server().run(session -> {
|
||||
|
@ -111,13 +112,21 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
|
|||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("Cannot find key created on the previous line"));
|
||||
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(key.getPublicKey()), SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier(), true);
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(key.getPublicKey()), XMLCipher.RSA_v1dot5, null, null, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm);
|
||||
@Test
|
||||
public void testRsaOaepAlgorithm() throws Exception {
|
||||
RealmResource realm = adminClient.realm(bc.consumerRealmName());
|
||||
KeysMetadataRepresentation.KeyMetadataRepresentation key = KeyUtils.findActiveEncryptingKey(realm, Algorithm.RSA_OAEP);
|
||||
assertThat(key.getProviderId(), equalTo(encProviderId));
|
||||
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(key.getPublicKey()), XMLCipher.RSA_OAEP_11, XMLCipher.SHA256, EncryptionConstants.MGF1_SHA256, true);
|
||||
}
|
||||
|
||||
private void sendDocumentWithEncryptedElement(PublicKey publicKey, String keyEncryptionAlgorithm, boolean shouldPass) throws ConfigurationException, ParsingException, ProcessingException {
|
||||
protected abstract SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm, String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm);
|
||||
|
||||
private void sendDocumentWithEncryptedElement(PublicKey publicKey, String keyEncryptionAlgorithm, String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm, boolean shouldPass) throws ConfigurationException, ParsingException, ProcessingException {
|
||||
createRolesForRealm(bc.consumerRealmName());
|
||||
|
||||
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST + ".dot/ted", getConsumerRoot() + "/sales-post/saml", null);
|
||||
|
@ -138,7 +147,7 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
|
|||
.login().user(bc.getUserLogin(), bc.getUserPassword()).build()
|
||||
|
||||
.processSamlResponse(SamlClient.Binding.POST) // Response from producer IdP
|
||||
.transformDocument(encryptDocument(publicKey, keyEncryptionAlgorithm))
|
||||
.transformDocument(encryptDocument(publicKey, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm))
|
||||
.build();
|
||||
|
||||
if (shouldPass) {
|
||||
|
|
|
@ -39,7 +39,7 @@ import static org.keycloak.testsuite.utils.io.IOUtil.setDocElementAttributeValue
|
|||
public class KcSamlEncryptedAssertionTest extends AbstractKcSamlEncryptedElementsTest {
|
||||
|
||||
@Override
|
||||
protected SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm) {
|
||||
protected SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm, String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm) {
|
||||
return document -> { // Replace Assertion with EncryptedAssertion
|
||||
Node assertionElement = document.getDocumentElement()
|
||||
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0);
|
||||
|
@ -69,7 +69,7 @@ public class KcSamlEncryptedAssertionTest extends AbstractKcSamlEncryptedElement
|
|||
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
|
||||
XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
|
||||
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), document, publicKey,
|
||||
secretKey, encryptionKeySize, encryptedAssertionElementQName, true, keyEncryptionAlgorithm);
|
||||
secretKey, encryptionKeySize, encryptedAssertionElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||
} catch (Exception e) {
|
||||
throw new ProcessingException("failed to encrypt", e);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION
|
|||
public class KcSamlEncryptedIdTest extends AbstractKcSamlEncryptedElementsTest {
|
||||
|
||||
@Override
|
||||
protected SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm) {
|
||||
protected SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm, String keyEncryptionDigestMethod, String keyEncryptionMgfAlgorithm) {
|
||||
return document -> { // Replace Subject -> NameID with EncryptedId
|
||||
Node assertionElement = document.getDocumentElement()
|
||||
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0);
|
||||
|
@ -54,7 +54,7 @@ public class KcSamlEncryptedIdTest extends AbstractKcSamlEncryptedElementsTest {
|
|||
|
||||
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
|
||||
XMLEncryptionUtil.encryptElement(nameIdQName, document, publicKey,
|
||||
secretKey, 128, encryptedIdElementQName, true, keyEncryptionAlgorithm);
|
||||
secretKey, 128, encryptedIdElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
|
||||
} catch (Exception e) {
|
||||
throw new ProcessingException("failed to encrypt", e);
|
||||
}
|
||||
|
|
|
@ -30,11 +30,13 @@ import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
|
|||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.xml.crypto.dsig.XMLSignature;
|
||||
|
||||
|
@ -234,7 +236,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
|
|||
Assert.assertNotNull(signingCert);
|
||||
Assert.assertNotNull(encCert);
|
||||
Assert.assertNotEquals(signingCert, encCert);
|
||||
hasEncAlgorithms(spDescriptor, SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier());
|
||||
hasEncAlgorithms(spDescriptor, SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifiers());
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
@ -253,7 +255,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
|
|||
Assert.assertNotNull(signingCert);
|
||||
Assert.assertNotNull(encCert);
|
||||
Assert.assertNotEquals(signingCert, encCert);
|
||||
hasEncAlgorithms(spDescriptor, SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier());
|
||||
hasEncAlgorithms(spDescriptor, SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifiers());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -270,8 +272,8 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
|
|||
.update()) {
|
||||
spDescriptor = getExportedSamlProvider();
|
||||
hasEncAlgorithms(spDescriptor,
|
||||
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier(),
|
||||
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier()
|
||||
Stream.concat(Arrays.stream(SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifiers()),
|
||||
Arrays.stream(SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifiers())).toArray(String[]::new)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -282,7 +284,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
|
|||
.update()) {
|
||||
spDescriptor = getExportedSamlProvider();
|
||||
hasEncAlgorithms(spDescriptor,
|
||||
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier()
|
||||
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifiers()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -292,7 +294,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
|
|||
.update()) {
|
||||
spDescriptor = getExportedSamlProvider();
|
||||
hasEncAlgorithms(spDescriptor,
|
||||
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier()
|
||||
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifiers()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue