Adding support for rsa-oaep for SAML encryption

Closes https://github.com/keycloak/keycloak/issues/19689
This commit is contained in:
rmartinc 2023-04-20 16:59:11 +02:00 committed by Marek Posolda
parent 035fdc4047
commit 04ac3a64ee
10 changed files with 292 additions and 45 deletions

View file

@ -73,6 +73,9 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
protected String encryptionAlgorithm = "AES"; protected String encryptionAlgorithm = "AES";
protected boolean encrypt; protected boolean encrypt;
protected String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE; protected String canonicalizationMethodType = CanonicalizationMethod.EXCLUSIVE;
protected String keyEncryptionAlgorithm;
protected String keyEncryptionDigestMethod;
protected String keyEncryptionMgfAlgorithm;
public T canonicalizationMethod(String method) { public T canonicalizationMethod(String method) {
this.canonicalizationMethodType = method; this.canonicalizationMethodType = method;
@ -136,6 +139,21 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
return (T)this; 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) { public T relayState(String relayState) {
this.relayState = relayState; this.relayState = relayState;
return (T)this; return (T)this;
@ -271,8 +289,8 @@ public class BaseSAML2BindingBuilder<T extends BaseSAML2BindingBuilder> {
// encrypt the Assertion element and replace it with a EncryptedAssertion element. // encrypt the Assertion element and replace it with a EncryptedAssertion element.
XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey, JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), samlDocument, encryptionPublicKey, secretKey, encryptionKeySize,
secretKey, encryptionKeySize, encryptedAssertionElementQName, true); encryptedAssertionElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
} catch (Exception e) { } catch (Exception e) {
throw new ProcessingException("failed to encrypt", e); throw new ProcessingException("failed to encrypt", e);
} }

View file

@ -79,15 +79,17 @@ public class SPMetadataDescriptor {
return entityDescriptor; 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(); KeyDescriptorType keyDescriptor = new KeyDescriptorType();
keyDescriptor.setUse(use); keyDescriptor.setUse(use);
keyDescriptor.setKeyInfo(keyInfo); keyDescriptor.setKeyInfo(keyInfo);
if (algorithm != null) { if (algorithm != null) {
EncryptionMethodType encMethod = new EncryptionMethodType(algorithm); for (String alg : algorithm) {
EncryptionMethodType encMethod = new EncryptionMethodType(alg);
keyDescriptor.addEncryptionMethod(encMethod); keyDescriptor.addEncryptionMethod(encMethod);
} }
}
return keyDescriptor; return keyDescriptor;
} }

View file

