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:
parent
5b626231d9
commit
dc8b759c3d
38 changed files with 1035 additions and 350 deletions
|
@ -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()));
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}.
|
||||
*/
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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));
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in a new issue