Use encryption keys rather than sig for crypto in SAML

Closes #13606

Co-authored-by: mhajas <mhajas@redhat.com>
Co-authored-by: hmlnarik <hmlnarik@redhat.com>
This commit is contained in:
laskasn 2022-12-13 12:58:41 +01:00 committed by Michal Hajas
parent 5b626231d9
commit dc8b759c3d
38 changed files with 1035 additions and 350 deletions

View File

@ -533,7 +533,8 @@ public abstract class AbstractSamlAuthenticationHandler implements SamlAuthentic
// We'll need to decrypt it first.
Document encryptedAssertionDocument = DocumentUtil.createDocument();
encryptedAssertionDocument.appendChild(encryptedAssertionDocument.importNode(encryptedAssertion, true));
return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, deployment.getDecryptionKey());
return XMLEncryptionUtil.decryptElementInDocument(encryptedAssertionDocument, data -> Collections.singletonList(deployment.getDecryptionKey()));
}
return DocumentUtil.getElement(responseHolder.getSamlDocument(), new QName(JBossSAMLConstants.ASSERTION.get()));
}

View File

@ -16,8 +16,11 @@
*/
package org.keycloak.crypto;
import org.keycloak.common.crypto.CryptoConstants;
public interface Algorithm {
/* RSA signing algorithms */
String HS256 = "HS256";
String HS384 = "HS384";
String HS512 = "HS512";
@ -31,5 +34,11 @@ public interface Algorithm {
String PS384 = "PS384";
String PS512 = "PS512";
/* RSA Encryption Algorithms */
String RSA1_5 = CryptoConstants.RSA1_5;
String RSA_OAEP = CryptoConstants.RSA_OAEP;
String RSA_OAEP_256 = CryptoConstants.RSA_OAEP_256;
/* AES */
String AES = "AES";
}

View File

@ -19,6 +19,7 @@ package org.keycloak.admin.client.resource;
import org.keycloak.representations.idm.ComponentRepresentation;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@ -59,5 +60,9 @@ public interface ComponentsResource {
@Path("{id}")
ComponentResource component(@PathParam("id") String id);
@Path("{id}")
@DELETE
ComponentResource removeComponent(@PathParam("id") String id);
}

View File

@ -155,6 +155,7 @@ public enum JBossSAMLConstants {
// Attribute names and other constants
ADDRESS("Address"),
ALGORITHM("Algorithm"),
ALLOW_CREATE("AllowCreate"),
ASSERTION_CONSUMER_SERVICE_URL("AssertionConsumerServiceURL"),
ASSERTION_CONSUMER_SERVICE_INDEX("AssertionConsumerServiceIndex"),

View File

@ -30,6 +30,7 @@ import org.keycloak.dom.saml.v2.metadata.IndexedEndpointType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.dom.xmlsec.w3.xmlenc.EncryptionMethodType;
import org.keycloak.saml.processing.core.saml.v2.common.IDGenerator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -43,9 +44,9 @@ import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.PROTOCOL_
*/
public class SPMetadataDescriptor {
public static EntityDescriptorType buildSPdescriptor(URI loginBinding, URI logoutBinding, URI assertionEndpoint, URI logoutEndpoint,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<Element> signingCerts, List<Element> encryptionCerts)
public static EntityDescriptorType buildSPDescriptor(URI loginBinding, URI logoutBinding, URI assertionEndpoint, URI logoutEndpoint,
boolean wantAuthnRequestsSigned, boolean wantAssertionsSigned, boolean wantAssertionsEncrypted,
String entityId, String nameIDPolicyFormat, List<KeyDescriptorType> signingCerts, List<KeyDescriptorType> encryptionCerts)
{
EntityDescriptorType entityDescriptor = new EntityDescriptorType(entityId);
entityDescriptor.setID(IDGenerator.create("ID_"));
@ -57,22 +58,14 @@ public class SPMetadataDescriptor {
spSSODescriptor.addSingleLogoutService(new EndpointType(logoutBinding, logoutEndpoint));
if (wantAuthnRequestsSigned && signingCerts != null) {
for (Element key: signingCerts)
{
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
keyDescriptor.setUse(KeyTypes.SIGNING);
keyDescriptor.setKeyInfo(key);
spSSODescriptor.addKeyDescriptor(keyDescriptor);
for (KeyDescriptorType key: signingCerts) {
spSSODescriptor.addKeyDescriptor(key);
}
}
if (wantAssertionsEncrypted && encryptionCerts != null) {
for (Element key: encryptionCerts)
{
KeyDescriptorType keyDescriptor = new KeyDescriptorType();
keyDescriptor.setUse(KeyTypes.ENCRYPTION);
keyDescriptor.setKeyInfo(key);
spSSODescriptor.addKeyDescriptor(keyDescriptor);
for (KeyDescriptorType key: encryptionCerts) {
spSSODescriptor.addKeyDescriptor(key);
}
}
@ -86,6 +79,19 @@ public class SPMetadataDescriptor {
return entityDescriptor;
}
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);
}
return keyDescriptor;
}
public static Element buildKeyInfoElement(String keyName, String pemEncodedCertificate)
throws javax.xml.parsers.ParserConfigurationException
{

View File

@ -61,7 +61,6 @@ import org.w3c.dom.Node;
import javax.xml.crypto.dsig.XMLSignature;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import java.io.ByteArrayInputStream;
@ -69,7 +68,9 @@ import java.io.ByteArrayOutputStream;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
/**
@ -566,7 +567,7 @@ public class AssertionUtil {
if (privateKey == null) {
throw new ProcessingException("Encryptd assertion and decrypt private key is null");
}
decryptAssertion(holder, responseType, privateKey);
decryptAssertion(responseType, privateKey);
}
return responseType.getAssertions().get(0).getAssertion();
@ -583,25 +584,32 @@ public class AssertionUtil {
return rtChoiceType.getEncryptedAssertion() != null;
}
public static Element decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
return decryptAssertion(responseType, encryptedData -> Collections.singletonList(privateKey));
}
/**
* This method modifies the given responseType, and replaces the encrypted assertion with a decrypted version.
* @param responseType a response containg an encrypted assertion
*
* @param responseType a response containing an encrypted assertion
* @param decryptionKeyLocator locator of keys suitable for decrypting encrypted element
*
* @return the assertion element as it was decrypted. This can be used in signature verification.
*/
public static Element decryptAssertion(SAMLDocumentHolder holder, ResponseType responseType, PrivateKey privateKey) throws ParsingException, ProcessingException, ConfigurationException {
Document doc = holder.getSamlDocument();
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
if (enc == null) {
throw new ProcessingException("No encrypted assertion found.");
}
public static Element decryptAssertion(ResponseType responseType, XMLEncryptionUtil.DecryptionKeyLocator decryptionKeyLocator) throws ParsingException, ProcessingException, ConfigurationException {
Element enc = responseType.getAssertions().stream()
.map(ResponseType.RTChoiceType::getEncryptedAssertion)
.filter(Objects::nonNull)
.findFirst()
.map(EncryptedElementType::getEncryptedElement)
.orElseThrow(() -> new ProcessingException("No encrypted assertion found."));
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
Document newDoc = DocumentUtil.createDocument();
Node importedNode = newDoc.importNode(enc, true);
newDoc.appendChild(importedNode);
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, decryptionKeyLocator);
SAMLParser parser = SAMLParser.getInstance();
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
@ -618,7 +626,14 @@ public class AssertionUtil {
return subTypeElement != null && subTypeElement.getEncryptedID() != null;
}
public static void decryptId(final ResponseType responseType, final PrivateKey privateKey) throws ConfigurationException, ProcessingException, ParsingException {
/**
* This method modifies the given responseType, and replaces the encrypted id with a decrypted version.
*
* @param responseType a response containing an encrypted id
* @param decryptionKeyLocator locator of keys suitable for decrypting encrypted element
*
*/
public static void decryptId(final ResponseType responseType, XMLEncryptionUtil.DecryptionKeyLocator decryptionKeyLocator) throws ConfigurationException, ProcessingException, ParsingException {
final STSubType subTypeElement = getSubTypeElement(responseType);
if(subTypeElement == null) {
return;
@ -631,7 +646,7 @@ public class AssertionUtil {
Document newDoc = DocumentUtil.createDocument();
Node importedNode = newDoc.importNode(encryptedElement, true);
newDoc.appendChild(importedNode);
Element decryptedNameIdElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
Element decryptedNameIdElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, decryptionKeyLocator);
final XMLEventReader xmlEventReader = StaxParserUtil.getXMLEventReader(DocumentUtil.getNodeAsStream(decryptedNameIdElement));
NameIDType nameIDType = SAMLParserUtil.parseNameIDType(xmlEventReader);

View File

@ -40,6 +40,7 @@ import org.keycloak.dom.saml.v2.metadata.RequestedAttributeType;
import org.keycloak.dom.saml.v2.metadata.RoleDescriptorType;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.dom.saml.v2.metadata.SSODescriptorType;
import org.keycloak.dom.xmlsec.w3.xmlenc.EncryptionMethodType;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ProcessingException;
@ -499,10 +500,24 @@ public class SAMLMetadataWriter extends BaseWriter {
Element keyInfo = keyDescriptor.getKeyInfo();
StaxUtil.writeDOMElement(writer, keyInfo);
List<EncryptionMethodType> encryptionMethodTypes = keyDescriptor.getEncryptionMethod();
if (encryptionMethodTypes != null && !encryptionMethodTypes.isEmpty()) {
for (EncryptionMethodType encryptionMethodType : encryptionMethodTypes) {
writeEncryptionMethod(encryptionMethodType);
}
}
StaxUtil.writeEndElement(writer);
StaxUtil.flush(writer);
}
public void writeEncryptionMethod(EncryptionMethodType methodType) throws ProcessingException {
StaxUtil.writeStartElement(writer, METADATA_PREFIX, JBossSAMLConstants.ENCRYPTION_METHOD.get(), JBossSAMLURIConstants.METADATA_NSURI.get());
StaxUtil.writeAttribute(writer, JBossSAMLConstants.ALGORITHM.get(), methodType.getAlgorithm());
StaxUtil.writeEndElement(writer);
}
public void writeAttributeService(EndpointType endpoint) throws ProcessingException {
StaxUtil.writeStartElement(writer, METADATA_PREFIX, JBossSAMLConstants.ATTRIBUTE_SERVICE.get(), JBossSAMLURIConstants.METADATA_NSURI.get());

View File

@ -38,6 +38,7 @@ import javax.xml.namespace.QName;
import java.security.Key;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.List;
import java.util.Objects;
import javax.xml.XMLConstants;
import javax.xml.crypto.dsig.XMLSignature;
@ -52,6 +53,18 @@ import javax.xml.crypto.dsig.XMLSignature;
*/
public class XMLEncryptionUtil {
public interface DecryptionKeyLocator {
/**
* Provides a list of private keys that are suitable for decrypting
* the given {@code encryptedData}.
*
* @param encryptedData data that need to be decrypted
* @return a list of private keys
*/
List<PrivateKey> getKeys(EncryptedData encryptedData);
}
private static final PicketLinkLogger logger = PicketLinkLoggerFactory.getLogger();
static {
@ -86,13 +99,11 @@ public class XMLEncryptionUtil {
* @throws org.keycloak.saml.common.exceptions.ProcessingException
*/
private static EncryptedKey encryptKey(Document document, SecretKey keyToBeEncrypted, PublicKey keyUsedToEncryptSecretKey,
int keySize) throws ProcessingException {
int keySize, String encryptionUrlForKeyUnwrap) throws ProcessingException {
XMLCipher keyCipher;
String pubKeyAlg = keyUsedToEncryptSecretKey.getAlgorithm();
try {
String keyWrapAlgo = getXMLEncryptionURLForKeyUnwrap(pubKeyAlg, keySize);
keyCipher = XMLCipher.getInstance(keyWrapAlgo);
keyCipher = XMLCipher.getInstance(encryptionUrlForKeyUnwrap);
keyCipher.init(XMLCipher.WRAP_MODE, keyUsedToEncryptSecretKey);
return keyCipher.encryptKey(document, keyToBeEncrypted);
@ -101,6 +112,12 @@ 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));
}
/**
* Given an element in a Document, encrypt the element and replace the element in the document with the encrypted
* data
@ -116,7 +133,7 @@ public class XMLEncryptionUtil {
* @throws ProcessingException
*/
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, String encryptionUrlForKeyUnwrap) throws ProcessingException {
if (elementQName == null)
throw logger.nullArgumentError("elementQName");
if (document == null)
@ -131,7 +148,7 @@ public class XMLEncryptionUtil {
throw logger.domMissingDocElementError(elementQName.toString());
XMLCipher cipher = null;
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize);
EncryptedKey encryptedKey = encryptKey(document, secretKey, publicKey, keySize, encryptionUrlForKeyUnwrap);
String encryptionAlgorithm = getXMLEncryptionURL(secretKey.getAlgorithm(), keySize);
// Encrypt the Document
@ -197,14 +214,18 @@ public class XMLEncryptionUtil {
}
/**
* Decrypt an encrypted element inside a document
* Decrypts an encrypted element inside a document. It tries to use all
* keys provided by {@code decryptionKeyLocator} and if it does not
* succeed it throws {@link ProcessingException}.
*
* @param documentWithEncryptedElement
* @param privateKey key need to unwrap the encryption key
* @param documentWithEncryptedElement document containing encrypted element
* @param decryptionKeyLocator decryption key locator
*
* @return the document with the encrypted element replaced by the data element
*
* @throws ProcessingException when decrypting was not successful
*/
public static Element decryptElementInDocument(Document documentWithEncryptedElement, PrivateKey privateKey)
public static Element decryptElementInDocument(Document documentWithEncryptedElement, DecryptionKeyLocator decryptionKeyLocator)
throws ProcessingException {
if (documentWithEncryptedElement == null)
throw logger.nullArgumentError("Input document is null");
@ -242,21 +263,38 @@ public class XMLEncryptionUtil {
Document decryptedDoc = null;
if (encryptedData != null && encryptedKey != null) {
try {
String encAlgoURL = encryptedData.getEncryptionMethod().getAlgorithm();
XMLCipher keyCipher = XMLCipher.getInstance();
keyCipher.init(XMLCipher.UNWRAP_MODE, privateKey);
Key encryptionKey = keyCipher.decryptKey(encryptedKey, encAlgoURL);
cipher = XMLCipher.getInstance();
cipher.init(XMLCipher.DECRYPT_MODE, encryptionKey);
boolean success = false;
final Exception enclosingThrowable = new RuntimeException("Cannot decrypt element in document");
List<PrivateKey> encryptionKeys;
encryptionKeys = decryptionKeyLocator.getKeys(encryptedData);
decryptedDoc = cipher.doFinal(documentWithEncryptedElement, encDataElement);
} catch (Exception e) {
throw logger.processingError(e);
if (encryptionKeys == null || encryptionKeys.isEmpty()) {
throw logger.nullValueError("Key for EncryptedData not found.");
}
for (PrivateKey privateKey : encryptionKeys) {
try {
String encAlgoURL = encryptedData.getEncryptionMethod().getAlgorithm();
XMLCipher keyCipher = XMLCipher.getInstance();
keyCipher.init(XMLCipher.UNWRAP_MODE, privateKey);
Key encryptionKey = keyCipher.decryptKey(encryptedKey, encAlgoURL);
cipher = XMLCipher.getInstance();
cipher.init(XMLCipher.DECRYPT_MODE, encryptionKey);
decryptedDoc = cipher.doFinal(documentWithEncryptedElement, encDataElement);
success = true;
break;
} catch (Exception e) {
enclosingThrowable.addSuppressed(e);
}
}
if (!success) {
throw logger.processingError(enclosingThrowable);
}
}
if(decryptedDoc == null){
if (decryptedDoc == null) {
throw logger.nullValueError("decryptedDoc");
}

View File

@ -218,7 +218,7 @@ public class SAMLParserTest {
assertNotNull(rtChoiceType.getEncryptedAssertion());
PrivateKey privateKey = DerUtils.decodePrivateKey(Base64.decode(PRIVATE_KEY));
AssertionUtil.decryptAssertion(holder, resp, privateKey);
AssertionUtil.decryptAssertion(resp, privateKey);
rtChoiceType = resp.getAssertions().get(0);
assertNotNull(rtChoiceType.getAssertion());

View File

@ -13,6 +13,7 @@ import java.io.InputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.Scanner;
import org.junit.BeforeClass;
@ -86,7 +87,8 @@ public class AssertionUtilTest {
assertNotNull(subType.getEncryptedID());
assertNull(subType.getBaseID());
AssertionUtil.decryptId(responseType, extractPrivateKey());
PrivateKey pk = extractPrivateKey();
AssertionUtil.decryptId(responseType, data -> Collections.singletonList(pk));
assertNull(subType.getEncryptedID());
assertNotNull(subType.getBaseID());

View File

@ -72,7 +72,7 @@ public class DefaultKeyProviders {
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("keyUse", KeyUse.ENC.name());
config.putSingle("algorithm", JWEConstants.RSA_OAEP);
config.putSingle("algorithm", Algorithm.RSA_OAEP);
generated.setConfig(config);
realm.addComponentModel(generated);
@ -123,6 +123,7 @@ public class DefaultKeyProviders {
rsa.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("keyUse", KeyUse.SIG.getSpecName());
config.putSingle("priority", DEFAULT_PRIORITY);
config.putSingle("privateKey", privateKeyPem);
if (certificatePem != null) {
@ -133,6 +134,25 @@ public class DefaultKeyProviders {
realm.addComponentModel(rsa);
}
if (!hasProvider(realm, "rsa-enc")) {
ComponentModel rsaEnc = new ComponentModel();
rsaEnc.setName("rsa-enc");
rsaEnc.setParentId(realm.getId());
rsaEnc.setProviderId("rsa-enc");
rsaEnc.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> configEnc = new MultivaluedHashMap<>();
configEnc.putSingle("keyUse", KeyUse.ENC.getSpecName());
configEnc.putSingle("priority", "100");
configEnc.putSingle("privateKey", privateKeyPem);
if (certificatePem != null) {
configEnc.putSingle("certificate", certificatePem);
}
rsaEnc.setConfig(configEnc);
realm.addComponentModel(rsaEnc);
}
createSecretProvider(realm);
createAesProvider(realm);
}

View File

@ -54,6 +54,7 @@ import org.keycloak.protocol.saml.SamlProtocolUtils;
import org.keycloak.protocol.saml.SamlService;
import org.keycloak.protocol.saml.SamlSessionUtils;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
import org.keycloak.protocol.saml.SAMLDecryptionKeysLocator;
import org.keycloak.saml.SAML2LogoutResponseBuilder;
import org.keycloak.saml.SAMLRequestParser;
import org.keycloak.saml.common.constants.GeneralConstants;
@ -148,6 +149,9 @@ public class SAMLEndpoint {
private final HttpHeaders headers;
public static final String ENCRYPTION_DEPRECATED_MODE_PROPERTY = "keycloak.saml.deprecated.encryption";
private final boolean DEPRECATED_ENCRYPTION = Boolean.getBoolean(ENCRYPTION_DEPRECATED_MODE_PROPERTY);
public SAMLEndpoint(KeycloakSession session, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback, DestinationValidator destinationValidator) {
this.realm = session.getContext().getRealm();
@ -415,7 +419,6 @@ public class SAMLEndpoint {
}
session.getContext().setAuthenticationSession(authSession);
KeyManager.ActiveRsaKey keys = SamlProtocolUtils.getDecryptionKey(session, realm, config);
if (! isSuccessfulSamlResponse(responseType)) {
String statusMessage = responseType.getStatus() == null || responseType.getStatus().getStatusMessage() == null ? Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR : responseType.getStatus().getStatusMessage();
return callback.error(statusMessage);
@ -433,11 +436,22 @@ public class SAMLEndpoint {
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
}
Element assertionElement;
Element assertionElement = null;
if (assertionIsEncrypted) {
// This methods writes the parsed and decrypted assertion back on the responseType parameter:
assertionElement = AssertionUtil.decryptAssertion(holder, responseType, keys.getPrivateKey());
try {
/* This code is deprecated and will be removed in Keycloak 24 */
if (DEPRECATED_ENCRYPTION) {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
assertionElement = AssertionUtil.decryptAssertion(responseType, keys.getPrivateKey());
} else {
/* End of deprecated code */
assertionElement = AssertionUtil.decryptAssertion(responseType, new SAMLDecryptionKeysLocator(session, realm, config.getEncryptionAlgorithm()));
}
} catch (ProcessingException ex) {
logger.warnf(ex, "Not possible to decrypt SAML assertion. Please check realm keys of usage ENC in the realm '%s' and make sure there is a key able to decrypt the assertion encrypted by identity provider '%s'", realm.getName(), config.getAlias());
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
}
} else {
/* We verify the assertion using original document to handle cases where the IdP
includes whitespace and/or newlines inside tags. */
@ -477,9 +491,20 @@ public class SAMLEndpoint {
return ErrorPage.error(session, authSession, Response.Status.BAD_REQUEST, Messages.INVALID_REQUESTER);
}
if(AssertionUtil.isIdEncrypted(responseType)) {
// This methods writes the parsed and decrypted id back on the responseType parameter:
AssertionUtil.decryptId(responseType, keys.getPrivateKey());
if (AssertionUtil.isIdEncrypted(responseType)) {
try {
/* This code is deprecated and will be removed in Keycloak 24 */
if (DEPRECATED_ENCRYPTION) {
KeyManager.ActiveRsaKey keys = session.keys().getActiveRsaKey(realm);
AssertionUtil.decryptId(responseType, data -> Collections.singletonList(keys.getPrivateKey()));
} else {
/* End of deprecated code */
AssertionUtil.decryptId(responseType, new SAMLDecryptionKeysLocator(session, realm, config.getEncryptionAlgorithm()));
}
} catch (ProcessingException ex) {
logger.warnf(ex, "Not possible to decrypt SAML encryptedId. Please check realm keys of usage ENC in the realm '%s' and make sure there is a key able to decrypt the encryptedId encrypted by identity provider '%s'", realm.getName(), config.getAlias());
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
}
}
AssertionType assertion = responseType.getAssertions().get(0).getAssertion();

View File

@ -26,15 +26,15 @@ import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.assertion.SubjectType;
import org.keycloak.dom.saml.v2.metadata.AttributeConsumingServiceType;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.metadata.LocalizedNameType;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
@ -53,6 +53,7 @@ import org.keycloak.protocol.saml.SamlService;
import org.keycloak.protocol.saml.SamlSessionUtils;
import org.keycloak.protocol.saml.mappers.SamlMetadataDescriptorUpdater;
import org.keycloak.protocol.saml.preprocessor.SamlAuthenticationPreprocessor;
import org.keycloak.protocol.saml.SAMLEncryptionAlgorithms;
import org.keycloak.saml.SAML2AuthnRequestBuilder;
import org.keycloak.saml.SAML2LogoutRequestBuilder;
import org.keycloak.saml.SAML2NameIDPolicyBuilder;
@ -96,7 +97,6 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Pedro Igor
@ -363,15 +363,41 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
String nameIDPolicyFormat = getConfig().getNameIDPolicyFormat();
List<Element> signingKeys = streamForExport(session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256), false)
// We export all keys for algorithm RS256, both active and passive so IDP is able to verify signature even
// if a key rotation happens in the meantime
List<KeyDescriptorType> signingKeys = session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256)
.filter(key -> key.getCertificate() != null)
.sorted(SamlService::compareKeys)
.map(key -> {
try {
return SPMetadataDescriptor.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate()));
} catch (ParserConfigurationException e) {
logger.warn("Failed to export SAML SP Metadata!", e);
throw new RuntimeException(e);
}
})
.map(key -> SPMetadataDescriptor.buildKeyDescriptorType(key, KeyTypes.SIGNING, null))
.collect(Collectors.toList());
// See also SamlProtocolUtils.getDecryptionKey
// We export only active ENC keys so IDP uses different key as soon as possible if a key rotation happens
String encAlg = getConfig().getEncryptionAlgorithm();
Stream<KeyWrapper> encryptionKeyWrappers = (encAlg != null && !encAlg.trim().isEmpty())
? session.keys().getKeysStream(realm, KeyUse.ENC, encAlg)
: session.keys().getKeysStream(realm, KeyUse.SIG, Algorithm.RS256);
List<Element> encryptionKeys = streamForExport(encryptionKeyWrappers, true)
List<KeyDescriptorType> encryptionKeys = session.keys().getKeysStream(realm)
.filter(key -> key.getStatus().isActive() && KeyUse.ENC.equals(key.getUse())
&& (encAlg == null || Objects.equals(encAlg, key.getAlgorithmOrDefault()))
&& SAMLEncryptionAlgorithms.forKeycloakIdentifier(key.getAlgorithm()) != null
&& key.getCertificate() != null)
.sorted(SamlService::compareKeys)
.map(key -> {
Element keyInfo;
try {
keyInfo = SPMetadataDescriptor.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate()));
} catch (ParserConfigurationException e) {
logger.warn("Failed to export SAML SP Metadata!", e);
throw new RuntimeException(e);
}
return SPMetadataDescriptor.buildKeyDescriptorType(keyInfo, KeyTypes.ENCRYPTION, SAMLEncryptionAlgorithms.forKeycloakIdentifier(key.getAlgorithm()).getXmlEncIdentifier());
})
.collect(Collectors.toList());
// Prepare the metadata descriptor model
@ -379,7 +405,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPdescriptor(
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPDescriptor(
authnBinding, authnBinding, endpoint, endpoint,
wantAuthnRequestsSigned, wantAssertionsSigned, wantAssertionsEncrypted,
entityId, nameIDPolicyFormat, signingKeys, encryptionKeys);
@ -454,23 +480,6 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
}
}
private Stream<Element> streamForExport(Stream<KeyWrapper> keys, boolean checkActive) {
return keys.filter(Objects::nonNull)
.filter(key -> key.getCertificate() != null)
.filter(key -> !checkActive || key.getStatus() == KeyStatus.ACTIVE)
.sorted(SamlService::compareKeys)
.map(key -> {
try {
Element element = SPMetadataDescriptor
.buildKeyInfoElement(key.getKid(), PemUtils.encodeCertificate(key.getCertificate()));
return element;
} catch (ParserConfigurationException e) {
logger.warn("Failed to export SAML SP Metadata!", e);
throw new RuntimeException(e);
}
});
}
public SignatureAlgorithm getSignatureAlgorithm() {
String alg = getConfig().getSignatureAlgorithm();
if (alg != null) {

View File

@ -21,6 +21,7 @@ import java.util.List;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.KeycloakSession;
@ -68,9 +69,9 @@ public class GeneratedRsaEncKeyProviderFactory extends AbstractGeneratedRsaKeyPr
@Override
protected boolean isSupportedRsaAlgorithm(String algorithm) {
return algorithm.equals(JWEConstants.RSA1_5)
|| algorithm.equals(JWEConstants.RSA_OAEP)
|| algorithm.equals(JWEConstants.RSA_OAEP_256);
return algorithm.equals(Algorithm.RSA1_5)
|| algorithm.equals(Algorithm.RSA_OAEP)
|| algorithm.equals(Algorithm.RSA_OAEP_256);
}
@Override

View File

@ -18,6 +18,7 @@
package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.KeycloakSession;
@ -61,9 +62,9 @@ public class ImportedRsaEncKeyProviderFactory extends AbstractImportedRsaKeyProv
@Override
protected boolean isSupportedRsaAlgorithm(String algorithm) {
return algorithm.equals(JWEConstants.RSA1_5)
|| algorithm.equals(JWEConstants.RSA_OAEP)
|| algorithm.equals(JWEConstants.RSA_OAEP_256);
return algorithm.equals(Algorithm.RSA1_5)
|| algorithm.equals(Algorithm.RSA_OAEP)
|| algorithm.equals(Algorithm.RSA_OAEP_256);
}
@Override

View File

@ -0,0 +1,166 @@
/*
* 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 org.apache.xml.security.encryption.EncryptedData;
import org.apache.xml.security.encryption.EncryptedKey;
import org.apache.xml.security.encryption.EncryptionMethod;
import org.apache.xml.security.exceptions.XMLSecurityException;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.keys.content.KeyName;
import org.keycloak.common.util.DerUtils;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
import java.security.Key;
import java.security.PrivateKey;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* This implementation locates the decryption keys within realm keys.
* It filters realm keys based on algorithm provided within {@link EncryptedData}
*
* Example of encrypted data:
* <pre>
* {@code
* <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element">
* <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
* <ds:KeyInfo>
* <xenc:EncryptedKey>
* <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
* <xenc:CipherData>
* <xenc:CipherValue>
* .....
* </xenc:CipherValue>
* </xenc:CipherData>
* </xenc:EncryptedKey>
* </ds:KeyInfo>
* <xenc:CipherData>
* <xenc:CipherValue>
* ...
* </xenc:CipherValue>
* </xenc:CipherData>
* </xenc:EncryptedData>
* }
* </pre>
*
*/
public class SAMLDecryptionKeysLocator implements XMLEncryptionUtil.DecryptionKeyLocator {
private final KeycloakSession session;
private final RealmModel realm;
private final String requestedAlgorithm;
public SAMLDecryptionKeysLocator(KeycloakSession session, RealmModel realm, String requestedAlgorithm) {
this.session = session;
this.realm = realm;
this.requestedAlgorithm = requestedAlgorithm;
}
private List<String> getKeyNames(KeyInfo keyInfo) {
List<String> keyNames = new LinkedList<>();
try {
for (int i = 0; i < keyInfo.lengthKeyName(); i++) {
KeyName keyName = keyInfo.itemKeyName(i);
if (keyName != null) {
keyNames.add(keyName.getKeyName());
}
}
} catch (XMLSecurityException e) {
throw new IllegalStateException("Cannot load keyNames from document", e);
}
return keyNames;
}
private Predicate<KeyWrapper> hasMatchingAlgorithm(String algorithm) {
SAMLEncryptionAlgorithms usedAlgorithm = SAMLEncryptionAlgorithms.forXMLEncIdentifier(algorithm);
if (usedAlgorithm == null) {
throw new IllegalStateException("Keycloak does not support encryption keys for given algorithm: " + algorithm);
}
return keyWrapper -> Objects.equals(keyWrapper.getAlgorithmOrDefault(), usedAlgorithm.getKeycloakIdentifier());
}
@Override
public List<PrivateKey> getKeys(EncryptedData encryptedData) {
// Check encryptedData contains keyinfo
KeyInfo keyInfo = encryptedData.getKeyInfo();
if (keyInfo == null) {
throw new IllegalStateException("EncryptedData does not contain KeyInfo");
}
Stream<KeyWrapper> keysStream = session.keys().getKeysStream(realm)
.filter(key -> key.getStatus().isEnabled() && KeyUse.ENC.equals(key.getUse()));
if (requestedAlgorithm != null && !requestedAlgorithm.trim().isEmpty()) {
keysStream = keysStream.filter(keyWrapper -> Objects.equals(keyWrapper.getAlgorithmOrDefault(), requestedAlgorithm));
}
// If encryptedData contains keyName we will use only for keys with given kid
if (keyInfo.containsKeyName()) {
List<String> keyNames = getKeyNames(keyInfo);
keysStream = keysStream.filter(keyWrapper -> keyNames.contains(keyWrapper.getKid()));
}
// Look for algorithm used inside encryptedData and allow only keys generated for specific algorithm
try {
EncryptedKey encryptedKey = keyInfo.itemEncryptedKey(0);
if (encryptedKey != null) {
EncryptionMethod encryptionMethod = encryptedKey.getEncryptionMethod();
if (encryptionMethod == null) {
throw new IllegalArgumentException("KeyInfo does not contain encryption method");
}
String algorithm = encryptionMethod.getAlgorithm();
if (algorithm == null) {
throw new IllegalArgumentException("Not able to find algorithm for given encryption method");
}
keysStream = keysStream.filter(hasMatchingAlgorithm(algorithm));
}
} catch (XMLSecurityException e) {
throw new IllegalArgumentException("EncryptedData does not contain KeyInfo ", e);
}
// Map keys to PrivateKey
return keysStream
.map(KeyWrapper::getPrivateKey)
.map(Key::getEncoded)
.map(encoded -> {
try {
return DerUtils.decodePrivateKey(encoded);
} catch (Exception e) {
throw new RuntimeException("Could not decode private key.", e);
}
})
.collect(Collectors.toList());
}
}

View File

@ -0,0 +1,60 @@
/*
* 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 org.apache.xml.security.encryption.XMLCipher;
import org.keycloak.crypto.Algorithm;
import java.util.Arrays;
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);
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()));
SAMLEncryptionAlgorithms(String xmlEncIdentifier, String keycloakIdentifier) {
this.xmlEncIdentifier = xmlEncIdentifier;
this.keycloakIdentifier = keycloakIdentifier;
}
public String getXmlEncIdentifier() {
return xmlEncIdentifier;
}
public String getKeycloakIdentifier() {
return keycloakIdentifier;
}
public static SAMLEncryptionAlgorithms forXMLEncIdentifier(String xmlEncIdentifier) {
return forXMLEncIdentifier.get(xmlEncIdentifier);
}
public static SAMLEncryptionAlgorithms forKeycloakIdentifier(String keycloakIdentifier) {
return forKeycloakIdentifier.get(keycloakIdentifier);
}
}

View File

@ -25,6 +25,9 @@ import org.jboss.logging.Logger;
import org.keycloak.broker.saml.SAMLDataMarshaller;
import org.keycloak.common.util.KeycloakUriBuilder;
import org.keycloak.connections.httpclient.HttpClientProvider;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.dom.saml.v2.SAML2Object;
import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
@ -85,6 +88,7 @@ import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.net.URI;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
@ -466,9 +470,9 @@ public class SamlProtocol implements LoginProtocol {
Document samlDocument = null;
ResponseType samlModel = null;
KeyManager keyManager = session.keys();
KeyManager.ActiveRsaKey keys = keyManager.getActiveRsaKey(realm);
KeyWrapper keyPair = keyManager.getActiveKey(realm, KeyUse.SIG, Algorithm.RS256);
boolean postBinding = isPostBinding(authSession);
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keys.getKid(), keys.getCertificate());
String keyName = samlClient.getXmlSigKeyInfoKeyNameTransformer().getKeyName(keyPair.getKid(), keyPair.getCertificate());
String nameId = getSAMLNameId(samlNameIdMappers, nameIdFormat, session, userSession, clientSession);
if (nameId == null) {
@ -522,7 +526,7 @@ public class SamlProtocol implements LoginProtocol {
if (canonicalization != null) {
bindingBuilder.canonicalizationMethod(canonicalization);
}
bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, keys.getPrivateKey(), keys.getPublicKey(), keys.getCertificate());
bindingBuilder.signatureAlgorithm(samlClient.getSignatureAlgorithm()).signWith(keyName, (PrivateKey) keyPair.getPrivateKey(), (PublicKey) keyPair.getPublicKey(), keyPair.getCertificate());
if (samlClient.requiresRealmSignature()) bindingBuilder.signDocument();
if (samlClient.requiresAssertionSignature()) bindingBuilder.signAssertions();

View File

@ -23,19 +23,13 @@ import java.net.URI;
import java.security.Key;
import org.jboss.logging.Logger;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.PemUtils;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.dom.saml.v2.protocol.ArtifactResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusCodeType;
import org.keycloak.dom.saml.v2.protocol.StatusType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeyManager;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.saml.SignatureAlgorithm;
import org.keycloak.saml.common.constants.GeneralConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
@ -132,22 +126,6 @@ public class SamlProtocolUtils {
return getPublicKey(client, SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE);
}
/**
* Returns private key used to decrypt SAML assertions encrypted by 3rd party SAML IDP
*/
public static KeyManager.ActiveRsaKey getDecryptionKey(KeycloakSession session, RealmModel realm, SAMLIdentityProviderConfig idpConfig) {
String encryptionAlgorithm = idpConfig.getEncryptionAlgorithm();
if (encryptionAlgorithm != null && !encryptionAlgorithm.trim().isEmpty()) {
KeyWrapper kw = session.keys().getActiveKey(realm, KeyUse.ENC, encryptionAlgorithm);
return new KeyManager.ActiveRsaKey(kw);
} else {
// Backwards compatibility. Fallback to return default realm key (which is signature key, even if we're not signing anything, but decrypting stuff)
logger.debugf("Fallback to use default realm RSA key as a key for decrypt SAML documents. It is recommended to configure 'Encryption algorithm' on SAML IDP '%s' and configure encryption key of this algorithm in realm '%s'",
idpConfig.getAlias(), realm.getName());
return session.keys().getActiveRsaKey(realm);
}
}
public static PublicKey getPublicKey(ClientModel client, String attribute) throws VerificationException {
String certPem = client.getAttribute(attribute);
return getPublicKey(certPem);

View File

@ -20,6 +20,8 @@ package org.keycloak.protocol.saml.installation;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -32,11 +34,9 @@ import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.util.StaxUtil;
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLMetadataWriter;
import org.w3c.dom.Element;
import java.io.StringWriter;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.xml.stream.XMLStreamWriter;
@ -92,17 +92,24 @@ public class SamlSPDescriptorClientInstallation implements ClientInstallationPro
String nameIdFormat = samlClient.getNameIDFormat();
if (nameIdFormat == null) nameIdFormat = SamlProtocol.SAML_DEFAULT_NAMEID_FORMAT;
Element spCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientSigningCertificate());
Element encCertificate = SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientEncryptingCertificate());
KeyDescriptorType spCertificate = SPMetadataDescriptor.buildKeyDescriptorType(
SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientSigningCertificate()),
KeyTypes.SIGNING,
null);
KeyDescriptorType encCertificate = SPMetadataDescriptor.buildKeyDescriptorType(
SPMetadataDescriptor.buildKeyInfoElement(null, samlClient.getClientEncryptingCertificate()),
KeyTypes.ENCRYPTION,
null);
StringWriter sw = new StringWriter();
XMLStreamWriter writer = StaxUtil.getXMLStreamWriter(sw);
SAMLMetadataWriter metadataWriter = new SAMLMetadataWriter(writer);
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPdescriptor(
EntityDescriptorType entityDescriptor = SPMetadataDescriptor.buildSPDescriptor(
loginBinding, logoutBinding, new URI(assertionUrl), new URI(logoutUrl),
samlClient.requiresClientSignature(), samlClient.requiresAssertionSignature(), samlClient.requiresEncryption(),
client.getClientId(), nameIdFormat, Arrays.asList(spCertificate), Arrays.asList(encCertificate));
client.getClientId(), nameIdFormat, Collections.singletonList(spCertificate), Collections.singletonList(encCertificate));
metadataWriter.writeEntityDescriptor(entityDescriptor);

View File

@ -23,13 +23,10 @@ import org.keycloak.admin.client.resource.ClientScopeResource;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.admin.client.resource.RoleResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -43,6 +40,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD;
/**
@ -269,23 +267,4 @@ public class ApiUtil {
return null;
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveSigningKey(RealmResource realm) {
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getPublicKey() != null && KeyStatus.valueOf(rep.getStatus()).isActive() && KeyUse.SIG.equals(rep.getUse())) {
return rep;
}
}
return null;
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveSigningKey(RealmResource realm, String alg) {
KeysMetadataRepresentation keyMetadata = realm.keys().getKeyMetadata();
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getPublicKey() != null && KeyStatus.valueOf(rep.getStatus()).isActive() && KeyUse.SIG.equals(rep.getUse()) && alg.equals(rep.getAlgorithm())) {
return rep;
}
}
return null;
}
}

View File

@ -1,11 +1,17 @@
package org.keycloak.testsuite.util;
import org.keycloak.admin.client.resource.RealmResource;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.keys.KeyProvider;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.testsuite.admin.ApiUtil;
import javax.ws.rs.core.Response;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
@ -15,8 +21,10 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
@ -44,16 +52,7 @@ public class KeyUtils {
}
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveSigningKey(KeysMetadataRepresentation keys, String algorithm) {
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getAlgorithm().equals(algorithm) && KeyStatus.valueOf(k.getStatus()).isActive() && KeyUse.SIG.equals(k.getUse())) {
return k;
}
}
throw new RuntimeException("Active key not found");
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveEncKey(KeysMetadataRepresentation keys, String algorithm) {
public static KeysMetadataRepresentation.KeyMetadataRepresentation getActiveEncryptionKey(KeysMetadataRepresentation keys, String algorithm) {
for (KeysMetadataRepresentation.KeyMetadataRepresentation k : keys.getKeys()) {
if (k.getAlgorithm().equals(algorithm) && KeyStatus.valueOf(k.getStatus()).isActive() && KeyUse.ENC.equals(k.getUse())) {
return k;
@ -62,6 +61,51 @@ public class KeyUtils {
throw new RuntimeException("Active key not found");
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveSigningKey(RealmResource realm) {
return findRealmKeys(realm, rep -> rep.getPublicKey() != null && KeyStatus.valueOf(rep.getStatus()).isActive() && KeyUse.SIG.equals(rep.getUse()))
.findFirst()
.orElse(null);
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveSigningKey(RealmResource realm, String alg) {
return findRealmKeys(realm, rep -> rep.getPublicKey() != null && KeyStatus.valueOf(rep.getStatus()).isActive() && KeyUse.SIG.equals(rep.getUse()) && alg.equals(rep.getAlgorithm()))
.findFirst()
.orElse(null);
}
public static KeysMetadataRepresentation.KeyMetadataRepresentation findActiveEncryptingKey(RealmResource realm, String alg) {
return findRealmKeys(realm, rep -> rep.getPublicKey() != null && KeyStatus.valueOf(rep.getStatus()).isActive() && KeyUse.ENC.equals(rep.getUse()) && alg.equals(rep.getAlgorithm()))
.findFirst()
.orElse(null);
}
public static Stream<KeysMetadataRepresentation.KeyMetadataRepresentation> findRealmKeys(RealmResource realm, Predicate<KeysMetadataRepresentation.KeyMetadataRepresentation> filter) {
return realm.keys().getKeyMetadata().getKeys().stream().filter(filter);
}
public static AutoCloseable generateNewRealmKey(RealmResource realm, KeyUse keyUse, String algorithm, String priority) {
String realmId = realm.toRepresentation().getId();
ComponentRepresentation keys = new ComponentRepresentation();
keys.setName("generated");
keys.setProviderType(KeyProvider.class.getName());
keys.setProviderId(keyUse == KeyUse.ENC ? "rsa-enc-generated" : "rsa-generated");
keys.setParentId(realmId);
keys.setConfig(new MultivaluedHashMap<>());
keys.getConfig().putSingle("priority", priority);
keys.getConfig().putSingle("keyUse", KeyUse.ENC.getSpecName());
keys.getConfig().putSingle("algorithm", algorithm);
Response response = realm.components().add(keys);
assertEquals(201, response.getStatus());
String id = ApiUtil.getCreatedId(response);
response.close();
return () -> realm.components().removeComponent(id);
}
public static AutoCloseable generateNewRealmKey(RealmResource realm, KeyUse keyUse, String algorithm) {
return generateNewRealmKey(realm, keyUse, algorithm, "100");
}
/**
* @return key sizes, which are expected to be supported by Keycloak server for {@link org.keycloak.keys.GeneratedRsaKeyProviderFactory} and {@link org.keycloak.keys.GeneratedRsaEncKeyProviderFactory}.
*/

View File

@ -36,16 +36,15 @@ import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.StreamUtil;
import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.keys.KeyProvider;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.adapters.action.GlobalRequestResult;
import org.keycloak.representations.idm.ClientRepresentation;
@ -59,6 +58,7 @@ import org.keycloak.testsuite.adapter.page.SecurePortal;
import org.keycloak.testsuite.adapter.page.TokenMinTTLPage;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.utils.arquillian.ContainerConstants;
import org.keycloak.testsuite.util.URLAssert;
import org.openqa.selenium.By;
@ -126,8 +126,9 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
// Logout
ApiUtil.findUserByUsernameId(adminClient.realm("demo"), "bburke@redhat.com").logout();
// Generate new realm key
generateNewRealmKey();
// Generate new realm keys
generateNewRealmKey(KeyUse.SIG);
generateNewRealmKey(KeyUse.ENC);
// Try to login again. It should fail now because not yet allowed to download new keys
tokenMinTTLPage.navigateTo();
@ -189,6 +190,9 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
// KEYCLOAK-3824: Test for public-key-cache-ttl
@Test
public void testPublicKeyCacheTtl() {
String customerDBUnsecuredUrl = customerDb.getUriBuilder().clone().path("unsecured").path("foo").build().toASCIIString();
String tokenMinTTLUnsecuredUrl = tokenMinTTLPage.getUriBuilder().clone().path("unsecured").path("foo").build().toASCIIString();
// increase accessTokenLifespan to 1200
RealmRepresentation demoRealm = adminClient.realm(DEMO).toRepresentation();
demoRealm.setAccessTokenLifespan(1200);
@ -202,9 +206,12 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
int status = invokeRESTEndpoint(accessTokenString);
Assert.assertEquals(200, status);
// Re-generate realm public key and remove the old key
String oldActiveKeyProviderId = getActiveKeyProvider();
generateNewRealmKey();
// Re-generate realm public key and remove the old key (for both sig and enc)
String oldActiveKeyProviderId = getActiveKeyProviderId(KeyUse.SIG);
generateNewRealmKey(KeyUse.SIG);
adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
oldActiveKeyProviderId = getActiveKeyProviderId(KeyUse.ENC);
generateNewRealmKey(KeyUse.ENC);
adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
// Send REST request to the customer-db app. Should be still succcessfully authenticated as the JWKPublicKeyLocator cache is still valid
@ -212,15 +219,15 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
Assert.assertEquals(200, status);
// TimeOffset to 900 on the REST app side. Token is still valid (1200) but JWKPublicKeyLocator should try to download new key (public-key-cache-ttl=600)
setAdapterAndServerTimeOffset(900, customerDb.toString() + "/unsecured/foo");
setAdapterAndServerTimeOffset(900, customerDBUnsecuredUrl, tokenMinTTLUnsecuredUrl);
// Send REST request. New request to the publicKey cache should be sent, and key is no longer returned as token contains the old kid
status = invokeRESTEndpoint(accessTokenString);
Assert.assertEquals(401, status);
// Revert public keys change and time offset
resetKeycloakDeploymentForAdapter(customerDb.toString() + "/unsecured/foo");
resetKeycloakDeploymentForAdapter(tokenMinTTLPage.toString() + "/unsecured/foo");
resetKeycloakDeploymentForAdapter(customerDBUnsecuredUrl);
resetKeycloakDeploymentForAdapter(tokenMinTTLUnsecuredUrl);
}
@ -243,16 +250,18 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
String accessTokenString = tokenMinTTLPage.getAccessTokenString();
// Generate new realm public key
String oldActiveKeyProviderId = getActiveKeyProvider();
generateNewRealmKey();
String oldActiveSigKeyProviderId = getActiveKeyProviderId(KeyUse.SIG);
generateNewRealmKey(KeyUse.SIG);
String oldActiveEncKeyProviderId = getActiveKeyProviderId(KeyUse.ENC);
generateNewRealmKey(KeyUse.ENC);
// Send REST request to customer-db app. It should be successfully authenticated even that token is signed by the old key
int status = invokeRESTEndpoint(accessTokenString);
Assert.assertEquals(200, status);
// Remove the old realm key now
adminClient.realm(DEMO).components().component(oldActiveKeyProviderId).remove();
// Remove the old realm keys now
adminClient.realm(DEMO).components().component(oldActiveSigKeyProviderId).remove();
adminClient.realm(DEMO).components().component(oldActiveEncKeyProviderId).remove();
// Set some offset to ensure pushing notBefore will pass
setAdapterAndServerTimeOffset(130, customerDBUnsecuredUrl, tokenMinTTLUnsecuredUrl);
@ -287,30 +296,28 @@ public class OIDCPublicKeyRotationAdapterTest extends AbstractServletsAdapterTes
}
private void generateNewRealmKey() {
private void generateNewRealmKey(KeyUse keyUse) {
String realmId = adminClient.realm(DEMO).toRepresentation().getId();
ComponentRepresentation keys = new ComponentRepresentation();
keys.setName("generated");
keys.setProviderType(KeyProvider.class.getName());
keys.setProviderId("rsa-generated");
keys.setProviderId(keyUse == KeyUse.SIG ? "rsa-generated" : "rsa-enc-generated");
keys.setParentId(realmId);
keys.setConfig(new MultivaluedHashMap<>());
keys.getConfig().putSingle("priority", "150");
keys.getConfig().putSingle("keyUse", keyUse.getSpecName());
Response response = adminClient.realm(DEMO).components().add(keys);
assertEquals(201, response.getStatus());
response.close();
}
private String getActiveKeyProvider() {
KeysMetadataRepresentation keyMetadata = adminClient.realm(DEMO).keys().getKeyMetadata();
String activeKid = keyMetadata.getActive().get(Algorithm.RS256);
for (KeysMetadataRepresentation.KeyMetadataRepresentation rep : keyMetadata.getKeys()) {
if (rep.getKid().equals(activeKid)) {
return rep.getProviderId();
}
}
return null;
private String getActiveKeyProviderId(KeyUse keyUse) {
KeysMetadataRepresentation.KeyMetadataRepresentation key = keyUse == KeyUse.ENC
? KeyUtils.findActiveEncryptingKey(adminClient.realm(DEMO), Algorithm.RSA_OAEP)
: KeyUtils.findActiveSigningKey(adminClient.realm(DEMO), Algorithm.RS256);
return key != null ? key.getProviderId() : null;
}
private int invokeRESTEndpoint(String accessTokenString) {

View File

@ -35,9 +35,9 @@ import org.keycloak.protocol.saml.SamlProtocol;
import org.keycloak.protocol.saml.installation.SamlSPDescriptorClientInstallation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.KeyUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
@ -168,7 +168,7 @@ public class InstallationTest extends AbstractClientTest {
private void assertOidcInstallationConfig(String config) {
assertThat(config, containsString("test"));
assertThat(config, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getPublicKey())));
assertThat(config, not(containsString(KeyUtils.findActiveSigningKey(testRealmResource()).getPublicKey())));
assertThat(config, containsString(authServerUrl()));
}
@ -182,7 +182,7 @@ public class InstallationTest extends AbstractClientTest {
String xml = samlClient.getInstallationProvider("keycloak-saml");
assertThat(xml, containsString("<keycloak-saml-adapter>"));
assertThat(xml, containsString("SPECIFY YOUR entityID!"));
assertThat(xml, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getCertificate())));
assertThat(xml, not(containsString(KeyUtils.findActiveSigningKey(testRealmResource()).getCertificate())));
assertThat(xml, containsString(samlUrl()));
}
@ -191,7 +191,7 @@ public class InstallationTest extends AbstractClientTest {
String cli = samlClient.getInstallationProvider("keycloak-saml-subsystem-cli");
assertThat(cli, containsString("/subsystem=keycloak-saml/secure-deployment=YOUR-WAR.war/"));
assertThat(cli, containsString("SPECIFY YOUR entityID!"));
assertThat(cli, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getCertificate())));
assertThat(cli, not(containsString(KeyUtils.findActiveSigningKey(testRealmResource()).getCertificate())));
assertThat(cli, containsString(samlUrl()));
}
@ -209,7 +209,7 @@ public class InstallationTest extends AbstractClientTest {
String xml = samlClient.getInstallationProvider("keycloak-saml-subsystem");
assertThat(xml, containsString("<secure-deployment"));
assertThat(xml, containsString("SPECIFY YOUR entityID!"));
assertThat(xml, not(containsString(ApiUtil.findActiveSigningKey(testRealmResource()).getCertificate())));
assertThat(xml, not(containsString(KeyUtils.findActiveSigningKey(testRealmResource()).getCertificate())));
assertThat(xml, containsString(samlUrl()));
}

View File

@ -37,6 +37,7 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.AdminEventPaths;
import org.keycloak.testsuite.util.AssertAdminEvents;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.OAuthClient.AccessTokenResponse;
import java.security.PublicKey;
@ -71,7 +72,7 @@ public abstract class AbstractGroupTest extends AbstractKeycloakTest {
String accessToken = tokenResponse.getAccessToken();
String refreshToken = tokenResponse.getRefreshToken();
PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveSigningKey(adminClient.realm("test")).getPublicKey());
PublicKey publicKey = PemUtils.decodePublicKey(KeyUtils.findActiveSigningKey(adminClient.realm("test")).getPublicKey());
AccessToken accessTokenRepresentation = RSATokenVerifier.verifyToken(accessToken, publicKey, getAuthServerContextRoot() + "/auth/realms/test");

View File

@ -0,0 +1,165 @@
/*
* 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.testsuite.broker;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.admin.client.resource.RealmResource;
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;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClientBuilder;
import org.keycloak.testsuite.util.saml.SamlDocumentStepBuilder;
import org.w3c.dom.Document;
import javax.ws.rs.core.Response;
import java.security.PublicKey;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.keycloak.broker.saml.SAMLEndpoint.ENCRYPTION_DEPRECATED_MODE_PROPERTY;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
import static org.keycloak.testsuite.saml.AbstractSamlTest.SAML_CLIENT_ID_SALES_POST;
import static org.keycloak.testsuite.util.Matchers.isSamlResponse;
import static org.keycloak.testsuite.util.Matchers.statusCodeIsHC;
public abstract class AbstractKcSamlEncryptedElementsTest extends AbstractBrokerTest {
private String encProviderId;
private String sigProviderId;
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return KcSamlBrokerConfiguration.INSTANCE;
}
@Before
public void setupKeys() {
sigProviderId = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName())).getProviderId();
encProviderId = KeyUtils.findActiveEncryptingKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RSA_OAEP).getProviderId();
assertThat(sigProviderId, not(equalTo(encProviderId)));
}
@Test
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);
}
@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);
}
@Test
public void testEncryptedElementIsReadableInDeprecatedMode() throws ConfigurationException, ParsingException, ProcessingException {
try {
// Set flag that enabled deprecated mode for encryption
testingClient.server().run(session -> {
System.setProperty(ENCRYPTION_DEPRECATED_MODE_PROPERTY, "true");
});
KeysMetadataRepresentation.KeyMetadataRepresentation activeSignatureKey = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()));
assertThat(activeSignatureKey.getProviderId(), equalTo(sigProviderId));
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(activeSignatureKey.getPublicKey()), SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier(), true);
} finally {
// Clear flag
testingClient.server().run(session -> {
System.clearProperty(ENCRYPTION_DEPRECATED_MODE_PROPERTY);
});
}
}
@Test
public void testUseDifferentEncryptionAlgorithm() throws Exception {
RealmResource realm = adminClient.realm(bc.consumerRealmName());
try (AutoCloseable ac = KeyUtils.generateNewRealmKey(realm, KeyUse.ENC, Algorithm.RSA1_5)) {
KeysMetadataRepresentation.KeyMetadataRepresentation key = KeyUtils.findRealmKeys(realm, k -> k.getAlgorithm().equals(Algorithm.RSA1_5))
.findFirst()
.orElseThrow(() -> new RuntimeException("Cannot find key created on the previous line"));
sendDocumentWithEncryptedElement(PemUtils.decodePublicKey(key.getPublicKey()), SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier(), true);
}
}
protected abstract SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm);
private void sendDocumentWithEncryptedElement(PublicKey publicKey, String keyEncryptionAlgorithm, 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);
Document doc = SAML2Request.convert(loginRep);
final AtomicReference<String> username = new AtomicReference<>();
assertThat(adminClient.realm(bc.consumerRealmName()).users().search(username.get()), hasSize(0));
SamlClientBuilder samlClientBuilder = new SamlClientBuilder()
.authnRequest(getConsumerSamlEndpoint(bc.consumerRealmName()), doc, SamlClient.Binding.POST).build() // Request to consumer IdP
.login().idp(bc.getIDPAlias()).build()
.processSamlResponse(SamlClient.Binding.POST) // AuthnRequest to producer IdP
.targetAttributeSamlRequest()
.build()
.login().user(bc.getUserLogin(), bc.getUserPassword()).build()
.processSamlResponse(SamlClient.Binding.POST) // Response from producer IdP
.transformDocument(encryptDocument(publicKey, keyEncryptionAlgorithm))
.build();
if (shouldPass) {
// first-broker flow
SAMLDocumentHolder samlResponse =
samlClientBuilder.updateProfile().firstName("a").lastName("b").email(bc.getUserEmail()).build()
.followOneRedirect()
.getSamlResponse(SamlClient.Binding.POST); // Response from consumer IdP
assertThat(samlResponse, Matchers.notNullValue());
assertThat(samlResponse.getSamlObject(), isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(adminClient.realm(bc.consumerRealmName()).users().search(username.get()), hasSize(1));
} else {
samlClientBuilder.executeAndTransform(response -> {
assertThat(response, statusCodeIsHC(Response.Status.BAD_REQUEST));
return null;
});
}
}
}

View File

@ -43,12 +43,9 @@ import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.ProfileAssume;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.client.resources.TestingCacheResource;
import org.keycloak.testsuite.updaters.ClientAttributeUpdater;
import org.keycloak.testsuite.updaters.RealmAttributeUpdater;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.WaitUtils;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@ -150,7 +147,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
cfg.setValidateSignature(true);
cfg.setUseJwksUrl(false);
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveSigningKey(providerRealm(), Algorithm.RS256);
KeysMetadataRepresentation.KeyMetadataRepresentation key = org.keycloak.testsuite.util.KeyUtils.findActiveSigningKey(providerRealm(), Algorithm.RS256);
cfg.setPublicKeySignatureVerifier(key.getPublicKey());
updateIdentityProvider(idpRep);
@ -185,7 +182,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
rotateKeys(Algorithm.ES256, "ecdsa-generated");
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveSigningKey(providerRealm(), Algorithm.ES256);
KeysMetadataRepresentation.KeyMetadataRepresentation key = org.keycloak.testsuite.util.KeyUtils.findActiveSigningKey(providerRealm(), Algorithm.ES256);
cfg.setPublicKeySignatureVerifier(key.getPublicKey());
updateIdentityProvider(idpRep);
@ -213,7 +210,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
rotateKeys(Algorithm.PS512, "rsa-generated");
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveSigningKey(providerRealm(), Algorithm.PS512);
KeysMetadataRepresentation.KeyMetadataRepresentation key = org.keycloak.testsuite.util.KeyUtils.findActiveSigningKey(providerRealm(), Algorithm.PS512);
cfg.setPublicKeySignatureVerifier(key.getPublicKey());
updateIdentityProvider(idpRep);
@ -267,7 +264,7 @@ public class KcOIDCBrokerWithSignatureTest extends AbstractBaseBrokerTest {
cfg.setValidateSignature(true);
cfg.setUseJwksUrl(false);
KeysMetadataRepresentation.KeyMetadataRepresentation key = ApiUtil.findActiveSigningKey(providerRealm(), Algorithm.RS256);
KeysMetadataRepresentation.KeyMetadataRepresentation key = org.keycloak.testsuite.util.KeyUtils.findActiveSigningKey(providerRealm(), Algorithm.RS256);
String pemData = key.getPublicKey();
cfg.setPublicKeySignatureVerifier(pemData);
String expectedKeyId = KeyUtils.createKeyId(PemUtils.decodePublicKey(pemData));

View File

@ -46,7 +46,7 @@ public class KcOidcBrokerPrivateKeyJwtTest extends AbstractBrokerTest {
public List<ClientRepresentation> createProviderClients() {
List<ClientRepresentation> clientsRepList = super.createProviderClients();
log.info("Update provider clients to accept JWT authentication");
KeyMetadataRepresentation keyRep = KeyUtils.getActiveSigningKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256);
KeyMetadataRepresentation keyRep = KeyUtils.findActiveSigningKey(adminClient.realm(consumerRealmName()), Algorithm.RS256);
for (ClientRepresentation client: clientsRepList) {
client.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
if (client.getAttributes() == null) {

View File

@ -0,0 +1,82 @@
/*
* 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.testsuite.broker;
import org.keycloak.saml.RandomSecret;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
import org.keycloak.testsuite.util.saml.SamlDocumentStepBuilder;
import org.w3c.dom.Node;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.namespace.QName;
import java.security.PublicKey;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
import static org.keycloak.testsuite.utils.io.IOUtil.setDocElementAttributeValue;
public class KcSamlEncryptedAssertionTest extends AbstractKcSamlEncryptedElementsTest {
@Override
protected SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm) {
return document -> { // Replace Assertion with EncryptedAssertion
Node assertionElement = document.getDocumentElement()
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0);
if (assertionElement == null) {
throw new IllegalStateException("Unable to find assertion in saml response document");
}
String samlNSPrefix = assertionElement.getPrefix();
// We need to add saml namespace to Assertion
// reason for that is because decryption is performed with assertion element extracted from the original
// document which has definition of saml namespace. After decrypting Assertion element without parent element
// saml namespace is not bound, so we add it
setDocElementAttributeValue(document, samlNSPrefix + ":" + JBossSAMLConstants.ASSERTION.get(), "xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion");
try {
QName encryptedAssertionElementQName = new QName(JBossSAMLURIConstants.ASSERTION_NSURI.get(),
JBossSAMLConstants.ENCRYPTED_ASSERTION.get(), samlNSPrefix);
int encryptionKeySize = 128;
byte[] secret = RandomSecret.createRandomSecret(encryptionKeySize / 8);
SecretKey secretKey = new SecretKeySpec(secret, "AES");
// 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);
} catch (Exception e) {
throw new ProcessingException("failed to encrypt", e);
}
assertThat(DocumentUtil.asString(document), containsString(keyEncryptionAlgorithm));
return document;
};
}
}

View File

@ -1,116 +1,68 @@
package org.keycloak.testsuite.broker;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.keycloak.common.util.PemUtils;
import org.keycloak.dom.saml.v2.protocol.AuthnRequestType;
import org.hamcrest.CoreMatchers;
import org.keycloak.saml.RandomSecret;
import org.keycloak.saml.common.constants.JBossSAMLConstants;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.exceptions.ConfigurationException;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.common.exceptions.ProcessingException;
import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.api.saml.v2.request.SAML2Request;
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.SamlClient;
import org.keycloak.testsuite.util.SamlClientBuilder;
import org.w3c.dom.Document;
import org.keycloak.testsuite.util.saml.SamlDocumentStepBuilder;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.namespace.QName;
import java.util.concurrent.atomic.AtomicReference;
import java.security.PublicKey;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.keycloak.saml.common.constants.JBossSAMLURIConstants.ASSERTION_NSURI;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
import static org.keycloak.testsuite.saml.AbstractSamlTest.SAML_CLIENT_ID_SALES_POST;
import static org.keycloak.testsuite.util.Matchers.isSamlResponse;
public class KcSamlEncryptedIdTest extends AbstractBrokerTest {
public class KcSamlEncryptedIdTest extends AbstractKcSamlEncryptedElementsTest {
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return KcSamlBrokerConfiguration.INSTANCE;
}
protected SamlDocumentStepBuilder.Saml2DocumentTransformer encryptDocument(PublicKey publicKey, String keyEncryptionAlgorithm) {
return document -> { // Replace Subject -> NameID with EncryptedId
Node assertionElement = document.getDocumentElement()
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0);
@Test
public void testEncryptedIdIsReadable() throws ConfigurationException, ParsingException, ProcessingException {
createRolesForRealm(bc.consumerRealmName());
if (assertionElement == null) {
throw new IllegalStateException("Unable to find assertion in saml response document");
}
AuthnRequestType loginRep = SamlClient.createLoginRequestDocument(SAML_CLIENT_ID_SALES_POST + ".dot/ted", getConsumerRoot() + "/sales-post/saml", null);
String samlNSPrefix = assertionElement.getPrefix();
String username;
try {
QName encryptedIdElementQName = new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.ENCRYPTED_ID.get(), samlNSPrefix);
QName nameIdQName = new QName(ASSERTION_NSURI.get(),
JBossSAMLConstants.NAMEID.get(), samlNSPrefix);
Document doc = SAML2Request.convert(loginRep);
// Add xmlns:saml attribute to NameId element,
// this is necessary as it is decrypted as a separate doc and saml namespace is not know
// unless added to NameId element
Element nameIdElement = DocumentUtil.getElement(document, nameIdQName);
if (nameIdElement == null) {
throw new RuntimeException("Assertion doesn't contain NameId " + DocumentUtil.asString(document));
}
nameIdElement.setAttribute("xmlns:" + samlNSPrefix, ASSERTION_NSURI.get());
username = nameIdElement.getTextContent();
final AtomicReference<String> username = new AtomicReference<>();
assertThat(adminClient.realm(bc.consumerRealmName()).users().search(username.get()), hasSize(0));
byte[] secret = RandomSecret.createRandomSecret(128 / 8);
SecretKey secretKey = new SecretKeySpec(secret, "AES");
SAMLDocumentHolder samlResponse = new SamlClientBuilder()
.authnRequest(getConsumerSamlEndpoint(bc.consumerRealmName()), doc, SamlClient.Binding.POST).build() // Request to consumer IdP
.login().idp(bc.getIDPAlias()).build()
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
XMLEncryptionUtil.encryptElement(nameIdQName, document, publicKey,
secretKey, 128, encryptedIdElementQName, true, keyEncryptionAlgorithm);
} catch (Exception e) {
throw new ProcessingException("failed to encrypt", e);
}
.processSamlResponse(SamlClient.Binding.POST) // AuthnRequest to producer IdP
.targetAttributeSamlRequest()
.build()
.login().user(bc.getUserLogin(), bc.getUserPassword()).build()
.processSamlResponse(SamlClient.Binding.POST) // Response from producer IdP
.transformDocument(document -> { // Replace Subject -> NameID with EncryptedId
Node assertionElement = document.getDocumentElement()
.getElementsByTagNameNS(ASSERTION_NSURI.get(), JBossSAMLConstants.ASSERTION.get()).item(0);
if (assertionElement == null) {
throw new IllegalStateException("Unable to find assertion in saml response document");
}
String samlNSPrefix = assertionElement.getPrefix();
try {
QName encryptedIdElementQName = new QName(ASSERTION_NSURI.get(), JBossSAMLConstants.ENCRYPTED_ID.get(), samlNSPrefix);
QName nameIdQName = new QName(ASSERTION_NSURI.get(),
JBossSAMLConstants.NAMEID.get(), samlNSPrefix);
// Add xmlns:saml attribute to NameId element,
// this is necessary as it is decrypted as a separate doc and saml namespace is not know
// unless added to NameId element
Element nameIdElement = DocumentUtil.getElement(document, nameIdQName);
if (nameIdElement == null) {
throw new RuntimeException("Assertion doesn't contain NameId " + DocumentUtil.asString(document));
}
nameIdElement.setAttribute("xmlns:" + samlNSPrefix, ASSERTION_NSURI.get());
username.set(nameIdElement.getTextContent());
byte[] secret = RandomSecret.createRandomSecret(128 / 8);
SecretKey secretKey = new SecretKeySpec(secret, "AES");
// encrypt the Assertion element and replace it with a EncryptedAssertion element.
XMLEncryptionUtil.encryptElement(nameIdQName, document, PemUtils.decodePublicKey(ApiUtil.findActiveSigningKey(adminClient.realm(bc.consumerRealmName())).getPublicKey()),
secretKey, 128, encryptedIdElementQName, true);
} catch (Exception e) {
throw new ProcessingException("failed to encrypt", e);
}
assertThat(DocumentUtil.asString(document), not(containsString(username.get())));
return document;
})
.build()
// first-broker flow
.updateProfile().firstName("a").lastName("b").email(bc.getUserEmail()).build()
.followOneRedirect()
.getSamlResponse(SamlClient.Binding.POST); // Response from consumer IdP
assertThat(samlResponse, Matchers.notNullValue());
assertThat(samlResponse.getSamlObject(), isSamlResponse(JBossSAMLURIConstants.STATUS_SUCCESS));
assertThat(adminClient.realm(bc.consumerRealmName()).users().search(username.get()), hasSize(1));
String doc = DocumentUtil.asString(document);
assertThat(doc, not(containsString(username)));
assertThat(doc, CoreMatchers.containsString(keyEncryptionAlgorithm));
return document;
};
}
}

View File

@ -15,6 +15,7 @@ import org.keycloak.protocol.saml.SamlConfigAttributes;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ComponentExportRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.saml.common.util.DocumentUtil;
@ -60,7 +61,6 @@ import org.w3c.dom.NodeList;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import static org.keycloak.testsuite.broker.BrokerTestConstants.*;
import static org.keycloak.testsuite.broker.BrokerTestTools.getConsumerRoot;
import static org.keycloak.testsuite.util.Matchers.bodyHC;
import static org.keycloak.testsuite.util.Matchers.isSamlResponse;
@ -68,12 +68,20 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.getProviderRoot;
public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
public void withSignedEncryptedAssertions(Runnable testBody, boolean signedDocument, boolean signedAssertion, boolean encryptedAssertion) throws Exception {
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());
String consumerCert = KeyUtils.getActiveEncKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), JWEConstants.RSA_OAEP).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue());
public void withSignedEncryptedAssertions(Runnable testBody, boolean signedDocument, boolean signedAssertion, boolean encryptedAssertion) throws Exception {
KeysMetadataRepresentation consumerKeysMetadata = adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata();
KeysMetadataRepresentation providerKeysMetadata = adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata();
String providerSigCert = KeyUtils.findActiveSigningKey(adminClient.realm(bc.providerRealmName()), Algorithm.RS256).getCertificate();
Assert.assertThat(providerSigCert, Matchers.notNullValue());
String consumerEncCert = KeyUtils.findActiveEncryptingKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RSA_OAEP).getCertificate();
Assert.assertThat(consumerEncCert, Matchers.notNullValue());
String consumerSigCert = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RS256).getCertificate();
Assert.assertThat(consumerSigCert, Matchers.notNullValue());
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.VALIDATE_SIGNATURE, Boolean.toString(signedAssertion || signedDocument))
@ -81,13 +89,14 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, Boolean.toString(encryptedAssertion))
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "false")
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, JWEConstants.RSA_OAEP)
.setAttribute(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, providerCert)
.setAttribute(SAMLIdentityProviderConfig.SIGNING_CERTIFICATE_KEY, providerSigCert)
.update();
Closeable clientUpdater = ClientAttributeUpdater.forClient(adminClient, bc.providerRealmName(), bc.getIDPClientIdInProviderRealm())
.setAttribute(SamlConfigAttributes.SAML_ENCRYPT, Boolean.toString(encryptedAssertion))
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, consumerCert)
.setAttribute(SamlConfigAttributes.SAML_ENCRYPTION_CERTIFICATE_ATTRIBUTE, consumerEncCert)
.setAttribute(SamlConfigAttributes.SAML_SERVER_SIGNATURE, Boolean.toString(signedDocument))
.setAttribute(SamlConfigAttributes.SAML_ASSERTION_SIGNATURE, Boolean.toString(signedAssertion))
.setAttribute(SamlConfigAttributes.SAML_SIGNING_CERTIFICATE_ATTRIBUTE, consumerSigCert)
.setAttribute(SamlConfigAttributes.SAML_CLIENT_SIGNATURE_ATTRIBUTE, "false") // Do not require client signature
.update())
{
@ -251,36 +260,12 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
public class KcSamlSignedBrokerConfiguration extends KcSamlBrokerConfiguration {
@Override
public RealmRepresentation createProviderRealm() {
RealmRepresentation realm = super.createProviderRealm();
realm.setPublicKey(REALM_PUBLIC_KEY);
realm.setPrivateKey(REALM_PRIVATE_KEY);
return realm;
}
@Override
public RealmRepresentation createConsumerRealm() {
RealmRepresentation realm = super.createConsumerRealm();
realm.setId(realm.getRealm());
ComponentExportRepresentation signingKey = createKeyRepToRealm(realm,"rsa");
signingKey.getConfig().putSingle(Attributes.PRIVATE_KEY_KEY, REALM_PRIVATE_KEY);
ComponentExportRepresentation decryptionKey = createKeyRepToRealm(realm, GeneratedRsaEncKeyProviderFactory.ID);
decryptionKey.getConfig().putSingle(Attributes.KEY_USE, KeyUse.ENC.name());
decryptionKey.getConfig().putSingle(Attributes.ALGORITHM_KEY, JWEConstants.RSA_OAEP);
return realm;
}
@Override
public List<ClientRepresentation> createProviderClients() {
List<ClientRepresentation> clientRepresentationList = super.createProviderClients();
String consumerCert = KeyUtils.getActiveSigningKey(adminClient.realm(consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
String consumerCert = KeyUtils.findActiveSigningKey(adminClient.realm(consumerRealmName()), Algorithm.RS256).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue());
for (ClientRepresentation client : clientRepresentationList) {
@ -307,7 +292,7 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
public IdentityProviderRepresentation setUpIdentityProvider(IdentityProviderSyncMode syncMode) {
IdentityProviderRepresentation result = super.setUpIdentityProvider(syncMode);
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
String providerCert = KeyUtils.findActiveSigningKey(adminClient.realm(providerRealmName()), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());
Map<String, String> config = result.getConfig();
@ -461,10 +446,10 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
public void testSignatureDataWhenWantsRequestsSigned() throws Exception {
// Verifies that an AuthnRequest contains the KeyInfo/X509Data element when
// client AuthnRequest signature is requested
String providerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.providerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
String providerCert = KeyUtils.findActiveSigningKey(adminClient.realm(bc.providerRealmName()), Algorithm.RS256).getCertificate();
Assert.assertThat(providerCert, Matchers.notNullValue());
String consumerCert = KeyUtils.getActiveSigningKey(adminClient.realm(bc.consumerRealmName()).keys().getKeyMetadata(), Algorithm.RS256).getCertificate();
String consumerCert = KeyUtils.findActiveSigningKey(adminClient.realm(bc.consumerRealmName()), Algorithm.RS256).getCertificate();
Assert.assertThat(consumerCert, Matchers.notNullValue());
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
@ -515,17 +500,4 @@ public class KcSamlSignedBrokerTest extends AbstractBrokerTest {
.execute();
}
}
protected ComponentExportRepresentation createKeyRepToRealm(RealmRepresentation realmRep, String providerId) {
ComponentExportRepresentation rep = new ComponentExportRepresentation();
rep.setName(providerId);
rep.setProviderId(providerId);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, DefaultKeyProviders.DEFAULT_PRIORITY);
if (realmRep.getComponents() == null) {
realmRep.setComponents(new MultivaluedHashMap<>());
}
realmRep.getComponents().add(KeyProvider.class.getName(), rep);
return rep;
}
}

View File

@ -3,36 +3,49 @@ package org.keycloak.testsuite.broker;
import com.google.common.collect.ImmutableMap;
import org.apache.tools.ant.filters.StringInputStream;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.broker.provider.ConfigConstants;
import org.keycloak.broker.saml.SAMLIdentityProviderConfig;
import org.keycloak.broker.saml.mappers.AttributeToRoleMapper;
import org.keycloak.broker.saml.mappers.UserAttributeMapper;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyUse;
import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType;
import org.keycloak.dom.saml.v2.metadata.KeyTypes;
import org.keycloak.dom.saml.v2.metadata.SPSSODescriptorType;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.dom.xmlsec.w3.xmlenc.EncryptionMethodType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderMapperSyncMode;
import org.keycloak.protocol.saml.SAMLEncryptionAlgorithms;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.updaters.IdentityProviderAttributeUpdater;
import java.io.Closeable;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.crypto.dsig.XMLSignature;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.util.KeyUtils.generateNewRealmKey;
public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
@ -220,14 +233,15 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
String encCert = certs.get("encryption");
Assert.assertNotNull(signingCert);
Assert.assertNotNull(encCert);
Assert.assertEquals(signingCert, encCert);
Assert.assertNotEquals(signingCert, encCert);
hasEncAlgorithms(spDescriptor, SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier());
}
// Enable signing and encryption and set encryption algorithm. Both keys are present and mapped to different realm key (signing to "rsa-generated"m encryption to "rsa-enc-generated")
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true")
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, JWEConstants.RSA_OAEP)
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, Algorithm.RSA_OAEP)
.update())
{
spDescriptor = getExportedSamlProvider();
@ -239,6 +253,48 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
Assert.assertNotNull(signingCert);
Assert.assertNotNull(encCert);
Assert.assertNotEquals(signingCert, encCert);
hasEncAlgorithms(spDescriptor, SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier());
}
}
@Test
public void testEncKeyDescriptors() throws Exception {
SPSSODescriptorType spDescriptor;
try (AutoCloseable ac1 = generateNewRealmKey(adminClient.realm(bc.consumerRealmName()), KeyUse.ENC, Algorithm.RSA1_5);
AutoCloseable ac2 = generateNewRealmKey(adminClient.realm(bc.consumerRealmName()), KeyUse.ENC, Algorithm.RSA_OAEP_256)) {
// Test all enc keys are present in metadata
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.update()) {
spDescriptor = getExportedSamlProvider();
hasEncAlgorithms(spDescriptor,
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier(),
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier()
);
}
// Specify algorithms for IDP
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, Algorithm.RSA_OAEP)
.update()) {
spDescriptor = getExportedSamlProvider();
hasEncAlgorithms(spDescriptor,
SAMLEncryptionAlgorithms.RSA_OAEP.getXmlEncIdentifier()
);
}
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.setAttribute(SAMLIdentityProviderConfig.ENCRYPTION_ALGORITHM, Algorithm.RSA1_5)
.update()) {
spDescriptor = getExportedSamlProvider();
hasEncAlgorithms(spDescriptor,
SAMLEncryptionAlgorithms.RSA1_5.getXmlEncIdentifier()
);
}
}
}
@ -249,6 +305,16 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
return o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
}
private void hasEncAlgorithms(SPSSODescriptorType spDescriptor, String... expectedAlgorithms) {
List<String> algorithms = spDescriptor.getKeyDescriptor().stream()
.filter(key -> key.getUse() == KeyTypes.ENCRYPTION)
.map(KeyDescriptorType::getEncryptionMethod)
.flatMap(list -> list.stream().map(EncryptionMethodType::getAlgorithm))
.collect(Collectors.toList());
assertThat(algorithms, containsInAnyOrder(expectedAlgorithms));
}
// Key is usage ("signing" or "encryption"), Value is string with X509 certificate
private Map<String, String> convertCerts(SPSSODescriptorType spDescriptor) {
return spDescriptor.getKeyDescriptor().stream()
@ -257,4 +323,59 @@ public class KcSamlSpDescriptorTest extends AbstractBrokerTest {
keyDescriptor -> keyDescriptor.getKeyInfo().getElementsByTagNameNS(XMLSignature.XMLNS, "X509Certificate").item(0).getTextContent()));
}
//KEYCLOAK-18909
@Test
public void testKeysExistenceInSpMetadata() throws IOException, ParsingException, URISyntaxException {
try (Closeable idpUpdater = new IdentityProviderAttributeUpdater(identityProviderResource)
.setAttribute(SAMLIdentityProviderConfig.WANT_AUTHN_REQUESTS_SIGNED, "true")
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_SIGNED, "true")
.setAttribute(SAMLIdentityProviderConfig.WANT_ASSERTIONS_ENCRYPTED, "true")
.update())
{
String spDescriptorString = identityProviderResource.export(null).readEntity(String.class);
SAMLParser parser = SAMLParser.getInstance();
EntityDescriptorType o = (EntityDescriptorType) parser.parse(new StringInputStream(spDescriptorString));
SPSSODescriptorType spDescriptor = o.getChoiceType().get(0).getDescriptors().get(0).getSpDescriptor();
//the SPSSODescriptor should have at least one KeyDescriptor for encryption and one for Signing
List<KeyDescriptorType> encKeyDescr = spDescriptor.getKeyDescriptor().stream().filter(k -> KeyTypes.ENCRYPTION.equals(k.getUse())).collect(Collectors.toList());
List<KeyDescriptorType> sigKeyDescr = spDescriptor.getKeyDescriptor().stream().filter(k -> KeyTypes.SIGNING.equals(k.getUse())).collect(Collectors.toList());
assertTrue(encKeyDescr.size() > 0);
assertTrue(sigKeyDescr.size() > 0);
//also, the keys should match the realm's dedicated keys for enc and sig
Set<String> encKeyDescNames = encKeyDescr.stream()
.map(k-> k.getKeyInfo().getElementsByTagName("ds:KeyName").item(0).getTextContent().trim())
.collect(Collectors.toCollection(HashSet::new));
Set<String> sigKeyDescNames = sigKeyDescr.stream()
.map(k-> k.getKeyInfo().getElementsByTagName("ds:KeyName").item(0).getTextContent().trim())
.collect(Collectors.toCollection(HashSet::new));
KeysMetadataRepresentation realmKeysMetadata = adminClient.realm(getBrokerConfiguration().consumerRealmName()).keys().getKeyMetadata();
long encMatches = realmKeysMetadata.getKeys().stream()
.filter(k -> KeyStatus.valueOf(k.getStatus()).isActive())
//.filter(k -> "RSA".equals(k.getType().trim()))
.filter(k -> KeyUse.ENC.equals(k.getUse()))
.filter(k -> encKeyDescNames.contains(k.getKid().trim()))
.count();
long sigMatches = realmKeysMetadata.getKeys().stream()
.filter(k -> KeyStatus.valueOf(k.getStatus()).isActive())
//.filter(k -> "RSA".equals(k.getType().trim()))
.filter(k -> KeyUse.SIG.equals(k.getUse()))
.filter(k -> sigKeyDescNames.contains(k.getKid().trim()))
.count();
assertTrue(encMatches > 0);
assertTrue(sigMatches > 0);
}
}
}

View File

@ -77,7 +77,7 @@ public class ImportedRsaKeyProviderTest extends AbstractKeycloakTest {
@Test
public void privateKeyOnlyForEnc() throws Exception {
privateKeyOnly(ImportedRsaEncKeyProviderFactory.ID, KeyUse.ENC, JWEConstants.RSA_OAEP);
privateKeyOnly(ImportedRsaEncKeyProviderFactory.ID, KeyUse.ENC, Algorithm.RSA_OAEP);
}
private void privateKeyOnly(String providerId, KeyUse keyUse, String algorithm) throws Exception {

View File

@ -551,7 +551,7 @@ public abstract class AbstractMigrationTest extends AbstractKeycloakTest {
String expectedMigrationRealmKey = "MIIEpAIBAAKCAQEApt6gCllWkVTZ7fy/oRIx6Bxjt9x3eKKyKGFXvN4iaafrNqpYU9lcqPngWJ9DyXGqUf8RpjPaQWiLWLxjw3xGBqLk2E1/Frb9e/dy8rj//fHGq6bujN1iguzyFwxPGT5Asd7jflRI3qU04M8JE52PArqPhGL2Fn+FiSK5SWRIGm+hVL7Ck/E/tVxM25sFG1/UTQqvrROm4q76TmP8FsyZaTLVf7cCwW2QPIX0N5HTVb3QbBb5KIsk4kKmk/g7uUxS9r42tu533LISzRr5CTyWZAL2XFRuF2RrKdE8gwqkEubw6sDmB2mE0EoPdY1DUhBQgVP/5rwJrCtTsUBR2xdEYQIDAQABAoIBAFbbsNBSOlZBpYJUOmcb8nBQPrOYhXN8tGGCccn0klMOvcdhmcJjdPDbyCQ5Gm7DxJUTwNsTSHsdcNMKlJ9Pk5+msJnKlOl87KrXXbTsCQvlCrWUmb0nCzz9GvJWTOHl3oT3cND0DE4gDksqWR4luCgCdevCGzgQvrBoK6wBD+r578uEW3iw10hnJ0+wnGiw8IvPzE1a9xbY4HD8/QrYdaLxuLb/aC1PDuzrz0cOjnvPkrws5JrbUSnbFygJiOv1z4l2Q00uGIxlHtXdwQBnTZZjVi4vOec2BYSHffgwDYEZIglw1mnrV7y0N1nnPbtJK/cegIkXoBQHXm8Q99TrWMUCgYEA9au86qcwrXZZg5H4BpR5cpy0MSkcKDbA1aRL1cAyTCqJxsczlAtLhFADF+NhnlXj4y7gwDEYWrz064nF73I+ZGicvCiyOy+tCTugTyTGS+XR948ElDMS6PCUUXsotS3dKa0b3c9wd2mxeddTjq/ArfgEVZJ6fE1KtjLt9dtfA+8CgYEAreK3JsvjR5b/Xct28TghYUU7Qnasombb/shqqy8FOMjYUr5OUm/OjNIgoCqhOlE8oQDJ4dOZofNSa7tL+oM8Gmbal+E3fRzxnx/9/EC4QV6sVaPLTIyk7EPfKTcZuzH7+BNZtAziTxJw9d6YJQRbkpg92EZIEoR8iDj2Xs5xrK8CgYEAwMVWwwYX8zT3vn7ukTM2LRH7bsvkVUXJgJqgCwT6Mrv6SmkK9vL5+cPS+Y6pjdW1sRGauBSOGL1Grf/4ug/6F03jFt4UJM8fRyxreU7Q7sNSQ6AMpsGA6BnHODycz7ZCYa59PErG5FyiL4of/cm5Nolz1TXQOPNpWZiTEqVlZC8CgYA4YPbjVF4nuxSnU64H/hwMjsbtAM9uhI016cN0J3W4+J3zDhMU9X1x+Tts0wWdg/N1fGz4lIQOl3cUyRCUc/KL2OdtMS+tmDHbVyMho9ZaE5kq10W2Vy+uDz+O/HeSU12QDK4cC8Vgv+jyPy7zaZtLR6NduUPrBRvfiyCOkr8WrwKBgQCY0h4RCdNFhr0KKLLmJipAtV8wBCGcg1jY1KoWKQswbcykfBKwHbF6EooVqkRW0ITjWB7ZZCf8TnSUxe0NXCUAkVBrhzS4DScgtoSZYOOUaSHgOxpfwgnQ3oYotKi98Yg3IsaLs1j4RuPG5Sp1z6o+ELP1uvr8azyn9YlLa+523Q==";
String realmId = migrationRealm.toRepresentation().getId();
List<ComponentRepresentation> components = migrationRealm.components().query(realmId, KeyProvider.class.getName());
assertEquals(3, components.size());
assertEquals(4, components.size());
components = migrationRealm.components().query(realmId, KeyProvider.class.getName(), "rsa");
assertEquals(1, components.size());

View File

@ -72,6 +72,7 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -334,7 +335,7 @@ public class ClientTokenExchangeSAML2Test extends AbstractKeycloakTest {
// Decrypt assertion
Document assertionDoc = DocumentUtil.getDocument(assertionXML);
Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(assertionDoc, privateKeyFromString(ENCRYPTION_PRIVATE_KEY));
Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(assertionDoc, data -> Collections.singletonList(privateKeyFromString(ENCRYPTION_PRIVATE_KEY)));
Assert.assertFalse(AssertionUtil.isSignedElement(assertionElement));
AssertionType assertion = (AssertionType) SAMLParser.getInstance().parse(assertionElement);
@ -382,7 +383,7 @@ public class ClientTokenExchangeSAML2Test extends AbstractKeycloakTest {
// Verify assertion
Document assertionDoc = DocumentUtil.getDocument(assertionXML);
Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(assertionDoc, privateKeyFromString(ENCRYPTION_PRIVATE_KEY));
Element assertionElement = XMLEncryptionUtil.decryptElementInDocument(assertionDoc, data -> Collections.singletonList(privateKeyFromString(ENCRYPTION_PRIVATE_KEY)));
Assert.assertTrue(AssertionUtil.isSignedElement(assertionElement));
AssertionType assertion = (AssertionType) SAMLParser.getInstance().parse(assertionElement);
Assert.assertTrue(AssertionUtil.isSignatureValid(assertionElement, publicKeyFromString()));
@ -698,7 +699,7 @@ public class ClientTokenExchangeSAML2Test extends AbstractKeycloakTest {
}
private PublicKey publicKeyFromString() {
KeysMetadataRepresentation.KeyMetadataRepresentation keyRep = KeyUtils.getActiveSigningKey(adminClient.realm(TEST).keys().getKeyMetadata(), Algorithm.RS256);
KeysMetadataRepresentation.KeyMetadataRepresentation keyRep = KeyUtils.findActiveSigningKey(adminClient.realm(TEST), Algorithm.RS256);
return org.keycloak.testsuite.util.KeyUtils.publicKeyFromString(keyRep.getPublicKey());
}

View File

@ -1443,7 +1443,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
if (keyId == null) {
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils
.getActiveEncKey(testRealm().keys().getKeyMetadata(),
.findActiveEncryptingKey(testRealm(),
Algorithm.PS256);
keyId = encKey.getKid();
}
@ -1472,8 +1472,8 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
@Test
public void testRealmPublicKeyEncryptedRequestObjectUsingKid() throws Exception {
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.getActiveEncKey(testRealm().keys().getKeyMetadata(),
Algorithm.RS256);
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.findActiveEncryptingKey(testRealm(),
Algorithm.RSA_OAEP);
JWEHeader jweHeader = new JWEHeader(RSA_OAEP, JWEConstants.A128CBC_HS256, null, encKey.getKid());
assertRequestObjectEncryption(jweHeader);
}
@ -1529,7 +1529,7 @@ public class OIDCAdvancedRequestParamsTest extends AbstractTestRealmKeycloakTest
String keyId = jweHeader.getKeyId();
if (keyId == null) {
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.getActiveEncKey(testRealm().keys().getKeyMetadata(),
KeysMetadataRepresentation.KeyMetadataRepresentation encKey = KeyUtils.findActiveEncryptingKey(testRealm(),
Algorithm.PS256);
keyId = encKey.getKid();
}

View File

@ -57,6 +57,7 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.testsuite.client.resources.TestApplicationResourceUrls;
import org.keycloak.testsuite.client.resources.TestOIDCEndpointsApplicationResource;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.util.KeyUtils;
import org.keycloak.testsuite.util.KeycloakModelUtils;
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
@ -440,7 +441,7 @@ public class UserInfoTest extends AbstractKeycloakTest {
.assertEvent();
// Check signature and content
PublicKey publicKey = PemUtils.decodePublicKey(ApiUtil.findActiveSigningKey(adminClient.realm("test")).getPublicKey());
PublicKey publicKey = PemUtils.decodePublicKey(KeyUtils.findActiveSigningKey(adminClient.realm("test")).getPublicKey());
Assert.assertEquals(200, response.getStatus());
Assert.assertEquals(response.getHeaderString(HttpHeaders.CONTENT_TYPE), MediaType.APPLICATION_JWT);

View File

@ -159,7 +159,7 @@ public class ArtifactBindingTest extends AbstractSamlTest {
assertThat(loginResponse.getAssertions().get(0).getEncryptedAssertion(), not(nullValue()));
SamlDeployment deployment = SamlUtils.getSamlDeploymentForClient("sales-post-enc");
AssertionUtil.decryptAssertion(response, loginResponse, deployment.getDecryptionKey());
AssertionUtil.decryptAssertion(loginResponse, deployment.getDecryptionKey());
assertThat(loginResponse.getAssertions().get(0).getAssertion(), not(nullValue()));
assertThat(loginResponse.getAssertions().get(0).getEncryptedAssertion(), nullValue());