@ -99,14 +99,15 @@ public class XMLEncryptionUtil {
* @throws org.keycloak.saml.common.exceptions.ProcessingException * @throws org.keycloak.saml.common.exceptions.ProcessingException
*/ */
private static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey, 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; XMLCipher keyCipher;
try { try {
keyCipher = XMLCipher.getInstance(encryptionUrlForKeyUnwrap); keyCipher = XMLCipher.getInstance(keyEncryptionAlgorithm, null, keyEncryptionDigestMethod);
keyCipher.init(XMLCipher.WRAP_MODE, keyUsedToEncryptSecretKey); keyCipher.init(XMLCipher.WRAP_MODE, keyUsedToEncryptSecretKey);
return keyCipher.encryptKey(document, keyToBeEncrypted); return keyCipher.encryptKey(document, keyToBeEncrypted, keyEncryptionMgfAlgorithm, null);
} catch (XMLEncryptionException e) { } catch (XMLEncryptionException e) {
throw logger.processingError(e); throw logger.processingError(e);
} }
@ -115,7 +116,14 @@ public class XMLEncryptionUtil {
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey, public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey,
int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo) throws ProcessingException { int keySize, QName wrappingElementQName, boolean addEncryptedKeyInKeyInfo) throws ProcessingException {
encryptElement(elementQName, document, publicKey, secretKey, keySize, wrappingElementQName, addEncryptedKeyInKeyInfo, 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 * data
* *
* @param elementQName QName of the element that we like to encrypt * @param elementQName QName of the element that we like to encrypt
* @param document * @param document The document with the element to encrypt
* @param publicKey * @param publicKey The public Key to wrap the secret key
* @param secretKey * @param secretKey The secret key to use for encryption
* @param keySize * @param keySize The size of the public key
* @param wrappingElementQName A QName of an element that will wrap the encrypted element * @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 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 * @throws ProcessingException
*/ */
public static void encryptElement(QName elementQName, Document document, PublicKey publicKey, SecretKey secretKey, 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) if (elementQName == null)
throw logger.nullArgumentError("elementQName"); throw logger.nullArgumentError("elementQName");
if (document == null) if (document == null)
@ -148,7 +160,11 @@ public class XMLEncryptionUtil {
throw logger.domMissingDocElementError(elementQName.toString()); throw logger.domMissingDocElementError(elementQName.toString());
XMLCipher cipher = null; 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); String encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
// Encrypt the Document // Encrypt the Document

View file

@ -397,7 +397,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
throw new RuntimeException(e); 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()); .collect(Collectors.toList());

View file

@ -20,40 +20,73 @@ package org.keycloak.protocol.saml;
import org.apache.xml.security.encryption.XMLCipher; import org.apache.xml.security.encryption.XMLCipher;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import java.util.Arrays; import java.util.Collections;
import java.util.HashMap;
import java.util.Map; 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. * 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. * It is used to make sure we are using keys generated for given algorithm only with that algorithm.
*/ */
public enum SAMLEncryptionAlgorithms { public enum SAMLEncryptionAlgorithms {
RSA_OAEP(XMLCipher.RSA_OAEP, Algorithm.RSA_OAEP), RSA_OAEP(Algorithm.RSA_OAEP, XMLCipher.RSA_OAEP, XMLCipher.RSA_OAEP_11),
RSA1_5(XMLCipher.RSA_v1dot5, Algorithm.RSA1_5); RSA1_5(Algorithm.RSA1_5, XMLCipher.RSA_v1dot5);
private String xmlEncIdentifier; private final String[] xmlEncIdentifier;
private String keycloakIdentifier; private final 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;
private static final Map<String, SAMLEncryptionAlgorithms> forKeycloakIdentifier = Arrays.stream(values()).collect(Collectors.toMap(SAMLEncryptionAlgorithms::getKeycloakIdentifier, Function.identity())); 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.xmlEncIdentifier = xmlEncIdentifier;
this.keycloakIdentifier = keycloakIdentifier; 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; return xmlEncIdentifier;
} }
/**
* Getter for the keycloak identifier.
* @return The keycloak identifier.
*/
public String getKeycloakIdentifier() { public String getKeycloakIdentifier() {
return keycloakIdentifier; 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) { public static SAMLEncryptionAlgorithms forXMLEncIdentifier(String xmlEncIdentifier) {
return forXMLEncIdentifier.get(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) { public static SAMLEncryptionAlgorithms forKeycloakIdentifier(String keycloakIdentifier) {
return forKeycloakIdentifier.get(keycloakIdentifier); return forKeycloakIdentifier.get(keycloakIdentifier);
} }

View file

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

View file

@ -17,6 +17,8 @@
package org.keycloak.testsuite.broker; 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.hamcrest.Matchers;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -25,7 +27,6 @@ import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse; import org.keycloak.crypto.KeyUse;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType; import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.protocol.saml.SAMLEncryptionAlgorithms;
import org.keycloak.representations.idm.KeysMetadataRepresentation; import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException; import org.keycloak.saml.common.exceptions.ConfigurationException;
@ -75,14 +76,14 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
public void testEncryptedElementIsReadable() throws ConfigurationException, ParsingException, ProcessingException { public void testEncryptedElementIsReadable() throws ConfigurationException, ParsingException, ProcessingException {
KeysMetadataRepresentation.KeyMetadataRepresentation activeEncryptingKey = KeyUtils.findActiveEncryptingKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RSA_OAEP); KeysMetadataRepresentation.KeyMetadataRepresentation activeEncryptingKey = KeyUtils.findActiveEncryptingKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RSA_OAEP);
assertThat(activeEncryptingKey.getProviderId(), equalTo(encProviderId)); 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 @Test
public void testSignatureKeyEncryptedElementIsNotReadableWithoutDeprecatedMode() throws ConfigurationException, ParsingException, ProcessingException { public void testSignatureKeyEncryptedElementIsNotReadableWithoutDeprecatedMode() throws ConfigurationException, ParsingException, ProcessingException {
KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName())); KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()));
assertThat(activeSignatureKey.getProviderId(), equalTo(sigProviderId)); 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 @Test
@ -94,7 +95,7 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
}); });
KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName())); KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()));
assertThat(activeSignatureKey.getProviderId(), equalTo(sigProviderId)); 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 { } finally {
// Clear flag // Clear flag
testingClient.server().run(session -> { testingClient.server().run(session -> {
@ -111,13 +112,21 @@ public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBroker
.findFirst() .findFirst()
.orElseThrow(() -> new RuntimeException("Cannot find key created on the previous line")); .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()); createRolesForRealm(bc.consumerRealmName());
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST + ".dot/ted", getConsumerRoot() + "/sales-post/saml", null); 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() .login().user(bc.getUserLogin(), bc.getUserPassword()).build()
.processSamlResponse(SamlClient.Binding.POST) // Response from producer IdP .processSamlResponse(SamlClient.Binding.POST) // Response from producer IdP
.transformDocument(encryptDocument(publicKey, keyEncryptionAlgorithm)) .transformDocument(encryptDocument(publicKey, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm))
.build(); .build();
if (shouldPass) { if (shouldPass) {

View file

@ -39,7 +39,7 @@ import static org.keycloak.testsuite.utils.io.IOUtil.setDocElementAttributeValue
public class KcSamlEncryptedAssertionTest extends AbstractKcSamlEncryptedElementsTest { public class KcSamlEncryptedAssertionTest extends AbstractKcSamlEncryptedElementsTest {
@Override @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 return document -> { // Replace Assertion with EncryptedAssertion
Node assertionElement = document.getDocumentElement() Node assertionElement = document.getDocumentElement()
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0); .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. // encrypt the Assertion element and replace it with a EncryptedAssertion element.
XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(), XMLEncryptionUtil.encryptElement(new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), document, publicKey, JBossSAMLConstants.ASSERTION.get(), samlNSPrefix), document, publicKey,
secretKey, encryptionKeySize, encryptedAssertionElementQName, true, keyEncryptionAlgorithm); secretKey, encryptionKeySize, encryptedAssertionElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
} catch (Exception e) { } catch (Exception e) {
throw new ProcessingException("failed to encrypt", e); throw new ProcessingException("failed to encrypt", e);
} }

View file

@ -23,7 +23,7 @@ import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION
public class KcSamlEncryptedIdTest extends AbstractKcSamlEncryptedElementsTest { public class KcSamlEncryptedIdTest extends AbstractKcSamlEncryptedElementsTest {
@Override @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 return document -> { // Replace Subject -> NameID with EncryptedId
Node assertionElement = document.getDocumentElement() Node assertionElement = document.getDocumentElement()
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0); .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. // encrypt the Assertion element and replace it with a EncryptedAssertion element.
XMLEncryptionUtil.encryptElement(nameIdQName, document, publicKey, XMLEncryptionUtil.encryptElement(nameIdQName, document, publicKey,
secretKey, 128, encryptedIdElementQName, true, keyEncryptionAlgorithm); secretKey, 128, encryptedIdElementQName, true, keyEncryptionAlgorithm, keyEncryptionDigestMethod, keyEncryptionMgfAlgorithm);
} catch (Exception e) { } catch (Exception e) {
throw new ProcessingException("failed to encrypt", e); throw new ProcessingException("failed to encrypt", e);
} }

View file

@ -30,11 +30,13 @@ 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.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.crypto.dsig.XMLSignature; import javax.xml.crypto.dsig.XMLSignature;
@ -234,7 +236,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
Assert.assertNotNull(signingCert); Assert.assertNotNull(signingCert);
Assert.assertNotNull(encCert); Assert.assertNotNull(encCert);
Assert.assertNotEquals(signingCert, 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") // 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(signingCert);
Assert.assertNotNull(encCert); Assert.assertNotNull(encCert);
Assert.assertNotEquals(signingCert, 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()) { .update()) {
spDescriptor = getExportedSamlProvider(); spDescriptor = getExportedSamlProvider();
hasEncAlgorithms(spDescriptor, hasEncAlgorithms(spDescriptor,
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier(), Stream.concat(Arrays.stream(SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifiers()),
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier() Arrays.stream(SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifiers())).toArray(String[]::new)
); );
} }
@ -282,7 +284,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
.update()) { .update()) {
spDescriptor = getExportedSamlProvider(); spDescriptor = getExportedSamlProvider();
hasEncAlgorithms(spDescriptor, hasEncAlgorithms(spDescriptor,
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier() SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifiers()
); );
} }
@ -292,7 +294,7 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
.update()) { .update()) {
spDescriptor = getExportedSamlProvider(); spDescriptor = getExportedSamlProvider();
hasEncAlgorithms(spDescriptor, hasEncAlgorithms(spDescriptor,
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier() SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifiers()
); );
} }
} }