Allow to create EC certificates if new EC-key-provider is created (#31843)
Closes #31842 Signed-off-by: Pascal Knüppel <pascal.knueppel@governikus.de>
This commit is contained in:
parent
637ca2e138
commit
41ee68611f
13 changed files with 195 additions and 35 deletions
|
@ -53,7 +53,7 @@ public abstract class AbstractJWKBuilder {
|
||||||
public JWK rsa(Key key) {
|
public JWK rsa(Key key) {
|
||||||
return rsa(key, null, KeyUse.SIG);
|
return rsa(key, null, KeyUse.SIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JWK rsa(Key key, X509Certificate certificate) {
|
public JWK rsa(Key key, X509Certificate certificate) {
|
||||||
return rsa(key, Collections.singletonList(certificate), KeyUse.SIG);
|
return rsa(key, Collections.singletonList(certificate), KeyUse.SIG);
|
||||||
}
|
}
|
||||||
|
@ -99,6 +99,10 @@ public abstract class AbstractJWKBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public JWK ec(Key key, KeyUse keyUse) {
|
public JWK ec(Key key, KeyUse keyUse) {
|
||||||
|
return this.ec(key, null, keyUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JWK ec(Key key, List<X509Certificate> certificates, KeyUse keyUse) {
|
||||||
ECPublicKey ecKey = (ECPublicKey) key;
|
ECPublicKey ecKey = (ECPublicKey) key;
|
||||||
|
|
||||||
ECPublicJWK k = new ECPublicJWK();
|
ECPublicJWK k = new ECPublicJWK();
|
||||||
|
@ -113,7 +117,15 @@ public abstract class AbstractJWKBuilder {
|
||||||
k.setCrv("P-" + fieldSize);
|
k.setCrv("P-" + fieldSize);
|
||||||
k.setX(Base64Url.encode(toIntegerBytes(ecKey.getW().getAffineX(), fieldSize)));
|
k.setX(Base64Url.encode(toIntegerBytes(ecKey.getW().getAffineX(), fieldSize)));
|
||||||
k.setY(Base64Url.encode(toIntegerBytes(ecKey.getW().getAffineY(), fieldSize)));
|
k.setY(Base64Url.encode(toIntegerBytes(ecKey.getW().getAffineY(), fieldSize)));
|
||||||
|
|
||||||
|
if (certificates != null && !certificates.isEmpty()) {
|
||||||
|
String[] certificateChain = new String[certificates.size()];
|
||||||
|
for (int i = 0; i < certificates.size(); i++) {
|
||||||
|
certificateChain[i] = PemUtils.encodeCertificate(certificates.get(i));
|
||||||
|
}
|
||||||
|
k.setX509CertificateChain(certificateChain);
|
||||||
|
}
|
||||||
|
|
||||||
return k;
|
return k;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,9 @@
|
||||||
package org.keycloak.jose.jwk;
|
package org.keycloak.jose.jwk;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
|
|
@ -20,7 +20,9 @@ package org.keycloak.jose.jwk;
|
||||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -37,6 +39,12 @@ public class JWK {
|
||||||
|
|
||||||
public static final String PUBLIC_KEY_USE = "use";
|
public static final String PUBLIC_KEY_USE = "use";
|
||||||
|
|
||||||
|
public static final String X5C = "x5c";
|
||||||
|
|
||||||
|
public static final String SHA1_509_THUMBPRINT = "x5t";
|
||||||
|
|
||||||
|
public static final String SHA256_509_THUMBPRINT = "x5t#S256";
|
||||||
|
|
||||||
public enum Use {
|
public enum Use {
|
||||||
SIG("sig"),
|
SIG("sig"),
|
||||||
ENCRYPTION("enc");
|
ENCRYPTION("enc");
|
||||||
|
@ -64,6 +72,13 @@ public class JWK {
|
||||||
@JsonProperty(PUBLIC_KEY_USE)
|
@JsonProperty(PUBLIC_KEY_USE)
|
||||||
private String publicKeyUse;
|
private String publicKeyUse;
|
||||||
|
|
||||||
|
@JsonProperty(X5C)
|
||||||
|
private String[] x509CertificateChain;
|
||||||
|
|
||||||
|
private String sha1x509Thumbprint;
|
||||||
|
|
||||||
|
private String sha256x509Thumbprint;
|
||||||
|
|
||||||
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
|
protected Map<String, Object> otherClaims = new HashMap<String, Object>();
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,6 +114,32 @@ public class JWK {
|
||||||
this.publicKeyUse = publicKeyUse;
|
this.publicKeyUse = publicKeyUse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String[] getX509CertificateChain() {
|
||||||
|
return x509CertificateChain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setX509CertificateChain(String[] x509CertificateChain) {
|
||||||
|
this.x509CertificateChain = x509CertificateChain;
|
||||||
|
if (x509CertificateChain != null && x509CertificateChain.length > 0) {
|
||||||
|
try {
|
||||||
|
sha1x509Thumbprint = PemUtils.generateThumbprint(x509CertificateChain, "SHA-1");
|
||||||
|
sha256x509Thumbprint = PemUtils.generateThumbprint(x509CertificateChain, "SHA-256");
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty(SHA1_509_THUMBPRINT)
|
||||||
|
public String getSha1x509Thumbprint() {
|
||||||
|
return sha1x509Thumbprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty(SHA256_509_THUMBPRINT)
|
||||||
|
public String getSha256x509Thumbprint() {
|
||||||
|
return sha256x509Thumbprint;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonAnyGetter
|
@JsonAnyGetter
|
||||||
public Map<String, Object> getOtherClaims() {
|
public Map<String, Object> getOtherClaims() {
|
||||||
return otherClaims;
|
return otherClaims;
|
||||||
|
|
|
@ -144,6 +144,7 @@ describe("Realm settings events tab tests", () => {
|
||||||
cy.findByTestId("option-ecdsa-generated").click();
|
cy.findByTestId("option-ecdsa-generated").click();
|
||||||
realmSettingsPage.enterUIDisplayName("test_ecdsa-generated");
|
realmSettingsPage.enterUIDisplayName("test_ecdsa-generated");
|
||||||
realmSettingsPage.toggleSwitch("active", false);
|
realmSettingsPage.toggleSwitch("active", false);
|
||||||
|
realmSettingsPage.toggleSwitch("ecGenerateCertificate", false);
|
||||||
realmSettingsPage.addProvider();
|
realmSettingsPage.addProvider();
|
||||||
|
|
||||||
realmSettingsPage.toggleAddProviderDropdown();
|
realmSettingsPage.toggleAddProviderDropdown();
|
||||||
|
|
|
@ -194,21 +194,8 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
|
||||||
{
|
{
|
||||||
name: "publicKeys",
|
name: "publicKeys",
|
||||||
displayKey: "publicKeys",
|
displayKey: "publicKeys",
|
||||||
cellRenderer: ({ type, publicKey, certificate }: KeyData) => {
|
cellRenderer: ({ publicKey, certificate }: KeyData) => {
|
||||||
if (type === "EC") {
|
if (certificate) {
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
togglePublicKeyDialog();
|
|
||||||
setPublicKey(publicKey!);
|
|
||||||
}}
|
|
||||||
variant="secondary"
|
|
||||||
id="kc-public-key"
|
|
||||||
>
|
|
||||||
{t("publicKey")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
} else if (type === "RSA") {
|
|
||||||
return (
|
return (
|
||||||
<div className="button-wrapper">
|
<div className="button-wrapper">
|
||||||
<Button
|
<Button
|
||||||
|
@ -234,7 +221,7 @@ export const KeysListTab = ({ realmComponents }: KeysListTabProps) => {
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (type === "OKP") {
|
} else if (publicKey) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -25,6 +25,8 @@ import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public abstract class AbstractEcKeyProvider implements KeyProvider {
|
public abstract class AbstractEcKeyProvider implements KeyProvider {
|
||||||
|
@ -54,7 +56,8 @@ public abstract class AbstractEcKeyProvider implements KeyProvider {
|
||||||
return Stream.of(key);
|
return Stream.of(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected KeyWrapper createKeyWrapper(KeyPair keyPair, String algorithm, KeyUse keyUse) {
|
protected KeyWrapper createKeyWrapper(KeyPair keyPair, String algorithm, KeyUse keyUse,
|
||||||
|
X509Certificate selfSignedCertificate) {
|
||||||
KeyWrapper key = new KeyWrapper();
|
KeyWrapper key = new KeyWrapper();
|
||||||
|
|
||||||
key.setProviderId(model.getId());
|
key.setProviderId(model.getId());
|
||||||
|
@ -67,6 +70,7 @@ public abstract class AbstractEcKeyProvider implements KeyProvider {
|
||||||
key.setStatus(status);
|
key.setStatus(status);
|
||||||
key.setPrivateKey(keyPair.getPrivate());
|
key.setPrivateKey(keyPair.getPrivate());
|
||||||
key.setPublicKey(keyPair.getPublic());
|
key.setPublicKey(keyPair.getPublic());
|
||||||
|
Optional.ofNullable(selfSignedCertificate).ifPresent(key::setCertificate);
|
||||||
|
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@ public abstract class AbstractEcKeyProviderFactory<T extends KeyProvider> implem
|
||||||
return ProviderConfigurationBuilder.create()
|
return ProviderConfigurationBuilder.create()
|
||||||
.property(Attributes.PRIORITY_PROPERTY)
|
.property(Attributes.PRIORITY_PROPERTY)
|
||||||
.property(Attributes.ENABLED_PROPERTY)
|
.property(Attributes.ENABLED_PROPERTY)
|
||||||
.property(Attributes.ACTIVE_PROPERTY);
|
.property(Attributes.ACTIVE_PROPERTY)
|
||||||
|
.property(Attributes.EC_GENERATE_CERTIFICATE_PROPERTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -44,7 +45,8 @@ public abstract class AbstractEcKeyProviderFactory<T extends KeyProvider> implem
|
||||||
ConfigurationValidationHelper.check(model)
|
ConfigurationValidationHelper.check(model)
|
||||||
.checkLong(Attributes.PRIORITY_PROPERTY, false)
|
.checkLong(Attributes.PRIORITY_PROPERTY, false)
|
||||||
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
|
.checkBoolean(Attributes.ENABLED_PROPERTY, false)
|
||||||
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
|
.checkBoolean(Attributes.ACTIVE_PROPERTY, false)
|
||||||
|
.checkBoolean(Attributes.EC_GENERATE_CERTIFICATE_PROPERTY, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KeyPair generateEcKeyPair(String keySpecName) {
|
public static KeyPair generateEcKeyPair(String keySpecName) {
|
||||||
|
|
|
@ -55,6 +55,17 @@ public interface Attributes {
|
||||||
ProviderConfigProperty KEY_USE_PROPERTY = new ProviderConfigProperty(KEY_USE, "Key use", "Whether the key should be used for signing or encryption.", LIST_TYPE,
|
ProviderConfigProperty KEY_USE_PROPERTY = new ProviderConfigProperty(KEY_USE, "Key use", "Whether the key should be used for signing or encryption.", LIST_TYPE,
|
||||||
KeyUse.SIG.getSpecName(), KeyUse.SIG.getSpecName(), KeyUse.ENC.getSpecName());
|
KeyUse.SIG.getSpecName(), KeyUse.SIG.getSpecName(), KeyUse.ENC.getSpecName());
|
||||||
|
|
||||||
|
String EC_GENERATE_CERTIFICATE_KEY = "ecGenerateCertificate";
|
||||||
|
ProviderConfigProperty EC_GENERATE_CERTIFICATE_PROPERTY = new ProviderConfigProperty(
|
||||||
|
EC_GENERATE_CERTIFICATE_KEY,
|
||||||
|
"Generate Certificate",
|
||||||
|
"""
|
||||||
|
If a certificate should be build on creation. If the certificate is build, it will be available in the \
|
||||||
|
realm JWK for the key in the claim x5c and corresponding thumbprints may be available in the claims like \
|
||||||
|
x5t or x5t#S256.""",
|
||||||
|
BOOLEAN_TYPE,
|
||||||
|
false);
|
||||||
|
|
||||||
String KID_KEY = "kid";
|
String KID_KEY = "kid";
|
||||||
|
|
||||||
String SECRET_KEY = "secret";
|
String SECRET_KEY = "secret";
|
||||||
|
|
|
@ -18,6 +18,8 @@ package org.keycloak.keys;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.crypto.KeyUse;
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.crypto.KeyWrapper;
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
|
@ -27,8 +29,11 @@ import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class GeneratedEcdhKeyProvider extends AbstractEcKeyProvider {
|
public class GeneratedEcdhKeyProvider extends AbstractEcKeyProvider {
|
||||||
private static final Logger logger = Logger.getLogger(GeneratedEcdhKeyProvider.class);
|
private static final Logger logger = Logger.getLogger(GeneratedEcdhKeyProvider.class);
|
||||||
|
@ -42,6 +47,10 @@ public class GeneratedEcdhKeyProvider extends AbstractEcKeyProvider {
|
||||||
String privateEcdhKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_PRIVATE_KEY_KEY);
|
String privateEcdhKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_PRIVATE_KEY_KEY);
|
||||||
String publicEcdhKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_PUBLIC_KEY_KEY);
|
String publicEcdhKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_PUBLIC_KEY_KEY);
|
||||||
String ecdhAlgorithm = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_ALGORITHM_KEY);
|
String ecdhAlgorithm = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_ALGORITHM_KEY);
|
||||||
|
boolean generateCertificate = Optional.ofNullable(model.getConfig()
|
||||||
|
.getFirst(Attributes.EC_GENERATE_CERTIFICATE_KEY))
|
||||||
|
.map(Boolean::parseBoolean)
|
||||||
|
.orElse(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEcdhKeyBase64Encoded));
|
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEcdhKeyBase64Encoded));
|
||||||
|
@ -52,9 +61,20 @@ public class GeneratedEcdhKeyProvider extends AbstractEcKeyProvider {
|
||||||
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
|
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
|
||||||
|
|
||||||
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
|
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
|
||||||
|
X509Certificate selfSignedCertificate = Optional.ofNullable(model.getConfig()
|
||||||
|
.getFirst(Attributes.CERTIFICATE_KEY))
|
||||||
|
.map(PemUtils::decodeCertificate)
|
||||||
|
.orElse(null);
|
||||||
|
if (generateCertificate && selfSignedCertificate == null)
|
||||||
|
{
|
||||||
|
selfSignedCertificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
|
model.getConfig().put(Attributes.CERTIFICATE_KEY,
|
||||||
|
List.of(Base64.encodeBytes(selfSignedCertificate.getEncoded())));
|
||||||
|
}
|
||||||
|
|
||||||
return createKeyWrapper(keyPair, ecdhAlgorithm, KeyUse.ENC);
|
return createKeyWrapper(keyPair, ecdhAlgorithm, KeyUse.ENC, selfSignedCertificate);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.debug(e.getMessage(), e);
|
||||||
logger.warnf("Exception at decodeEcdhPublicKey. %s", e.toString());
|
logger.warnf("Exception at decodeEcdhPublicKey. %s", e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,17 +18,24 @@ package org.keycloak.keys;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
import org.keycloak.crypto.KeyUse;
|
import org.keycloak.crypto.KeyUse;
|
||||||
import org.keycloak.crypto.KeyWrapper;
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class GeneratedEcdsaKeyProvider extends AbstractEcKeyProvider {
|
public class GeneratedEcdsaKeyProvider extends AbstractEcKeyProvider {
|
||||||
private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProvider.class);
|
private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProvider.class);
|
||||||
|
@ -42,6 +49,10 @@ public class GeneratedEcdsaKeyProvider extends AbstractEcKeyProvider {
|
||||||
String privateEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PRIVATE_KEY_KEY);
|
String privateEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PRIVATE_KEY_KEY);
|
||||||
String publicEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY);
|
String publicEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY);
|
||||||
String ecInNistRep = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_ELLIPTIC_CURVE_KEY);
|
String ecInNistRep = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_ELLIPTIC_CURVE_KEY);
|
||||||
|
boolean generateCertificate = Optional.ofNullable(model.getConfig()
|
||||||
|
.getFirst(Attributes.EC_GENERATE_CERTIFICATE_KEY))
|
||||||
|
.map(Boolean::parseBoolean)
|
||||||
|
.orElse(false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEcdsaKeyBase64Encoded));
|
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEcdsaKeyBase64Encoded));
|
||||||
|
@ -52,9 +63,23 @@ public class GeneratedEcdsaKeyProvider extends AbstractEcKeyProvider {
|
||||||
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
|
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
|
||||||
|
|
||||||
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
|
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
|
||||||
|
X509Certificate selfSignedCertificate = Optional.ofNullable(model.getConfig()
|
||||||
|
.getFirst(Attributes.CERTIFICATE_KEY))
|
||||||
|
.map(PemUtils::decodeCertificate)
|
||||||
|
.orElse(null);
|
||||||
|
if (generateCertificate && selfSignedCertificate == null)
|
||||||
|
{
|
||||||
|
selfSignedCertificate = CertificateUtils.generateV1SelfSignedCertificate(keyPair, realm.getName());
|
||||||
|
model.getConfig().put(Attributes.CERTIFICATE_KEY,
|
||||||
|
List.of(Base64.encodeBytes(selfSignedCertificate.getEncoded())));
|
||||||
|
}
|
||||||
|
|
||||||
return createKeyWrapper(keyPair,
|
return createKeyWrapper(keyPair,
|
||||||
GeneratedEcdsaKeyProviderFactory.convertECDomainParmNistRepToJWSAlgorithm(ecInNistRep), KeyUse.SIG);
|
GeneratedEcdsaKeyProviderFactory.convertECDomainParmNistRepToJWSAlgorithm(
|
||||||
|
ecInNistRep), KeyUse.SIG,
|
||||||
|
selfSignedCertificate);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.debug(e.getMessage(), e);
|
||||||
logger.warnf("Exception at decodeEcdsaPublicKey. %s", e.toString());
|
logger.warnf("Exception at decodeEcdsaPublicKey. %s", e.toString());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.keycloak.jose.jwk.JWK;
|
||||||
import org.keycloak.jose.jwk.JWKBuilder;
|
import org.keycloak.jose.jwk.JWKBuilder;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.wellknown.WellKnownProvider;
|
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -41,11 +40,12 @@ import java.util.Optional;
|
||||||
JWKBuilder b = JWKBuilder.create().kid(k.getKid()).algorithm(k.getAlgorithmOrDefault());
|
JWKBuilder b = JWKBuilder.create().kid(k.getKid()).algorithm(k.getAlgorithmOrDefault());
|
||||||
List<X509Certificate> certificates = Optional.ofNullable(k.getCertificateChain())
|
List<X509Certificate> certificates = Optional.ofNullable(k.getCertificateChain())
|
||||||
.filter(certs -> !certs.isEmpty())
|
.filter(certs -> !certs.isEmpty())
|
||||||
.orElseGet(() -> Collections.singletonList(k.getCertificate()));
|
.orElseGet(() -> Optional.ofNullable(k.getCertificate()).map(Collections::singletonList)
|
||||||
|
.orElseGet(Collections::emptyList));
|
||||||
if (k.getType().equals(KeyType.RSA)) {
|
if (k.getType().equals(KeyType.RSA)) {
|
||||||
return b.rsa(k.getPublicKey(), certificates, k.getUse());
|
return b.rsa(k.getPublicKey(), certificates, k.getUse());
|
||||||
} else if (k.getType().equals(KeyType.EC)) {
|
} else if (k.getType().equals(KeyType.EC)) {
|
||||||
return b.ec(k.getPublicKey(), k.getUse());
|
return b.ec(k.getPublicKey(), certificates, k.getUse());
|
||||||
} else if (k.getType().equals(KeyType.OKP)) {
|
} else if (k.getType().equals(KeyType.OKP)) {
|
||||||
return b.okp(k.getPublicKey(), k.getUse());
|
return b.okp(k.getPublicKey(), k.getUse());
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.eclipse.microprofile.openapi.annotations.Operation;
|
||||||
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
|
||||||
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
|
||||||
import org.jboss.resteasy.reactive.NoCache;
|
import org.jboss.resteasy.reactive.NoCache;
|
||||||
|
import org.keycloak.common.util.Base64;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.crypto.KeyWrapper;
|
import org.keycloak.crypto.KeyWrapper;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
@ -33,6 +34,7 @@ import jakarta.ws.rs.GET;
|
||||||
import jakarta.ws.rs.Produces;
|
import jakarta.ws.rs.Produces;
|
||||||
import jakarta.ws.rs.core.MediaType;
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import java.security.cert.CertificateEncodingException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -90,6 +92,21 @@ public class KeyResource {
|
||||||
r.setType(key.getType());
|
r.setType(key.getType());
|
||||||
r.setAlgorithm(key.getAlgorithmOrDefault());
|
r.setAlgorithm(key.getAlgorithmOrDefault());
|
||||||
r.setPublicKey(key.getPublicKey() != null ? PemUtils.encodeKey(key.getPublicKey()) : null);
|
r.setPublicKey(key.getPublicKey() != null ? PemUtils.encodeKey(key.getPublicKey()) : null);
|
||||||
|
if (key.getCertificate() != null ||
|
||||||
|
(key.getCertificateChain() != null && !key.getCertificateChain().isEmpty())) {
|
||||||
|
try {
|
||||||
|
final String base64Certificate;
|
||||||
|
if (key.getCertificate() != null) {
|
||||||
|
base64Certificate = Base64.encodeBytes(key.getCertificate().getEncoded());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
base64Certificate = Base64.encodeBytes(key.getCertificateChain().get(0).getEncoded());
|
||||||
|
}
|
||||||
|
r.setCertificate(base64Certificate);
|
||||||
|
} catch (CertificateEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
r.setUse(key.getUse());
|
r.setUse(key.getUse());
|
||||||
|
|
||||||
X509Certificate cert = key.getCertificate();
|
X509Certificate cert = key.getCertificate();
|
||||||
|
|
|
@ -22,6 +22,7 @@ import static org.junit.Assert.assertNotNull;
|
||||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||||
|
|
||||||
import java.security.KeyFactory;
|
import java.security.KeyFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.security.interfaces.ECPublicKey;
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -34,6 +35,7 @@ import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Base64;
|
import org.keycloak.common.util.Base64;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
|
import org.keycloak.common.util.PemUtils;
|
||||||
import org.keycloak.crypto.KeyType;
|
import org.keycloak.crypto.KeyType;
|
||||||
import org.keycloak.keys.Attributes;
|
import org.keycloak.keys.Attributes;
|
||||||
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
|
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
|
||||||
|
@ -64,28 +66,49 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
public void addTestRealms(List<RealmRepresentation> testRealms) {
|
||||||
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
|
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"),
|
||||||
|
RealmRepresentation.class);
|
||||||
testRealms.add(realm);
|
testRealms.add(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultEc() {
|
public void defaultEc() {
|
||||||
supportedEc(null);
|
supportedEc(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void defaultEcWithCertificate() {
|
||||||
|
supportedEc(null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportedEcP521() {
|
public void supportedEcP521() {
|
||||||
supportedEc("P-521");
|
supportedEc("P-521", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportedEcP521WithCertificate() {
|
||||||
|
supportedEc("P-521", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportedEcP384() {
|
public void supportedEcP384() {
|
||||||
supportedEc("P-384");
|
supportedEc("P-384", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportedEcP384WithCertificate() {
|
||||||
|
supportedEc("P-384", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void supportedEcP256() {
|
public void supportedEcP256() {
|
||||||
supportedEc("P-256");
|
supportedEc("P-256", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void supportedEcP256AndCertificate() {
|
||||||
|
supportedEc("P-256", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,7 +117,7 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
unsupportedEc("K-163");
|
unsupportedEc("K-163");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String supportedEc(String ecInNistRep) {
|
private String supportedEc(String ecInNistRep, boolean withCertificate) {
|
||||||
long priority = System.currentTimeMillis();
|
long priority = System.currentTimeMillis();
|
||||||
|
|
||||||
ComponentRepresentation rep = createRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
|
ComponentRepresentation rep = createRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
|
||||||
|
@ -102,9 +125,13 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
|
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
|
||||||
if (ecInNistRep != null) {
|
if (ecInNistRep != null) {
|
||||||
rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
|
rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
ecInNistRep = DEFAULT_EC;
|
ecInNistRep = DEFAULT_EC;
|
||||||
}
|
}
|
||||||
|
if (withCertificate) {
|
||||||
|
rep.getConfig().putSingle(Attributes.EC_GENERATE_CERTIFICATE_KEY, "true");
|
||||||
|
}
|
||||||
|
|
||||||
Response response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
|
Response response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
|
||||||
String id = ApiUtil.getCreatedId(response);
|
String id = ApiUtil.getCreatedId(response);
|
||||||
|
@ -114,9 +141,12 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
ComponentRepresentation createdRep = adminClient.realm(TEST_REALM_NAME).components().component(id).toRepresentation();
|
ComponentRepresentation createdRep = adminClient.realm(TEST_REALM_NAME).components().component(id).toRepresentation();
|
||||||
|
|
||||||
// stands for the number of properties in the key provider config
|
// stands for the number of properties in the key provider config
|
||||||
assertEquals(2, createdRep.getConfig().size());
|
assertEquals(withCertificate ? 3 : 2, createdRep.getConfig().size());
|
||||||
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst(Attributes.PRIORITY_KEY));
|
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst(Attributes.PRIORITY_KEY));
|
||||||
assertEquals(ecInNistRep, createdRep.getConfig().getFirst(ECDSA_ELLIPTIC_CURVE_KEY));
|
assertEquals(ecInNistRep, createdRep.getConfig().getFirst(ECDSA_ELLIPTIC_CURVE_KEY));
|
||||||
|
if (withCertificate) {
|
||||||
|
assertNotNull(createdRep.getConfig().getFirst(Attributes.EC_GENERATE_CERTIFICATE_KEY));
|
||||||
|
}
|
||||||
|
|
||||||
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
|
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
|
||||||
|
|
||||||
|
@ -133,6 +163,13 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
assertEquals(id, key.getProviderId());
|
assertEquals(id, key.getProviderId());
|
||||||
assertEquals(KeyType.EC, key.getType());
|
assertEquals(KeyType.EC, key.getType());
|
||||||
assertEquals(priority, key.getProviderPriority());
|
assertEquals(priority, key.getProviderPriority());
|
||||||
|
if (withCertificate) {
|
||||||
|
assertNotNull(key.getCertificate());
|
||||||
|
X509Certificate certificate = PemUtils.decodeCertificate(key.getCertificate());
|
||||||
|
final String expectedIssuerAndSubject = "CN=" + TEST_REALM_NAME;
|
||||||
|
assertEquals(expectedIssuerAndSubject, certificate.getIssuerX500Principal().getName());
|
||||||
|
assertEquals(expectedIssuerAndSubject, certificate.getSubjectX500Principal().getName());
|
||||||
|
}
|
||||||
|
|
||||||
return id; // created key's component id
|
return id; // created key's component id
|
||||||
}
|
}
|
||||||
|
@ -176,7 +213,7 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void changeCurve(String FromEcInNistRep, String ToEcInNistRep) throws Exception {
|
private void changeCurve(String FromEcInNistRep, String ToEcInNistRep) throws Exception {
|
||||||
String keyComponentId = supportedEc(FromEcInNistRep);
|
String keyComponentId = supportedEc(FromEcInNistRep, false);
|
||||||
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
|
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
|
||||||
KeysMetadataRepresentation.KeyMetadataRepresentation originalKey = null;
|
KeysMetadataRepresentation.KeyMetadataRepresentation originalKey = null;
|
||||||
for (KeyMetadataRepresentation k : keys.getKeys()) {
|
for (KeyMetadataRepresentation k : keys.getKeys()) {
|
||||||
|
|
Loading…
Reference in a new issue