From 04ac3a64eeecffa7fe345f9867ad87a54f9042e8 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Thu, 20 Apr 2023 16:59:11 +0200 Subject: [PATCH] Adding support for rsa-oaep for SAML encryption Closes https://github.com/keycloak/keycloak/issues/19689 --- .../saml/BaseSAML2BindingBuilder.java | 22 ++- .../keycloak/saml/SPMetadataDescriptor.java | 8 +- .../core/util/XMLEncryptionUtil.java | 36 ++-- .../broker/saml/SAMLIdentityProvider.java | 2 +- .../saml/SAMLEncryptionAlgorithms.java | 55 ++++-- .../protocol/saml/SamlEncryptionTest.java | 167 ++++++++++++++++++ .../AbstractKcSamlEncryptedElementsTest.java | 25 ++- .../broker/KcSamlEncryptedAssertionTest.java | 4 +- .../broker/KcSamlEncryptedIdTest.java | 4 +- .../broker/KcSamlSpDescriptorTest.java | 14 +- 10 files changed, 292 insertions(+), 45 deletions(-) create mode 100644 services/src/test/java/org/keycloak/protocol/saml/SamlEncryptionTest.java diff --git a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java index 58203e477e..0493e679a2 100755 --- a/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java +++ b/saml-core/src/main/java/org/keycloak/saml/BaseSAML2BindingBuilder.java @@ -73,6 +73,9 @@ public class 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 { 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 { // 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); } diff --git a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java index 114cd3832d..ff786d6853 100755 --- a/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java +++ b/saml-core/src/main/java/org/keycloak/saml/SPMetadataDescriptor.java @@ -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; diff --git a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java index 54d8fff04b..812ef66ac3 100755 --- a/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java +++ b/saml-core/src/main/java/org/keycloak/saml/processing/core/util/XMLEncryptionUtil.java @@ -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 diff --git a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java index 7bea27e1cc..8d4f604cdc 100755 --- a/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/saml/SAMLIdentityProvider.java @@ -397,7 +397,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider forXMLEncIdentifier = Arrays.stream(values()).collect(Collectors.toMap(SAMLEncryptionAlgorithms::getXmlEncIdentifier, Function.identity())); - private static final Map forKeycloakIdentifier = Arrays.stream(values()).collect(Collectors.toMap(SAMLEncryptionAlgorithms::getKeycloakIdentifier, Function.identity())); + private final String[] xmlEncIdentifier; + private final String keycloakIdentifier; + private static final Map forKeycloakIdentifier; + private static final Map forXMLEncIdentifier; - SAMLEncryptionAlgorithms(String xmlEncIdentifier, String keycloakIdentifier) { + static { + Map forKeycloakIdentifierTmp = new HashMap<>(); + Map 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); } diff --git a/services/src/test/java/org/keycloak/protocol/saml/SamlEncryptionTest.java b/services/src/test/java/org/keycloak/protocol/saml/SamlEncryptionTest.java new file mode 100644 index 0000000000..e6a5c7e22b --- /dev/null +++ b/services/src/test/java/org/keycloak/protocol/saml/SamlEncryptionTest.java @@ -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; + +/** + *

Simple test class that checks SAML encryption with different algorithms. + * No server needed.

+ * + * @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); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcSamlEncryptedElementsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcSamlEncryptedElementsTest.java index 636d959d85..3977c21c1d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcSamlEncryptedElementsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcSamlEncryptedElementsTest.java @@ -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) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedAssertionTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedAssertionTest.java index 087da016d1..ed64674750 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedAssertionTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedAssertionTest.java @@ -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); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedIdTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedIdTest.java index 1397e2421c..d2c9185aa3 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedIdTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlEncryptedIdTest.java @@ -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); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java index 5f99382eb9..09f477f6dd 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlSpDescriptorTest.java @@ -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() ); } }