Load client keys using SubjectPublicKeyInfo and upload jwks type into the jwks attributes for OIDC ones
Closes #33820 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
01026fab79
commit
6d52520730
6 changed files with 138 additions and 49 deletions
|
@ -61,7 +61,10 @@ public abstract class PemUtilsTest {
|
|||
public void testDecodeObjectsInPEMFormat() {
|
||||
String privateKey1 = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=";
|
||||
String publicKey1 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB";
|
||||
|
||||
String publicKeyEC = "-----BEGIN PUBLIC KEY-----\n"
|
||||
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAElyCs9XI47lFR5l4WafsZZrAiUmEr\n"
|
||||
+ "+kYeStgx3tyPntt3YNfs6kAVNozI4aJqdqDjITJWatHm6boJ0BRLPNphRA==\n"
|
||||
+ "-----END PUBLIC KEY-----";
|
||||
String cert1 = "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ==";
|
||||
String cert2 = "MIICnTCCAYUCBgFPPQDGxTANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE4NTAwNVoXDTI1MDgxNzE4NTE0NVowEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMMw3PaBffWxgS2PYSDDBp6As+cNvv9kt2C4f/RDAGmvSIHPFev9kuQiKs3Oaws3ZsV4JG3qHEuYgnh9W4vfe3DwNwtD1bjL5FYBhPBFTw0lAQECYxaBHnkjHwUKp957FqdSPPICm3LjmTcEdlH+9dpp9xHCMbbiNiWDzWI1xSxC8Fs2d0hwz1sd+Q4QeTBPIBWcPM+ICZtNG5MN+ORfayu4X+Me5d0tXG2fQO//rAevk1i5IFjKZuOjTwyKB5SJIY4b8QTeg0g/50IU7Ht00Pxw6CK02dHS+FvXHasZlD3ckomqCDjStTBWdhJo5dST0CbOqalkkpLlCCbGA1yEQRsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUIMeJ+EAo8eNpCG/nXImacjrKakbFnZYBGD/gqeTGaZynkX+jgBSructTHR83zSH+yELEhsAy+3BfK4EEihp+PEcRnK2fASVkHste8AQ7rlzC+HGGirlwrVhWCdizNUCGK80DE537IZ7nmZw6LFG9P5/Q2MvCsOCYjRUvMkukq6TdXBXR9tETwZ+0gpSfsOxjj0ZF7ftTRUSzx4rFfcbM9fRNdVizdOuKGc8HJPA5lLOxV6CyaYIvi3y5RlQI1OHeS34lE4w9CNPRFa/vdxXvN7ClyzA0HMFNWxBN7pC/Ht/FbhSvaAagJBHg+vCrcY5C26Oli7lAglf/zZrwUPs0w==";
|
||||
|
||||
|
@ -84,6 +87,7 @@ public abstract class PemUtilsTest {
|
|||
|
||||
testPrivateKeyEncodeDecode(privateKey1);
|
||||
testPublicKeyEncodeDecode(publicKey1);
|
||||
testPublicKeyEncodeDecode(publicKeyEC);
|
||||
testPrivateKeyEncodeDecode(PemUtils.removeBeginEnd(privateKey2).replace("\n", ""));
|
||||
testCertificateEncodeDecode(cert1);
|
||||
testCertificateEncodeDecode(cert2);
|
||||
|
@ -125,7 +129,7 @@ public abstract class PemUtilsTest {
|
|||
private void testPublicKeyEncodeDecode(String origPublicKeyPem) {
|
||||
PublicKey decodedPublicKey = PemUtils.decodePublicKey(origPublicKeyPem);
|
||||
String encodedPublicKey = PemUtils.encodeKey(decodedPublicKey);
|
||||
assertEquals(origPublicKeyPem, encodedPublicKey);
|
||||
assertEquals(PemUtils.removeBeginEnd(origPublicKeyPem), encodedPublicKey);
|
||||
}
|
||||
|
||||
private void testCertificateEncodeDecode(String origCertPem) {
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package org.keycloak.crypto.def;
|
||||
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.keycloak.common.util.DerUtils;
|
||||
import org.keycloak.common.util.PemException;
|
||||
|
@ -24,6 +26,7 @@ import org.keycloak.common.crypto.PemUtilsProvider;
|
|||
|
||||
import java.io.StringWriter;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* Encodes Key or Certificates to PEM format string
|
||||
|
@ -59,6 +62,22 @@ public class BCPemUtilsProvider extends PemUtilsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey decodePublicKey(String pem) {
|
||||
try {
|
||||
// try to decode using SubjectPublicKeyInfo which allows to know the key type
|
||||
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemToDer(pem));
|
||||
if (publicKeyInfo != null && publicKeyInfo.getAlgorithm() != null) {
|
||||
return new JcaPEMKeyConverter().getPublicKey(publicKeyInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// error reading PEM object just go to previous RSA forced key
|
||||
}
|
||||
|
||||
// assume RSA if it cannot be decoded from BC knowing the key
|
||||
return decodePublicKey(pem, "RSA");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey decodePrivateKey(String pem) {
|
||||
if (pem == null) {
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
package org.keycloak.crypto.fips;
|
||||
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
|
||||
import org.bouncycastle.openssl.PEMKeyPair;
|
||||
import org.bouncycastle.openssl.PEMParser;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.keycloak.common.util.BouncyIntegration;
|
||||
import org.keycloak.common.util.DerUtils;
|
||||
import org.keycloak.common.util.PemException;
|
||||
import org.keycloak.common.crypto.PemUtilsProvider;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
|
@ -31,9 +31,8 @@ import org.keycloak.common.util.PemUtils;
|
|||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* Encodes Key or Certificates to PEM format string
|
||||
|
@ -69,6 +68,22 @@ public class BCFIPSPemUtilsProvider extends PemUtilsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey decodePublicKey(String pem) {
|
||||
try {
|
||||
// try to decode using SubjectPublicKeyInfo which allows to know the key type
|
||||
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfo.getInstance(pemToDer(pem));
|
||||
if (publicKeyInfo != null && publicKeyInfo.getAlgorithm() != null) {
|
||||
return new JcaPEMKeyConverter().getPublicKey(publicKeyInfo);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// error reading PEM object just go to previous RSA forced key
|
||||
}
|
||||
|
||||
// assume RSA if it cannot be decoded from BC knowing the key
|
||||
return decodePublicKey(pem, "RSA");
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey decodePrivateKey(String pem) {
|
||||
if (pem == null) {
|
||||
|
|
|
@ -32,22 +32,18 @@ import org.keycloak.common.util.KeystoreUtil.KeystoreFormat;
|
|||
import org.keycloak.events.admin.OperationType;
|
||||
import org.keycloak.events.admin.ResourceType;
|
||||
import org.keycloak.http.FormPartValue;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKParser;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeyManager;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.representations.KeyStoreConfig;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.resources.KeycloakOpenAPI;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.services.util.CertificateInfoHelper;
|
||||
import org.keycloak.util.JWKSUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
|
@ -60,10 +56,9 @@ import jakarta.ws.rs.core.MultivaluedMap;
|
|||
import jakarta.ws.rs.core.Response;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Set;
|
||||
|
@ -140,7 +135,6 @@ public class ClientAttributeCertificateResource {
|
|||
/**
|
||||
* Upload certificate and eventually private key
|
||||
*
|
||||
* @param input
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
|
@ -154,9 +148,7 @@ public class ClientAttributeCertificateResource {
|
|||
auth.clients().requireConfigure(client);
|
||||
|
||||
try {
|
||||
CertificateRepresentation info = getCertFromRequest();
|
||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||
|
||||
CertificateRepresentation info = updateCertFromRequest();
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
|
||||
return info;
|
||||
} catch (IllegalStateException ise) {
|
||||
|
@ -167,7 +159,6 @@ public class ClientAttributeCertificateResource {
|
|||
/**
|
||||
* Upload only certificate, not private key
|
||||
*
|
||||
* @param input
|
||||
* @return information extracted from uploaded certificate - not necessarily the new state of certificate on the server
|
||||
* @throws IOException
|
||||
*/
|
||||
|
@ -181,10 +172,7 @@ public class ClientAttributeCertificateResource {
|
|||
auth.clients().requireConfigure(client);
|
||||
|
||||
try {
|
||||
CertificateRepresentation info = getCertFromRequest();
|
||||
info.setPrivateKey(null);
|
||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||
|
||||
CertificateRepresentation info = updateCertFromRequest();
|
||||
adminEvent.operation(OperationType.ACTION).resourcePath(session.getContext().getUri()).representation(info).success();
|
||||
return info;
|
||||
} catch (IllegalStateException ise) {
|
||||
|
@ -192,7 +180,7 @@ public class ClientAttributeCertificateResource {
|
|||
}
|
||||
}
|
||||
|
||||
private CertificateRepresentation getCertFromRequest() throws IOException {
|
||||
private CertificateRepresentation updateCertFromRequest() throws IOException {
|
||||
auth.clients().requireManage(client);
|
||||
CertificateRepresentation info = new CertificateRepresentation();
|
||||
MultivaluedMap<String, FormPartValue> uploadForm = session.getContext().getHttpRequest().getMultiPartFormParameters();
|
||||
|
@ -203,38 +191,34 @@ public class ClientAttributeCertificateResource {
|
|||
String keystoreFormat = keystoreFormatPart.asString();
|
||||
FormPartValue inputParts = uploadForm.getFirst("file");
|
||||
if (keystoreFormat.equals(CERTIFICATE_PEM)) {
|
||||
String pem = StreamUtil.readString(inputParts.asInputStream());
|
||||
|
||||
String pem = StreamUtil.readString(inputParts.asInputStream(), StandardCharsets.UTF_8);
|
||||
pem = PemUtils.removeBeginEnd(pem);
|
||||
|
||||
// Validate format
|
||||
KeycloakModelUtils.getCertificate(pem);
|
||||
|
||||
info.setCertificate(pem);
|
||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||
return info;
|
||||
} else if (keystoreFormat.equals(PUBLIC_KEY_PEM)) {
|
||||
String pem = StreamUtil.readString(inputParts.asInputStream());
|
||||
String pem = StreamUtil.readString(inputParts.asInputStream(), StandardCharsets.UTF_8);
|
||||
|
||||
// Validate format
|
||||
KeycloakModelUtils.getPublicKey(pem);
|
||||
|
||||
info.setPublicKey(pem);
|
||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||
return info;
|
||||
} else if (keystoreFormat.equals(JSON_WEB_KEY_SET)) {
|
||||
InputStream stream = inputParts.asInputStream();
|
||||
JSONWebKeySet keySet = JsonSerialization.readValue(stream, JSONWebKeySet.class);
|
||||
JWK publicKeyJwk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
|
||||
if (publicKeyJwk == null) {
|
||||
throw new IllegalStateException("Certificate not found for use sig");
|
||||
String jwks = StreamUtil.readString(inputParts.asInputStream(), StandardCharsets.UTF_8);
|
||||
|
||||
info = CertificateInfoHelper.jwksStringToSigCertificateRepresentation(jwks);
|
||||
// jwks is only valid for OIDC clients
|
||||
if (OIDCLoginProtocol.LOGIN_PROTOCOL.equals(client.getProtocol())) {
|
||||
CertificateInfoHelper.updateClientModelJwksString(client, attributePrefix, jwks);
|
||||
} else {
|
||||
PublicKey publicKey = JWKParser.create(publicKeyJwk).toPublicKey();
|
||||
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
|
||||
info.setPublicKey(publicKeyPem);
|
||||
info.setKid(publicKeyJwk.getKeyId());
|
||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String keyAlias = uploadForm.getFirst("keyAlias").asString();
|
||||
FormPartValue keyPasswordPart = uploadForm.getFirst("keyPassword");
|
||||
|
@ -267,6 +251,7 @@ public class ClientAttributeCertificateResource {
|
|||
info.setCertificate(certPem);
|
||||
}
|
||||
|
||||
CertificateInfoHelper.updateClientModelCertificateInfo(client, info, attributePrefix);
|
||||
return info;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,14 +18,20 @@
|
|||
package org.keycloak.services.util;
|
||||
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKParser;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.util.JWKSUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -48,6 +54,11 @@ public class CertificateInfoHelper {
|
|||
String publicKeyAttribute = attributePrefix + "." + PUBLIC_KEY;
|
||||
String kidAttribute = attributePrefix + "." + KID;
|
||||
|
||||
if (OIDCLoginProtocol.LOGIN_PROTOCOL.equals(client.getProtocol())
|
||||
&& Boolean.parseBoolean(client.getAttribute(OIDCConfigAttributes.USE_JWKS_STRING))) {
|
||||
return jwksStringToSigCertificateRepresentation(client.getAttribute(OIDCConfigAttributes.JWKS_STRING));
|
||||
}
|
||||
|
||||
CertificateRepresentation rep = new CertificateRepresentation();
|
||||
rep.setCertificate(client.getAttribute(certificateAttribute));
|
||||
rep.setPublicKey(client.getAttribute(publicKeyAttribute));
|
||||
|
@ -57,13 +68,30 @@ public class CertificateInfoHelper {
|
|||
return rep;
|
||||
}
|
||||
|
||||
public static CertificateRepresentation jwksStringToSigCertificateRepresentation(String jwks) {
|
||||
if (jwks == null) {
|
||||
throw new IllegalStateException("The jwks is null!");
|
||||
}
|
||||
|
||||
try {
|
||||
JSONWebKeySet keySet = JsonSerialization.readValue(jwks, JSONWebKeySet.class);
|
||||
JWK publicKeyJwk = JWKSUtils.getKeyForUse(keySet, JWK.Use.SIG);
|
||||
if (publicKeyJwk == null) {
|
||||
throw new IllegalStateException("Certificate not found for use sig");
|
||||
}
|
||||
|
||||
PublicKey publicKey = JWKParser.create(publicKeyJwk).toPublicKey();
|
||||
String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
|
||||
CertificateRepresentation info = new CertificateRepresentation();
|
||||
info.setPublicKey(publicKeyPem);
|
||||
info.setKid(publicKeyJwk.getKeyId());
|
||||
return info;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Invalid jwks representation!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateClientModelCertificateInfo(ClientModel client, CertificateRepresentation rep, String attributePrefix) {
|
||||
String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
|
||||
String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
|
||||
String publicKeyAttribute = attributePrefix + "." + PUBLIC_KEY;
|
||||
String kidAttribute = attributePrefix + "." + KID;
|
||||
|
||||
if (rep.getPublicKey() == null && rep.getCertificate() == null) {
|
||||
throw new IllegalStateException("Both certificate and publicKey are null!");
|
||||
}
|
||||
|
@ -72,10 +100,42 @@ public class CertificateInfoHelper {
|
|||
throw new IllegalStateException("Both certificate and publicKey are not null!");
|
||||
}
|
||||
|
||||
String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
|
||||
String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
|
||||
String publicKeyAttribute = attributePrefix + "." + PUBLIC_KEY;
|
||||
String kidAttribute = attributePrefix + "." + KID;
|
||||
|
||||
setOrRemoveAttr(client, privateKeyAttribute, rep.getPrivateKey());
|
||||
setOrRemoveAttr(client, publicKeyAttribute, rep.getPublicKey());
|
||||
setOrRemoveAttr(client, certificateAttribute, rep.getCertificate());
|
||||
setOrRemoveAttr(client, kidAttribute, rep.getKid());
|
||||
|
||||
if (OIDCLoginProtocol.LOGIN_PROTOCOL.equals(client.getProtocol())) {
|
||||
setOrRemoveAttr(client, OIDCConfigAttributes.USE_JWKS_STRING, null);
|
||||
setOrRemoveAttr(client, OIDCConfigAttributes.JWKS_STRING, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateClientModelJwksString(ClientModel client, String attributePrefix, String jwks) {
|
||||
if (jwks == null) {
|
||||
throw new IllegalStateException("jwks string is null!");
|
||||
}
|
||||
|
||||
if (!OIDCLoginProtocol.LOGIN_PROTOCOL.equals(client.getProtocol())) {
|
||||
throw new IllegalStateException("jwks can only be set for OIDC clients!");
|
||||
}
|
||||
|
||||
String privateKeyAttribute = attributePrefix + "." + PRIVATE_KEY;
|
||||
String certificateAttribute = attributePrefix + "." + X509CERTIFICATE;
|
||||
String publicKeyAttribute = attributePrefix + "." + PUBLIC_KEY;
|
||||
String kidAttribute = attributePrefix + "." + KID;
|
||||
|
||||
setOrRemoveAttr(client, privateKeyAttribute, null);
|
||||
setOrRemoveAttr(client, publicKeyAttribute, null);
|
||||
setOrRemoveAttr(client, certificateAttribute, null);
|
||||
setOrRemoveAttr(client, kidAttribute, null);
|
||||
setOrRemoveAttr(client, OIDCConfigAttributes.USE_JWKS_STRING, Boolean.TRUE.toString());
|
||||
setOrRemoveAttr(client, OIDCConfigAttributes.JWKS_STRING, jwks);
|
||||
}
|
||||
|
||||
private static void setOrRemoveAttr(ClientModel client, String attrName, String attrValue) {
|
||||
|
|
|
@ -99,6 +99,7 @@ import org.keycloak.representations.AccessToken;
|
|||
import org.keycloak.representations.JsonWebToken;
|
||||
import org.keycloak.representations.KeyStoreConfig;
|
||||
import org.keycloak.representations.RefreshToken;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.EventRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -475,9 +476,14 @@ public abstract class AbstractClientAuthSignedJWTTest extends AbstractKeycloakTe
|
|||
final String publicKeyNew = client.getAttributes().get(JWTClientAuthenticator.ATTR_PREFIX + "." + CertificateInfoHelper.PUBLIC_KEY);
|
||||
assertEquals("Certificates don't match", pem, publicKeyNew);
|
||||
} else if (keystoreFormat.equals(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.JSON_WEB_KEY_SET)) {
|
||||
final String publicKeyNew = client.getAttributes().get(JWTClientAuthenticator.ATTR_PREFIX + "." + CertificateInfoHelper.PUBLIC_KEY);
|
||||
Assert.assertEquals("true", client.getAttributes().get(OIDCConfigAttributes.USE_JWKS_STRING));
|
||||
String jwks = new String(Files.readAllBytes(keystoreFile.toPath()));
|
||||
Assert.assertEquals(jwks, client.getAttributes().get(OIDCConfigAttributes.JWKS_STRING));
|
||||
CertificateRepresentation info = getClient(testRealm.getRealm(), client.getId())
|
||||
.getCertficateResource(JWTClientAuthenticator.ATTR_PREFIX).getKeyInfo();
|
||||
Assert.assertNotNull(info.getPublicKey());
|
||||
// Just assert it's valid public key
|
||||
PublicKey pk = KeycloakModelUtils.getPublicKey(publicKeyNew);
|
||||
PublicKey pk = KeycloakModelUtils.getPublicKey(info.getPublicKey());
|
||||
Assert.assertNotNull(pk);
|
||||
} else if (keystoreFormat.equals(org.keycloak.services.resources.admin.ClientAttributeCertificateResource.CERTIFICATE_PEM)) {
|
||||
String pem = new String(Files.readAllBytes(keystoreFile.toPath()));
|
||||
|
|
Loading…
Reference in a new issue