Add ECDH-ES JWE Algorithm Provider, Add generated ECDH key provider (#23928)

Closes #23596
Closes #23597

Signed-off-by: Justin Tay <49700559+justin-tay@users.noreply.github.com>
This commit is contained in:
Justin Tay 2024-08-08 23:29:35 +08:00 committed by GitHub
parent 5b83a7993c
commit 966a454548
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 2650 additions and 180 deletions

View file

@ -10,6 +10,10 @@ public class CryptoConstants {
public static final String RSA1_5 = "RSA1_5";
public static final String RSA_OAEP = "RSA-OAEP";
public static final String RSA_OAEP_256 = "RSA-OAEP-256";
public static final String ECDH_ES = "ECDH-ES";
public static final String ECDH_ES_A128KW = "ECDH-ES+A128KW";
public static final String ECDH_ES_A192KW = "ECDH-ES+A192KW";
public static final String ECDH_ES_A256KW = "ECDH-ES+A256KW";
// Constant for the OCSP provider
// public static final String OCSP = "OCSP";

View file

@ -49,4 +49,9 @@ public interface Algorithm {
/* AES */
String AES = "AES";
String ECDH_ES = CryptoConstants.ECDH_ES;
String ECDH_ES_A128KW = CryptoConstants.ECDH_ES_A128KW;
String ECDH_ES_A192KW = CryptoConstants.ECDH_ES_A192KW;
String ECDH_ES_A256KW = CryptoConstants.ECDH_ES_A256KW;
}

View file

@ -20,6 +20,7 @@ package org.keycloak.jose.jwe;
import org.keycloak.common.util.Base64Url;
import org.keycloak.jose.JOSE;
import org.keycloak.jose.JOSEHeader;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
import org.keycloak.util.JsonSerialization;
@ -143,8 +144,10 @@ public class JWE implements JOSE {
keyStorage.setEncryptionProvider(encryptionProvider);
keyStorage.getCEKKey(JWEKeyStorage.KeyUse.ENCRYPTION, true); // Will generate CEK if it's not already present
byte[] encodedCEK = algorithmProvider.encodeCek(encryptionProvider, keyStorage, keyStorage.getEncryptionKey());
JWEHeaderBuilder headerBuilder = header.toBuilder();
byte[] encodedCEK = algorithmProvider.encodeCek(encryptionProvider, keyStorage, keyStorage.getEncryptionKey(), headerBuilder);
base64Cek = Base64Url.encode(encodedCEK);
header = headerBuilder.build();
encryptionProvider.encodeJwe(this);
@ -191,7 +194,7 @@ public class JWE implements JOSE {
keyStorage.setEncryptionProvider(encryptionProvider);
byte[] decodedCek = algorithmProvider.decodeCek(Base64Url.decode(base64Cek), keyStorage.getDecryptionKey());
byte[] decodedCek = algorithmProvider.decodeCek(Base64Url.decode(base64Cek), keyStorage.getDecryptionKey(), this.header, encryptionProvider);
keyStorage.setCEKBytes(decodedCek);
encryptionProvider.verifyAndDecodeJwe(this);

View file

@ -29,6 +29,10 @@ public class JWEConstants {
public static final String RSA1_5 = CryptoConstants.RSA1_5;
public static final String RSA_OAEP = CryptoConstants.RSA_OAEP;
public static final String RSA_OAEP_256 = CryptoConstants.RSA_OAEP_256;
public static final String ECDH_ES = CryptoConstants.ECDH_ES;
public static final String ECDH_ES_A128KW = CryptoConstants.ECDH_ES_A128KW;
public static final String ECDH_ES_A192KW = CryptoConstants.ECDH_ES_A192KW;
public static final String ECDH_ES_A256KW = CryptoConstants.ECDH_ES_A256KW;
public static final String A128CBC_HS256 = "A128CBC-HS256";
public static final String A192CBC_HS384 = "A192CBC-HS384";

View file

@ -18,6 +18,7 @@
package org.keycloak.jose.jwe;
import java.io.IOException;
import java.io.UncheckedIOException;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@ -25,6 +26,7 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.keycloak.jose.JOSEHeader;
import org.keycloak.jose.jwk.ECPublicJWK;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -41,7 +43,6 @@ public class JWEHeader implements JOSEHeader {
@JsonProperty("zip")
private String compressionAlgorithm;
@JsonProperty("typ")
private String type;
@ -51,6 +52,15 @@ public class JWEHeader implements JOSEHeader {
@JsonProperty("kid")
private String keyId;
@JsonProperty("epk")
private ECPublicJWK ephemeralPublicKey;
@JsonProperty("apu")
private String agreementPartyUInfo;
@JsonProperty("apv")
private String agreementPartyVInfo;
public JWEHeader() {
}
@ -75,6 +85,19 @@ public class JWEHeader implements JOSEHeader {
this.contentType = contentType;
}
public JWEHeader(String algorithm, String encryptionAlgorithm, String compressionAlgorithm, String keyId, String contentType,
String type, ECPublicJWK ephemeralPublicKey, String agreementPartyUInfo, String agreementPartyVInfo) {
this.algorithm = algorithm;
this.encryptionAlgorithm = encryptionAlgorithm;
this.compressionAlgorithm = compressionAlgorithm;
this.keyId = keyId;
this.type = type;
this.contentType = contentType;
this.ephemeralPublicKey = ephemeralPublicKey;
this.agreementPartyUInfo = agreementPartyUInfo;
this.agreementPartyVInfo = agreementPartyVInfo;
}
public String getAlgorithm() {
return algorithm;
}
@ -105,21 +128,102 @@ public class JWEHeader implements JOSEHeader {
return keyId;
}
public ECPublicJWK getEphemeralPublicKey() {
return ephemeralPublicKey;
}
public String getAgreementPartyUInfo() {
return agreementPartyUInfo;
}
public String getAgreementPartyVInfo() {
return agreementPartyVInfo;
}
private static final ObjectMapper mapper = new ObjectMapper();
static {
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
public String toString() {
try {
return mapper.writeValueAsString(this);
} catch (IOException e) {
throw new RuntimeException(e);
throw new UncheckedIOException(e);
}
}
public JWEHeaderBuilder toBuilder() {
return builder().algorithm(algorithm).encryptionAlgorithm(encryptionAlgorithm)
.compressionAlgorithm(compressionAlgorithm).type(type).contentType(contentType)
.keyId(keyId).ephemeralPublicKey(ephemeralPublicKey).agreementPartyUInfo(agreementPartyUInfo)
.agreementPartyVInfo(agreementPartyVInfo);
}
public static JWEHeaderBuilder builder() {
return new JWEHeaderBuilder();
}
public static class JWEHeaderBuilder {
private String algorithm = null;
private String encryptionAlgorithm = null;
private String compressionAlgorithm = null;
private String type = null;
private String contentType = null;
private String keyId = null;
private ECPublicJWK ephemeralPublicKey = null;
private String agreementPartyUInfo = null;
private String agreementPartyVInfo = null;
public JWEHeaderBuilder algorithm(String algorithm) {
this.algorithm = algorithm;
return this;
}
public JWEHeaderBuilder encryptionAlgorithm(String encryptionAlgorithm) {
this.encryptionAlgorithm = encryptionAlgorithm;
return this;
}
public JWEHeaderBuilder compressionAlgorithm(String compressionAlgorithm) {
this.compressionAlgorithm = compressionAlgorithm;
return this;
}
public JWEHeaderBuilder type(String type) {
this.type = type;
return this;
}
public JWEHeaderBuilder contentType(String contentType) {
this.contentType = contentType;
return this;
}
public JWEHeaderBuilder keyId(String keyId) {
this.keyId = keyId;
return this;
}
public JWEHeaderBuilder ephemeralPublicKey(ECPublicJWK ephemeralPublicKey) {
this.ephemeralPublicKey = ephemeralPublicKey;
return this;
}
public JWEHeaderBuilder agreementPartyUInfo(String agreementPartyUInfo) {
this.agreementPartyUInfo = agreementPartyUInfo;
return this;
}
public JWEHeaderBuilder agreementPartyVInfo(String agreementPartyVInfo) {
this.agreementPartyVInfo = agreementPartyVInfo;
return this;
}
public JWEHeader build() {
return new JWEHeader(algorithm, encryptionAlgorithm, compressionAlgorithm, keyId, contentType,
type, ephemeralPublicKey, agreementPartyUInfo, agreementPartyVInfo);
}
}
}

View file

@ -38,6 +38,8 @@ class JWERegistry {
private static final Map<String, JWEEncryptionProvider> ENC_PROVIDERS = new HashMap<>();
static {
ENC_PROVIDERS.put(JWEConstants.A128GCM, new AesGcmJWEEncryptionProvider(JWEConstants.A128GCM));
ENC_PROVIDERS.put(JWEConstants.A192GCM, new AesGcmJWEEncryptionProvider(JWEConstants.A192GCM));
ENC_PROVIDERS.put(JWEConstants.A256GCM, new AesGcmJWEEncryptionProvider(JWEConstants.A256GCM));
ENC_PROVIDERS.put(JWEConstants.A128CBC_HS256, new AesCbcHmacShaEncryptionProvider.Aes128CbcHmacSha256Provider());
ENC_PROVIDERS.put(JWEConstants.A192CBC_HS384, new AesCbcHmacShaEncryptionProvider.Aes192CbcHmacSha384Provider());

View file

@ -19,6 +19,8 @@ package org.keycloak.jose.jwe.alg;
import java.security.Key;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -28,12 +30,12 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class DirectAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) {
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) {
return new byte[0];
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey, JWEHeaderBuilder headerBuilder) {
return new byte[0];
}
}

View file

@ -19,7 +19,9 @@ package org.keycloak.jose.jwe.alg;
import java.security.Key;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
/**
@ -27,8 +29,8 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
*/
public interface JWEAlgorithmProvider {
byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception;
byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception;
byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception;
byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey, JWEHeaderBuilder headerBuilder) throws Exception;
}

View file

@ -22,6 +22,8 @@ import java.security.Key;
import org.bouncycastle.crypto.Wrapper;
import org.bouncycastle.crypto.engines.AESWrapEngine;
import org.bouncycastle.crypto.params.KeyParameter;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -32,14 +34,14 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(false, new KeyParameter(encryptionKey.getEncoded()));
return encrypter.unwrap(encodedCek, 0, encodedCek.length);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey, JWEHeaderBuilder headerBuilder) throws Exception {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(true, new KeyParameter(encryptionKey.getEncoded()));
byte[] cekBytes = keyStorage.getCekBytes();

View file

@ -0,0 +1,278 @@
/*
* 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.crypto.def;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.KeyAgreement;
import org.bouncycastle.crypto.Wrapper;
import org.bouncycastle.crypto.agreement.kdf.ConcatenationKDFGenerator;
import org.bouncycastle.crypto.engines.AESWrapEngine;
import org.bouncycastle.crypto.params.KDFParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.util.DigestFactory;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
import org.keycloak.jose.jwk.ECPublicJWK;
import org.keycloak.jose.jwk.JWKUtil;
/**
* ECDH Ephemeral Static Algorithm Provider.
*
* @author Justin Tay
* @see <a href=
* "https://datatracker.ietf.org/doc/html/rfc7518#section-4.6.2">Key
* Derivation for ECDH Key Agreement</a>
*/
public class BCEcdhEsAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header,
JWEEncryptionProvider encryptionProvider) throws Exception {
int keyDataLength = getKeyDataLength(header.getAlgorithm(), encryptionProvider);
PublicKey sharedPublicKey = toPublicKey(header.getEphemeralPublicKey());
String algorithmID = getAlgorithmID(header.getAlgorithm(), header.getEncryptionAlgorithm());
byte[] derivedKey = deriveKey(sharedPublicKey, encryptionKey, keyDataLength, algorithmID,
base64UrlDecode(header.getAgreementPartyUInfo()), base64UrlDecode(header.getAgreementPartyVInfo()));
if (Algorithm.ECDH_ES.equals(header.getAlgorithm())) {
return derivedKey;
} else {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(false, new KeyParameter(derivedKey));
return encrypter.unwrap(encodedCek, 0, encodedCek.length);
}
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey,
JWEHeaderBuilder headerBuilder) throws Exception {
JWEHeader header = headerBuilder.build();
int keyDataLength = getKeyDataLength(header.getAlgorithm(), encryptionProvider);
ECParameterSpec params = ((ECPublicKey) encryptionKey).getParams();
KeyPair ephemeralKeyPair = generateEcKeyPair(params);
ECPublicKey ephemeralPublicKey = (ECPublicKey) ephemeralKeyPair.getPublic();
ECPrivateKey ephemeralPrivateKey = (ECPrivateKey) ephemeralKeyPair.getPrivate();
byte[] agreementPartyUInfo = header.getAgreementPartyUInfo() != null
? base64UrlDecode(header.getAgreementPartyUInfo())
: new byte[0];
byte[] agreementPartyVInfo = header.getAgreementPartyVInfo() != null
? base64UrlDecode(header.getAgreementPartyVInfo())
: new byte[0];
headerBuilder.ephemeralPublicKey(toECPublicJWK(ephemeralPublicKey));
String algorithmID = getAlgorithmID(header.getAlgorithm(), header.getEncryptionAlgorithm());
byte[] derivedKey = deriveKey(encryptionKey, ephemeralPrivateKey, keyDataLength, algorithmID,
agreementPartyUInfo, agreementPartyVInfo);
if (Algorithm.ECDH_ES.equals(header.getAlgorithm())) {
keyStorage.setCEKBytes(derivedKey);
encryptionProvider.deserializeCEK(keyStorage);
return new byte[0];
} else {
Wrapper encrypter = new AESWrapEngine();
encrypter.init(true, new KeyParameter(derivedKey));
byte[] cekBytes = keyStorage.getCekBytes();
return encrypter.wrap(cekBytes, 0, cekBytes.length);
}
}
private byte[] base64UrlDecode(String encoded) {
return Base64Url.decode(encoded == null ? "" : encoded);
}
private static KeyPair generateEcKeyPair(ECParameterSpec params) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(params, randomGen);
return keyGen.generateKeyPair();
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] deriveOtherInfo(int keyDataLength, String algorithmID, byte[] agreementPartyUInfo,
byte[] agreementPartyVInfo) {
byte[] algorithmId = encodeDataLengthData(algorithmID.getBytes(Charset.forName("ASCII")));
byte[] partyUInfo = encodeDataLengthData(agreementPartyUInfo);
byte[] partyVInfo = encodeDataLengthData(agreementPartyVInfo);
byte[] suppPubInfo = toByteArray(keyDataLength);
byte[] suppPrivInfo = emptyBytes();
return concat(algorithmId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
}
public static byte[] deriveKey(Key publicKey, Key privateKey, int keyDataLength, String algorithmID,
byte[] agreementPartyUInfo, byte[] agreementPartyVInfo)
throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
byte[] z = deriveSharedSecret(publicKey, privateKey);
byte[] otherInfo = deriveOtherInfo(keyDataLength, algorithmID, agreementPartyUInfo, agreementPartyVInfo);
KDFParameters param = new KDFParameters(z, otherInfo);
ConcatenationKDFGenerator concatKdf = new ConcatenationKDFGenerator(DigestFactory.createSHA256());
concatKdf.init(param);
int derivedKeyLength = keyDataLength / 8;
byte[] derivedKeyBytes = new byte[derivedKeyLength];
concatKdf.generateBytes(derivedKeyBytes, 0, derivedKeyLength);
return derivedKeyBytes;
}
private static ECPublicJWK toECPublicJWK(ECPublicKey ecKey) {
ECPublicJWK k = new ECPublicJWK();
int fieldSize = ecKey.getParams().getCurve().getField().getFieldSize();
k.setCrv("P-" + fieldSize);
k.setKeyType(KeyType.EC);
k.setX(Base64Url.encode(JWKUtil.toIntegerBytes(ecKey.getW().getAffineX(), fieldSize)));
k.setY(Base64Url.encode(JWKUtil.toIntegerBytes(ecKey.getW().getAffineY(), fieldSize)));
return k;
}
private static PublicKey toPublicKey(ECPublicJWK jwk) {
String crv = jwk.getCrv();
String xStr = jwk.getX();
String yStr = jwk.getY();
if (crv == null) {
throw new IllegalArgumentException("JWK crv must be set");
}
if (xStr == null) {
throw new IllegalArgumentException("JWK x must be set");
}
if (yStr == null) {
throw new IllegalArgumentException("JWK y must be set");
}
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
String name = nistToSecCurveName(crv);
try {
ECPoint point = new ECPoint(x, y);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(name);
ECParameterSpec params = new ECNamedCurveSpec(name, spec.getCurve(), spec.getG(), spec.getN());
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePublic(pubKeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] deriveSharedSecret(Key publicKey, Key privateKey)
throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret();
}
private static String getAlgorithmID(String alg, String enc) {
if (Algorithm.ECDH_ES_A128KW.equals(alg) || Algorithm.ECDH_ES_A192KW.equals(alg)
|| Algorithm.ECDH_ES_A256KW.equals(alg)) {
return alg;
} else if (Algorithm.ECDH_ES.equals(alg)) {
return enc;
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
private static String nistToSecCurveName(String nistCurveName) {
switch (nistCurveName) {
case "P-256":
return "secp256r1";
case "P-384":
return "secp384r1";
case "P-521":
return "secp521r1";
default:
throw new IllegalArgumentException("Unsupported curve");
}
}
private static int getKeyDataLength(String alg, JWEEncryptionProvider encryptionProvider) {
if (Algorithm.ECDH_ES_A128KW.equals(alg)) {
return 128;
} else if (Algorithm.ECDH_ES_A192KW.equals(alg)) {
return 192;
} else if (Algorithm.ECDH_ES_A256KW.equals(alg)) {
return 256;
} else if (Algorithm.ECDH_ES.equals(alg)) {
return encryptionProvider.getExpectedCEKLength() * 8;
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
private static byte[] encodeDataLengthData(final byte[] data) {
byte[] databytes = data != null ? data : new byte[0];
byte[] datalen = toByteArray(databytes.length);
return concat(datalen, databytes);
}
private static byte[] emptyBytes() {
return new byte[0];
}
private static byte[] toByteArray(int intValue) {
return new byte[] { (byte) (intValue >> 24), (byte) (intValue >> 16), (byte) (intValue >> 8), (byte) intValue };
}
private static byte[] concat(byte[]... byteArrays) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
for (byte[] bytes : byteArrays) {
if (bytes != null) {
baos.write(bytes);
}
}
return baos.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View file

@ -60,6 +60,10 @@ public class DefaultCryptoProvider implements CryptoProvider {
providers.put(CryptoConstants.RSA1_5, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding"));
providers.put(CryptoConstants.RSA_OAEP, new DefaultRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"));
providers.put(CryptoConstants.RSA_OAEP_256, new DefaultRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"));
providers.put(CryptoConstants.ECDH_ES, new BCEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A128KW, new BCEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A192KW, new BCEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A256KW, new BCEcdhEsAlgorithmProvider());
if (existingBc == null) {
Security.addProvider(this.bcProvider);

View file

@ -3,7 +3,9 @@ package org.keycloak.crypto.def;
import java.security.Key;
import javax.crypto.Cipher;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -16,14 +18,14 @@ public class DefaultRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithm
}
@Override
public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
public byte[] decodeCek(byte[] encodedCek, Key privateKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception {
Cipher cipher = getCipherProvider();
initCipher(cipher, Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encodedCek);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey, JWEHeaderBuilder headerBuilder) throws Exception {
Cipher cipher = getCipherProvider();
initCipher(cipher, Cipher.ENCRYPT_MODE, publicKey);
byte[] cekBytes = keyStorage.getCekBytes();

View file

@ -0,0 +1,142 @@
/*
* 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.crypto.def.test;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Base64Url;
import org.keycloak.common.util.Environment;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.def.BCEcdhEsAlgorithmProvider;
import org.keycloak.jose.jwe.JWE;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.rule.CryptoInitRule;
public class BCEcdhEsAlgorithmProviderTest {
@Before
public void before() {
// Run this test just if java is not in FIPS mode
Assume.assumeFalse("Java is in FIPS mode. Skipping the test.", Environment.isJavaInFipsMode());
}
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
/**
* Test ECDH-ES Key Agreement Computation.
*
* @see <a href=
* "https://datatracker.ietf.org/doc/html/rfc7518#appendix-C">Example
* ECDH-ES Key Agreement Computation</a>
* @throws InvalidKeySpecException exception
* @throws NoSuchAlgorithmException exception
* @throws NoSuchProviderException exception
* @throws IllegalStateException exception
* @throws InvalidKeyException exception
*/
@Test
public void deriveKey() throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException,
InvalidKeyException, IllegalStateException {
PrivateKey ephemeralPrivateKey = getPrivateKey("P-256", "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo");
PublicKey encryptionPublicKey = getPublicKey("P-256", "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
byte[] derivedKey = BCEcdhEsAlgorithmProvider.deriveKey(encryptionPublicKey, ephemeralPrivateKey, 128,
"A128GCM", Base64Url.decode("QWxpY2U"), Base64Url.decode("Qm9i"));
Assert.assertEquals("VqqN6vgjbSBcIijNcacQGg", Base64Url.encode(derivedKey));
}
@Test
public void encodeDecode()
throws JWEException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
PublicKey encryptionPublicKey = getPublicKey("P-256", "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
String content = "plaintext";
JWE jweEncode = new JWE()
.header(JWEHeader.builder().algorithm(Algorithm.ECDH_ES_A128KW)
.encryptionAlgorithm(JWEConstants.A128CBC_HS256)
.build())
.content(content.getBytes(StandardCharsets.UTF_8));
jweEncode.getKeyStorage().setEncryptionKey(encryptionPublicKey);
String encodedJwe = jweEncode.encodeJwe();
PrivateKey decryptionPrivateKey = getPrivateKey("P-256", "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw");
JWE jweDecode = new JWE();
jweDecode.getKeyStorage().setDecryptionKey(decryptionPrivateKey);
jweDecode = jweDecode.verifyAndDecodeJwe(encodedJwe);
Assert.assertArrayEquals(jweEncode.getContent(), jweDecode.getContent());
}
private PublicKey getPublicKey(String crv, String xStr, String yStr)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
ECPoint point = new ECPoint(x, y);
String name = nistToSecCurveName(crv);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(name);
ECParameterSpec params = new ECNamedCurveSpec(name, spec.getCurve(), spec.getG(), spec.getN());
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePublic(pubKeySpec);
}
private PrivateKey getPrivateKey(String crv, String dStr)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
BigInteger d = new BigInteger(1, Base64Url.decode(dStr));
String name = nistToSecCurveName(crv);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(name);
ECParameterSpec params = new ECNamedCurveSpec(name, spec.getCurve(), spec.getG(), spec.getN());
ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(d, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
return keyFactory.generatePrivate(privKeySpec);
}
private static String nistToSecCurveName(String nistCurveName) {
switch (nistCurveName) {
case "P-256":
return "secp256r1";
case "P-384":
return "secp384r1";
case "P-521":
return "secp521r1";
default:
throw new IllegalArgumentException("Unsupported curve");
}
}
}

View file

@ -20,7 +20,9 @@ import java.security.Key;
import javax.crypto.Cipher;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.JWEKeyStorage.KeyUse;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -31,14 +33,14 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class AesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception {
Cipher cipher = Cipher.getInstance("AESWrap_128");
cipher.init(Cipher.UNWRAP_MODE, encryptionKey);
return cipher.unwrap(encodedCek, "AES", Cipher.SECRET_KEY).getEncoded();
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey, JWEHeaderBuilder headerBuilder) throws Exception {
Cipher cipher = Cipher.getInstance("AESWrap_128");
cipher.init(Cipher.WRAP_MODE, encryptionKey);
return cipher.wrap(keyStorage.getCEKKey(KeyUse.ENCRYPTION, false));

View file

@ -0,0 +1,252 @@
/*
* 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.crypto.elytron;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import javax.crypto.Cipher;
import javax.crypto.KeyAgreement;
import javax.crypto.spec.SecretKeySpec;
import org.jose4j.jwe.kdf.ConcatKeyDerivationFunction;
import org.jose4j.keys.EcKeyUtil;
import org.jose4j.keys.EllipticCurves;
import org.jose4j.lang.JoseException;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
import org.keycloak.jose.jwk.ECPublicJWK;
import org.keycloak.jose.jwk.JWKUtil;
/**
* ECDH Ephemeral Static Algorithm Provider.
*
* @author Justin Tay
* @see <a href=
* "https://datatracker.ietf.org/doc/html/rfc7518#section-4.6.2">Key
* Derivation for ECDH Key Agreement</a>
*/
public class ElytronEcdhEsAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header,
JWEEncryptionProvider encryptionProvider) throws Exception {
int keyDataLength = getKeyDataLength(header.getAlgorithm(), encryptionProvider);
PublicKey sharedPublicKey = toPublicKey(header.getEphemeralPublicKey());
String algorithmID = getAlgorithmID(header.getAlgorithm(), header.getEncryptionAlgorithm());
byte[] derivedKey = deriveKey(sharedPublicKey, encryptionKey, keyDataLength, algorithmID,
base64UrlDecode(header.getAgreementPartyUInfo()), base64UrlDecode(header.getAgreementPartyVInfo()));
if (Algorithm.ECDH_ES.equals(header.getAlgorithm())) {
return derivedKey;
} else {
Cipher cipher = Cipher.getInstance(getAesWrapAlgorithm(header.getAlgorithm()));
cipher.init(Cipher.UNWRAP_MODE, new SecretKeySpec(derivedKey, "AES"));
return cipher.unwrap(encodedCek, "AES", Cipher.SECRET_KEY).getEncoded();
}
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey,
JWEHeaderBuilder headerBuilder) throws Exception {
JWEHeader header = headerBuilder.build();
int keyDataLength = getKeyDataLength(header.getAlgorithm(), encryptionProvider);
ECParameterSpec params = ((ECPublicKey) encryptionKey).getParams();
KeyPair ephemeralKeyPair = generateEcKeyPair(params);
ECPublicKey ephemeralPublicKey = (ECPublicKey) ephemeralKeyPair.getPublic();
ECPrivateKey ephemeralPrivateKey = (ECPrivateKey) ephemeralKeyPair.getPrivate();
byte[] agreementPartyUInfo = header.getAgreementPartyUInfo() != null
? base64UrlDecode(header.getAgreementPartyUInfo())
: new byte[0];
byte[] agreementPartyVInfo = header.getAgreementPartyVInfo() != null
? base64UrlDecode(header.getAgreementPartyVInfo())
: new byte[0];
headerBuilder.ephemeralPublicKey(toECPublicJWK(ephemeralPublicKey));
String algorithmID = getAlgorithmID(header.getAlgorithm(), header.getEncryptionAlgorithm());
byte[] derivedKey = deriveKey(encryptionKey, ephemeralPrivateKey, keyDataLength, algorithmID,
agreementPartyUInfo, agreementPartyVInfo);
if (Algorithm.ECDH_ES.equals(header.getAlgorithm())) {
keyStorage.setCEKBytes(derivedKey);
encryptionProvider.deserializeCEK(keyStorage);
return new byte[0];
} else {
Cipher cipher = Cipher.getInstance(getAesWrapAlgorithm(header.getAlgorithm()));
cipher.init(Cipher.WRAP_MODE, new SecretKeySpec(derivedKey, "AES"));
byte[] cekBytes = keyStorage.getCekBytes();
return cipher.wrap(new SecretKeySpec(cekBytes, "AES"));
}
}
private byte[] base64UrlDecode(String encoded) {
return Base64Url.decode(encoded == null ? "" : encoded);
}
private static KeyPair generateEcKeyPair(ECParameterSpec params) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(params, randomGen);
return keyGen.generateKeyPair();
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
}
public static byte[] deriveKey(Key publicKey, Key privateKey, int keyDataLength, String algorithmID,
byte[] agreementPartyUInfo, byte[] agreementPartyVInfo)
throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
byte[] z = deriveSharedSecret(publicKey, privateKey);
byte[] suppPrivInfo = emptyBytes();
ConcatKeyDerivationFunction concatKdf = new ConcatKeyDerivationFunction("SHA-256");
return concatKdf.kdf(z, keyDataLength, encodeDataLengthData(algorithmID.getBytes(Charset.forName("ASCII"))),
encodeDataLengthData(agreementPartyUInfo), encodeDataLengthData(agreementPartyVInfo),
toByteArray(keyDataLength), suppPrivInfo);
}
private static ECPublicJWK toECPublicJWK(ECPublicKey ecKey) {
ECPublicJWK k = new ECPublicJWK();
int fieldSize = ecKey.getParams().getCurve().getField().getFieldSize();
k.setCrv("P-" + fieldSize);
k.setKeyType(KeyType.EC);
k.setX(Base64Url.encode(JWKUtil.toIntegerBytes(ecKey.getW().getAffineX(), fieldSize)));
k.setY(Base64Url.encode(JWKUtil.toIntegerBytes(ecKey.getW().getAffineY(), fieldSize)));
return k;
}
private static PublicKey toPublicKey(ECPublicJWK jwk) {
String crv = jwk.getCrv();
String xStr = jwk.getX();
String yStr = jwk.getY();
if (crv == null) {
throw new IllegalArgumentException("JWK crv must be set");
}
if (xStr == null) {
throw new IllegalArgumentException("JWK x must be set");
}
if (yStr == null) {
throw new IllegalArgumentException("JWK y must be set");
}
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
EcKeyUtil ecKeyUtil = new EcKeyUtil();
try {
return ecKeyUtil.publicKey(x, y, EllipticCurves.getSpec(crv));
} catch (JoseException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] deriveSharedSecret(Key publicKey, Key privateKey)
throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException {
KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
keyAgreement.init(privateKey);
keyAgreement.doPhase(publicKey, true);
return keyAgreement.generateSecret();
}
private static String getAlgorithmID(String alg, String enc) {
if (Algorithm.ECDH_ES_A128KW.equals(alg) || Algorithm.ECDH_ES_A192KW.equals(alg)
|| Algorithm.ECDH_ES_A256KW.equals(alg)) {
return alg;
} else if (Algorithm.ECDH_ES.equals(alg)) {
return enc;
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
private static int getKeyDataLength(String alg, JWEEncryptionProvider encryptionProvider) {
if (Algorithm.ECDH_ES_A128KW.equals(alg)) {
return 128;
} else if (Algorithm.ECDH_ES_A192KW.equals(alg)) {
return 192;
} else if (Algorithm.ECDH_ES_A256KW.equals(alg)) {
return 256;
} else if (Algorithm.ECDH_ES.equals(alg)) {
return encryptionProvider.getExpectedCEKLength() * 8;
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
private static byte[] encodeDataLengthData(final byte[] data) {
byte[] databytes = data != null ? data : new byte[0];
byte[] datalen = toByteArray(databytes.length);
return concat(datalen, databytes);
}
private static byte[] emptyBytes() {
return new byte[0];
}
private static byte[] toByteArray(int intValue) {
return new byte[] { (byte) (intValue >> 24), (byte) (intValue >> 16), (byte) (intValue >> 8), (byte) intValue };
}
private static byte[] concat(byte[]... byteArrays) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
for (byte[] bytes : byteArrays) {
if (bytes != null) {
baos.write(bytes);
}
}
return baos.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static String getAesWrapAlgorithm(String alg) {
if (Algorithm.ECDH_ES_A128KW.equals(alg)) {
return "AESWrap_128";
} else if (Algorithm.ECDH_ES_A192KW.equals(alg)) {
return "AESWrap_192";
} else if (Algorithm.ECDH_ES_A256KW.equals(alg)) {
return "AESWrap_256";
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
}

View file

@ -20,7 +20,9 @@ import java.security.Key;
import javax.crypto.Cipher;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -36,14 +38,14 @@ public class ElytronRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithm
}
@Override
public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
public byte[] decodeCek(byte[] encodedCek, Key privateKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception {
Cipher cipher = getCipherProvider();
initCipher(cipher, Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encodedCek);
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey, JWEHeaderBuilder headerBuilder) throws Exception {
Cipher cipher = getCipherProvider();
initCipher(cipher, Cipher.ENCRYPT_MODE, publicKey);
byte[] cekBytes = keyStorage.getCekBytes();

View file

@ -61,6 +61,10 @@ public class WildFlyElytronProvider implements CryptoProvider {
providers.put(CryptoConstants.RSA1_5, new ElytronRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/PKCS1Padding"));
providers.put(CryptoConstants.RSA_OAEP, new ElytronRsaKeyEncryptionJWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-1AndMGF1Padding"));
providers.put(CryptoConstants.RSA_OAEP_256, new ElytronRsaKeyEncryption256JWEAlgorithmProvider("RSA/ECB/OAEPWithSHA-256AndMGF1Padding"));
providers.put(CryptoConstants.ECDH_ES, new ElytronEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A128KW, new ElytronEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A192KW, new ElytronEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A256KW, new ElytronEcdhEsAlgorithmProvider());
}
@Override

View file

@ -0,0 +1,104 @@
/*
* 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.crypto.elytron.test;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import org.jose4j.keys.EcKeyUtil;
import org.jose4j.keys.EllipticCurves;
import org.jose4j.lang.JoseException;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.elytron.ElytronEcdhEsAlgorithmProvider;
import org.keycloak.jose.jwe.JWE;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.rule.CryptoInitRule;
public class ElytronEcdhEsAlgorithmProviderTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
/**
* Test ECDH-ES Key Agreement Computation.
*
* @see <a href=
* "https://datatracker.ietf.org/doc/html/rfc7518#appendix-C">Example
* ECDH-ES Key Agreement Computation</a>
* @throws InvalidKeySpecException exception
* @throws NoSuchAlgorithmException exception
* @throws NoSuchProviderException exception
* @throws IllegalStateException exception
* @throws InvalidKeyException exception
*/
@Test
public void deriveKey() throws JoseException, InvalidKeyException, NoSuchAlgorithmException, IllegalStateException {
PrivateKey ephemeralPrivateKey = getPrivateKey("P-256", "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo");
PublicKey encryptionPublicKey = getPublicKey("P-256", "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
byte[] derivedKey = ElytronEcdhEsAlgorithmProvider.deriveKey(encryptionPublicKey, ephemeralPrivateKey, 128,
"A128GCM", Base64Url.decode("QWxpY2U"), Base64Url.decode("Qm9i"));
Assert.assertEquals("VqqN6vgjbSBcIijNcacQGg", Base64Url.encode(derivedKey));
}
@Test
public void encodeDecode()
throws JWEException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException,
JoseException {
PublicKey encryptionPublicKey = getPublicKey("P-256", "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
String content = "plaintext";
JWE jweEncode = new JWE()
.header(JWEHeader.builder().algorithm(Algorithm.ECDH_ES_A128KW)
.encryptionAlgorithm(JWEConstants.A128CBC_HS256)
.build())
.content(content.getBytes(StandardCharsets.UTF_8));
jweEncode.getKeyStorage().setEncryptionKey(encryptionPublicKey);
String encodedJwe = jweEncode.encodeJwe();
PrivateKey decryptionPrivateKey = getPrivateKey("P-256", "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw");
JWE jweDecode = new JWE();
jweDecode.getKeyStorage().setDecryptionKey(decryptionPrivateKey);
jweDecode = jweDecode.verifyAndDecodeJwe(encodedJwe);
Assert.assertArrayEquals(jweEncode.getContent(), jweDecode.getContent());
}
private PublicKey getPublicKey(String crv, String xStr, String yStr) throws JoseException {
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
EcKeyUtil ecKeyUtil = new EcKeyUtil();
return ecKeyUtil.publicKey(x, y, EllipticCurves.getSpec(crv));
}
private PrivateKey getPrivateKey(String crv, String dStr) throws JoseException {
BigInteger d = new BigInteger(1, Base64Url.decode(dStr));
EcKeyUtil ecKeyUtil = new EcKeyUtil();
return ecKeyUtil.privateKey(d, EllipticCurves.getSpec(crv));
}
}

View file

@ -0,0 +1,262 @@
/*
* 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.crypto.fips;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.KeyUnwrapper;
import org.bouncycastle.crypto.KeyWrapper;
import org.bouncycastle.crypto.SymmetricKey;
import org.bouncycastle.crypto.SymmetricSecretKey;
import org.bouncycastle.crypto.asymmetric.AsymmetricECPrivateKey;
import org.bouncycastle.crypto.asymmetric.AsymmetricECPublicKey;
import org.bouncycastle.crypto.asymmetric.ECDomainParameters;
import org.bouncycastle.crypto.fips.FipsAES;
import org.bouncycastle.crypto.fips.FipsAES.WrapParameters;
import org.bouncycastle.crypto.fips.FipsAgreement;
import org.bouncycastle.crypto.fips.FipsEC;
import org.bouncycastle.crypto.fips.FipsKDF;
import org.bouncycastle.crypto.fips.FipsKDF.AgreementKDFPRF;
import org.bouncycastle.jcajce.spec.ECDomainParameterSpec;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
import org.keycloak.jose.jwk.ECPublicJWK;
import org.keycloak.jose.jwk.JWKUtil;
/**
* ECDH Ephemeral Static Algorithm Provider.
*
* @author Justin Tay
* @see <a href=
* "https://datatracker.ietf.org/doc/html/rfc7518#section-4.6.2">Key
* Derivation for ECDH Key Agreement</a>
*/
public class BCFIPSEcdhEsAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header,
JWEEncryptionProvider encryptionProvider) throws Exception {
int keyDataLength = getKeyDataLength(header.getAlgorithm(), encryptionProvider);
PublicKey sharedPublicKey = toPublicKey(header.getEphemeralPublicKey());
String algorithmID = getAlgorithmID(header.getAlgorithm(), header.getEncryptionAlgorithm());
byte[] derivedKey = deriveKey(sharedPublicKey, encryptionKey, keyDataLength, algorithmID,
base64UrlDecode(header.getAgreementPartyUInfo()), base64UrlDecode(header.getAgreementPartyVInfo()));
if (Algorithm.ECDH_ES.equals(header.getAlgorithm())) {
return derivedKey;
} else {
SymmetricKey aesKey = new SymmetricSecretKey(FipsAES.KW, derivedKey);
FipsAES.KeyWrapOperatorFactory factory = new FipsAES.KeyWrapOperatorFactory();
KeyUnwrapper<WrapParameters> unwrapper = factory.createKeyUnwrapper(aesKey, FipsAES.KW);
return unwrapper.unwrap(encodedCek, 0, encodedCek.length);
}
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey,
JWEHeaderBuilder headerBuilder) throws Exception {
JWEHeader header = headerBuilder.build();
int keyDataLength = getKeyDataLength(header.getAlgorithm(), encryptionProvider);
ECParameterSpec params = ((ECPublicKey) encryptionKey).getParams();
KeyPair ephemeralKeyPair = generateEcKeyPair(params);
ECPublicKey ephemeralPublicKey = (ECPublicKey) ephemeralKeyPair.getPublic();
ECPrivateKey ephemeralPrivateKey = (ECPrivateKey) ephemeralKeyPair.getPrivate();
byte[] agreementPartyUInfo = header.getAgreementPartyUInfo() != null
? base64UrlDecode(header.getAgreementPartyUInfo())
: new byte[0];
byte[] agreementPartyVInfo = header.getAgreementPartyVInfo() != null
? base64UrlDecode(header.getAgreementPartyVInfo())
: new byte[0];
headerBuilder.ephemeralPublicKey(toECPublicJWK(ephemeralPublicKey));
String algorithmID = getAlgorithmID(header.getAlgorithm(), header.getEncryptionAlgorithm());
byte[] derivedKey = deriveKey(encryptionKey, ephemeralPrivateKey, keyDataLength, algorithmID,
agreementPartyUInfo, agreementPartyVInfo);
if (Algorithm.ECDH_ES.equals(header.getAlgorithm())) {
keyStorage.setCEKBytes(derivedKey);
encryptionProvider.deserializeCEK(keyStorage);
return new byte[0];
} else {
byte[] inputKeyBytes = keyStorage.getCekBytes(); // bytes making up the key to be wrapped
byte[] keyBytes = derivedKey; // bytes making up AES key doing the wrapping
SymmetricKey aesKey = new SymmetricSecretKey(FipsAES.KW, keyBytes);
FipsAES.KeyWrapOperatorFactory factory = new FipsAES.KeyWrapOperatorFactory();
KeyWrapper<WrapParameters> wrapper = factory.createKeyWrapper(aesKey, FipsAES.KW);
return wrapper.wrap(inputKeyBytes, 0, inputKeyBytes.length);
}
}
private byte[] base64UrlDecode(String encoded) {
return Base64Url.decode(encoded == null ? "" : encoded);
}
private static KeyPair generateEcKeyPair(ECParameterSpec params) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BCFIPS");
SecureRandom randomGen = SecureRandom.getInstance("DEFAULT", "BCFIPS");
keyGen.initialize(params, randomGen);
return keyGen.generateKeyPair();
} catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchProviderException e) {
throw new IllegalArgumentException(e);
}
}
private static byte[] deriveOtherInfo(int keyDataLength, String algorithmID, byte[] agreementPartyUInfo,
byte[] agreementPartyVInfo) {
byte[] algorithmId = encodeDataLengthData(algorithmID.getBytes(Charset.forName("ASCII")));
byte[] partyUInfo = encodeDataLengthData(agreementPartyUInfo);
byte[] partyVInfo = encodeDataLengthData(agreementPartyVInfo);
byte[] suppPubInfo = toByteArray(keyDataLength);
byte[] suppPrivInfo = emptyBytes();
return concat(algorithmId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo);
}
public static byte[] deriveKey(Key publicKey, Key privateKey, int keyDataLength, String algorithmID,
byte[] agreementPartyUInfo, byte[] agreementPartyVInfo) {
byte[] otherInfo = deriveOtherInfo(keyDataLength, algorithmID, agreementPartyUInfo, agreementPartyVInfo);
FipsEC.DHAgreementFactory factory = new FipsEC.DHAgreementFactory();
FipsAgreement<FipsEC.AgreementParameters> agree = factory.createAgreement(
new AsymmetricECPrivateKey(FipsEC.ALGORITHM, privateKey.getEncoded()),
FipsEC.DH.withKDF(FipsKDF.CONCATENATION.withPRF(AgreementKDFPRF.SHA256), otherInfo, keyDataLength / 8));
return agree.calculate(new AsymmetricECPublicKey(FipsEC.ALGORITHM, publicKey.getEncoded()));
}
private static ECPublicJWK toECPublicJWK(ECPublicKey ecKey) {
ECPublicJWK k = new ECPublicJWK();
int fieldSize = ecKey.getParams().getCurve().getField().getFieldSize();
k.setCrv("P-" + fieldSize);
k.setKeyType(KeyType.EC);
k.setX(Base64Url.encode(JWKUtil.toIntegerBytes(ecKey.getW().getAffineX(), fieldSize)));
k.setY(Base64Url.encode(JWKUtil.toIntegerBytes(ecKey.getW().getAffineY(), fieldSize)));
return k;
}
private static PublicKey toPublicKey(ECPublicJWK jwk) {
String crv = jwk.getCrv();
String xStr = jwk.getX();
String yStr = jwk.getY();
if (crv == null) {
throw new IllegalArgumentException("JWK crv must be set");
}
if (xStr == null) {
throw new IllegalArgumentException("JWK x must be set");
}
if (yStr == null) {
throw new IllegalArgumentException("JWK y must be set");
}
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
try {
ECPoint point = new ECPoint(x, y);
X9ECParameters ecParams = NISTNamedCurves.getByName(crv);
ECParameterSpec params = new ECDomainParameterSpec(
new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN(), ecParams.getH()));
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BCFIPS");
return keyFactory.generatePublic(pubKeySpec);
} catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchProviderException e) {
throw new IllegalArgumentException(e);
}
}
private static String getAlgorithmID(String alg, String enc) {
if (Algorithm.ECDH_ES_A128KW.equals(alg) || Algorithm.ECDH_ES_A192KW.equals(alg)
|| Algorithm.ECDH_ES_A256KW.equals(alg)) {
return alg;
} else if (Algorithm.ECDH_ES.equals(alg)) {
return enc;
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
private static int getKeyDataLength(String alg, JWEEncryptionProvider encryptionProvider) {
if (Algorithm.ECDH_ES_A128KW.equals(alg)) {
return 128;
} else if (Algorithm.ECDH_ES_A192KW.equals(alg)) {
return 192;
} else if (Algorithm.ECDH_ES_A256KW.equals(alg)) {
return 256;
} else if (Algorithm.ECDH_ES.equals(alg)) {
return encryptionProvider.getExpectedCEKLength() * 8;
} else {
throw new IllegalArgumentException("Unsupported algorithm");
}
}
private static byte[] encodeDataLengthData(final byte[] data) {
byte[] databytes = data != null ? data : new byte[0];
byte[] datalen = toByteArray(databytes.length);
return concat(datalen, databytes);
}
private static byte[] emptyBytes() {
return new byte[0];
}
private static byte[] toByteArray(int intValue) {
return new byte[] { (byte) (intValue >> 24), (byte) (intValue >> 16), (byte) (intValue >> 8), (byte) intValue };
}
private static byte[] concat(byte[]... byteArrays) {
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
for (byte[] bytes : byteArrays) {
if (bytes != null) {
baos.write(bytes);
}
}
return baos.toByteArray();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}

View file

@ -86,6 +86,10 @@ public class FIPS1402Provider implements CryptoProvider {
providers.put(CryptoConstants.RSA1_5, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_PKCS1v1_5));
providers.put(CryptoConstants.RSA_OAEP, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP));
providers.put(CryptoConstants.RSA_OAEP_256, new FIPSRsaKeyEncryptionJWEAlgorithmProvider(FipsRSA.WRAP_OAEP.withDigest(FipsSHS.Algorithm.SHA256)));
providers.put(CryptoConstants.ECDH_ES, new BCFIPSEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A128KW, new BCFIPSEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A192KW, new BCFIPSEcdhEsAlgorithmProvider());
providers.put(CryptoConstants.ECDH_ES_A256KW, new BCFIPSEcdhEsAlgorithmProvider());
Security.insertProviderAt(new KeycloakFipsSecurityProvider(bcFipsProvider), 1);
if (existingBcFipsProvider == null) {

View file

@ -7,7 +7,9 @@ import org.bouncycastle.crypto.KeyWrapper;
import org.bouncycastle.crypto.SymmetricKey;
import org.bouncycastle.crypto.SymmetricSecretKey;
import org.bouncycastle.crypto.fips.FipsAES;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -17,7 +19,7 @@ import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
public class FIPSAesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
@Override
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey) throws Exception {
public byte[] decodeCek(byte[] encodedCek, Key encryptionKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception {
byte[] keyBytes = encryptionKey.getEncoded(); // bytes making up AES key doing the wrapping
SymmetricKey aesKey = new SymmetricSecretKey(FipsAES.KW, keyBytes);
FipsAES.KeyWrapOperatorFactory factory = new FipsAES.KeyWrapOperatorFactory();
@ -26,7 +28,7 @@ public class FIPSAesKeyWrapAlgorithmProvider implements JWEAlgorithmProvider {
}
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey) throws Exception {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key encryptionKey, JWEHeaderBuilder headerBuilder) throws Exception {
byte[] inputKeyBytes = keyStorage.getCekBytes(); // bytes making up the key to be wrapped
byte[] keyBytes = encryptionKey.getEncoded(); // bytes making up AES key doing the wrapping
SymmetricKey aesKey = new SymmetricSecretKey(FipsAES.KW, keyBytes);

View file

@ -8,7 +8,9 @@ import org.bouncycastle.crypto.KeyWrapperUsingSecureRandom;
import org.bouncycastle.crypto.asymmetric.AsymmetricRSAPrivateKey;
import org.bouncycastle.crypto.asymmetric.AsymmetricRSAPublicKey;
import org.bouncycastle.crypto.fips.FipsRSA;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.jose.jwe.JWEKeyStorage;
import org.keycloak.jose.jwe.JWEHeader.JWEHeaderBuilder;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.jose.jwe.enc.JWEEncryptionProvider;
@ -27,7 +29,7 @@ public class FIPSRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmPro
}
@Override
public byte[] decodeCek(byte[] encodedCek, Key privateKey) throws Exception {
public byte[] decodeCek(byte[] encodedCek, Key privateKey, JWEHeader header, JWEEncryptionProvider encryptionProvider) throws Exception {
AsymmetricRSAPrivateKey rsaPrivateKey =
new AsymmetricRSAPrivateKey(FipsRSA.ALGORITHM, privateKey.getEncoded());
@ -41,7 +43,7 @@ public class FIPSRsaKeyEncryptionJWEAlgorithmProvider implements JWEAlgorithmPro
@Override
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey) throws Exception {
public byte[] encodeCek(JWEEncryptionProvider encryptionProvider, JWEKeyStorage keyStorage, Key publicKey, JWEHeaderBuilder headerBuilder) throws Exception {
AsymmetricRSAPublicKey rsaPubKey =
new AsymmetricRSAPublicKey(FipsRSA.ALGORITHM, publicKey.getEncoded());
byte[] inputKeyBytes = keyStorage.getCekBytes();

View file

@ -23,7 +23,9 @@ import org.junit.runners.Parameterized;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.fips.BCFIPSECDSACryptoProvider;
import org.keycloak.keys.AbstractEcdsaKeyProviderFactory;
import org.keycloak.keys.AbstractEcKeyProviderFactory;
import org.keycloak.keys.GeneratedEcdhKeyProviderFactory;
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
import org.keycloak.rule.CryptoInitRule;
import java.security.InvalidAlgorithmParameterException;
@ -73,8 +75,11 @@ public class BCFIPSECDSACryptoProviderTest {
try {
KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen("ECDSA");
String domainParamNistRep = AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(algorithm);
String curve = AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToSecRep(domainParamNistRep);
String domainParamNistRep = GeneratedEcdsaKeyProviderFactory.convertJWSAlgorithmToECDomainParmNistRep(algorithm);
if (domainParamNistRep == null) {
domainParamNistRep = GeneratedEcdhKeyProviderFactory.convertJWEAlgorithmToECDomainParmNistRep(algorithm);
}
String curve = AbstractEcKeyProviderFactory.convertECDomainParmNistRepToSecRep(domainParamNistRep);
ECGenParameterSpec parameterSpec = new ECGenParameterSpec(curve);
kpg.initialize(parameterSpec);
return kpg.generateKeyPair();

View file

@ -0,0 +1,117 @@
/*
* 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.crypto.fips.test;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.InvalidKeySpecException;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.asymmetric.ECDomainParameters;
import org.bouncycastle.jcajce.spec.ECDomainParameterSpec;
import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.common.util.Base64Url;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.fips.BCFIPSEcdhEsAlgorithmProvider;
import org.keycloak.jose.jwe.JWE;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.JWEException;
import org.keycloak.jose.jwe.JWEHeader;
import org.keycloak.rule.CryptoInitRule;
public class BCFIPSEcdhEsAlgorithmProviderTest {
@ClassRule
public static CryptoInitRule cryptoInitRule = new CryptoInitRule();
/**
* Test ECDH-ES Key Agreement Computation.
*
* @see <a href=
* "https://datatracker.ietf.org/doc/html/rfc7518#appendix-C">Example
* ECDH-ES Key Agreement Computation</a>
* @throws InvalidKeySpecException exception
* @throws NoSuchAlgorithmException exception
* @throws NoSuchProviderException exception
*/
@Test
public void deriveKey() throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
PrivateKey ephemeralPrivateKey = getPrivateKey("P-256", "0_NxaRPUMQoAJt50Gz8YiTr8gRTwyEaCumd-MToTmIo");
PublicKey encryptionPublicKey = getPublicKey("P-256", "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
byte[] derivedKey = BCFIPSEcdhEsAlgorithmProvider.deriveKey(encryptionPublicKey, ephemeralPrivateKey, 128,
"A128GCM", Base64Url.decode("QWxpY2U"), Base64Url.decode("Qm9i"));
Assert.assertEquals("VqqN6vgjbSBcIijNcacQGg", Base64Url.encode(derivedKey));
}
@Test
public void encodeDecode()
throws JWEException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
PublicKey encryptionPublicKey = getPublicKey("P-256", "weNJy2HscCSM6AEDTDg04biOvhFhyyWvOHQfeF_PxMQ",
"e8lnCO-AlStT-NJVX-crhB7QRYhiix03illJOVAOyck");
String content = "plaintext";
JWE jweEncode = new JWE()
.header(JWEHeader.builder().algorithm(Algorithm.ECDH_ES_A128KW)
.encryptionAlgorithm(JWEConstants.A128CBC_HS256)
.build())
.content(content.getBytes(StandardCharsets.UTF_8));
jweEncode.getKeyStorage().setEncryptionKey(encryptionPublicKey);
String encodedJwe = jweEncode.encodeJwe();
PrivateKey decryptionPrivateKey = getPrivateKey("P-256", "VEmDZpDXXK8p8N0Cndsxs924q6nS1RXFASRl6BfUqdw");
JWE jweDecode = new JWE();
jweDecode.getKeyStorage().setDecryptionKey(decryptionPrivateKey);
jweDecode = jweDecode.verifyAndDecodeJwe(encodedJwe);
Assert.assertArrayEquals(jweEncode.getContent(), jweDecode.getContent());
}
private PublicKey getPublicKey(String crv, String xStr, String yStr)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
BigInteger x = new BigInteger(1, Base64Url.decode(xStr));
BigInteger y = new BigInteger(1, Base64Url.decode(yStr));
ECPoint point = new ECPoint(x, y);
X9ECParameters ecParams = NISTNamedCurves.getByName(crv);
ECParameterSpec params = new ECDomainParameterSpec(
new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN(), ecParams.getH()));
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BCFIPS");
return keyFactory.generatePublic(pubKeySpec);
}
private PrivateKey getPrivateKey(String crv, String dStr)
throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {
BigInteger d = new BigInteger(1, Base64Url.decode(dStr));
X9ECParameters ecParams = NISTNamedCurves.getByName(crv);
ECParameterSpec params = new ECDomainParameterSpec(
new ECDomainParameters(ecParams.getCurve(), ecParams.getG(), ecParams.getN(), ecParams.getH()));
ECPrivateKeySpec privKeySpec = new ECPrivateKeySpec(d, params);
KeyFactory keyFactory = KeyFactory.getInstance("EC", "BCFIPS");
return keyFactory.generatePrivate(privKeySpec);
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.crypto;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.KeycloakSession;
public class EcdhEsA128KwCekManagementProviderFactory implements CekManagementProviderFactory {
public static final String ID = JWEConstants.ECDH_ES_A128KW;
@Override
public String getId() {
return ID;
}
@Override
public CekManagementProvider create(KeycloakSession session) {
return new EcdhEsCekManagementProvider(session, ID);
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.crypto;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.KeycloakSession;
public class EcdhEsA192KwCekManagementProviderFactory implements CekManagementProviderFactory {
public static final String ID = JWEConstants.ECDH_ES_A192KW;
@Override
public String getId() {
return ID;
}
@Override
public CekManagementProvider create(KeycloakSession session) {
return new EcdhEsCekManagementProvider(session, ID);
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.crypto;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.KeycloakSession;
public class EcdhEsA256KwCekManagementProviderFactory implements CekManagementProviderFactory {
public static final String ID = JWEConstants.ECDH_ES_A256KW;
@Override
public String getId() {
return ID;
}
@Override
public CekManagementProvider create(KeycloakSession session) {
return new EcdhEsCekManagementProvider(session, ID);
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.crypto;
import org.keycloak.common.crypto.CryptoIntegration;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.jose.jwe.alg.JWEAlgorithmProvider;
import org.keycloak.models.KeycloakSession;
public class EcdhEsCekManagementProvider implements CekManagementProvider {
private final KeycloakSession session;
private final String jweAlgorithmName;
public EcdhEsCekManagementProvider(KeycloakSession session, String jweAlgorithmName) {
this.session = session;
this.jweAlgorithmName = jweAlgorithmName;
}
@Override
public JWEAlgorithmProvider jweAlgorithmProvider() {
if (JWEConstants.ECDH_ES.equals(jweAlgorithmName) || JWEConstants.ECDH_ES_A128KW.equals(jweAlgorithmName)
|| JWEConstants.ECDH_ES_A192KW.equals(jweAlgorithmName)
|| JWEConstants.ECDH_ES_A256KW.equals(jweAlgorithmName)) {
return CryptoIntegration.getProvider().getAlgorithmProvider(JWEAlgorithmProvider.class, jweAlgorithmName);
} else {
return null;
}
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.crypto;
import org.keycloak.jose.jwe.JWEConstants;
import org.keycloak.models.KeycloakSession;
public class EcdhEsCekManagementProviderFactory implements CekManagementProviderFactory {
public static final String ID = JWEConstants.ECDH_ES;
@Override
public String getId() {
return ID;
}
@Override
public CekManagementProvider create(KeycloakSession session) {
return new EcdhEsCekManagementProvider(session, ID);
}
}

View file

@ -27,7 +27,7 @@ import org.keycloak.models.RealmModel;
import java.security.KeyPair;
import java.util.stream.Stream;
public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
public abstract class AbstractEcKeyProvider implements KeyProvider {
private final KeyStatus status;
@ -35,7 +35,7 @@ public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
private final KeyWrapper key;
public AbstractEcdsaKeyProvider(RealmModel realm, ComponentModel model) {
public AbstractEcKeyProvider(RealmModel realm, ComponentModel model) {
this.model = model;
this.status = KeyStatus.from(model.get(Attributes.ACTIVE_KEY, true), model.get(Attributes.ENABLED_KEY, true));
@ -54,16 +54,16 @@ public abstract class AbstractEcdsaKeyProvider implements KeyProvider {
return Stream.of(key);
}
protected KeyWrapper createKeyWrapper(KeyPair keyPair, String ecInNistRep) {
protected KeyWrapper createKeyWrapper(KeyPair keyPair, String algorithm, KeyUse keyUse) {
KeyWrapper key = new KeyWrapper();
key.setProviderId(model.getId());
key.setProviderPriority(model.get("priority", 0l));
key.setKid(KeyUtils.createKeyId(keyPair.getPublic()));
key.setUse(KeyUse.SIG);
key.setUse(keyUse);
key.setType(KeyType.EC);
key.setAlgorithm(AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToAlgorithm(ecInNistRep));
key.setAlgorithm(algorithm);
key.setStatus(status);
key.setPrivateKey(keyPair.getPrivate());
key.setPublicKey(keyPair.getPublic());

View file

@ -18,11 +18,9 @@ package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.crypto.Algorithm;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import java.security.KeyPair;
@ -30,19 +28,10 @@ import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
public abstract class AbstractEcKeyProviderFactory<T extends KeyProvider> implements KeyProviderFactory<T> {
public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFactory {
public static final String DEFAULT_EC_ELLIPTIC_CURVE = "P-256";
protected static final String ECDSA_PRIVATE_KEY_KEY = "ecdsaPrivateKey";
protected static final String ECDSA_PUBLIC_KEY_KEY = "ecdsaPublicKey";
protected static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
// only support NIST P-256 for ES256, P-384 for ES384, P-521 for ES512
protected static ProviderConfigProperty ECDSA_ELLIPTIC_CURVE_PROPERTY = new ProviderConfigProperty(ECDSA_ELLIPTIC_CURVE_KEY, "Elliptic Curve", "Elliptic Curve used in ECDSA", LIST_TYPE,
String.valueOf(GeneratedEcdsaKeyProviderFactory.DEFAULT_ECDSA_ELLIPTIC_CURVE),
"P-256", "P-384", "P-521");
public final static ProviderConfigurationBuilder configurationBuilder() {
return ProviderConfigurationBuilder.create()
.property(Attributes.PRIORITY_PROPERTY)
@ -58,7 +47,7 @@ public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFact
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
}
public static KeyPair generateEcdsaKeyPair(String keySpecName) {
public static KeyPair generateEcKeyPair(String keySpecName) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
@ -75,44 +64,17 @@ public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFact
String ecInSecRep = null;
switch(ecInNistRep) {
case "P-256" :
ecInSecRep = "secp256r1";
ecInSecRep = "secp256r1";
break;
case "P-384" :
ecInSecRep = "secp384r1";
ecInSecRep = "secp384r1";
break;
case "P-521" :
ecInSecRep = "secp521r1";
ecInSecRep = "secp521r1";
break;
default :
// return null
}
return ecInSecRep;
}
public static String convertECDomainParmNistRepToAlgorithm(String ecInNistRep) {
switch(ecInNistRep) {
case "P-256" :
return Algorithm.ES256;
case "P-384" :
return Algorithm.ES384;
case "P-521" :
return Algorithm.ES512;
default :
return null;
}
}
public static String convertAlgorithmToECDomainParmNistRep(String algorithm) {
switch(algorithm) {
case Algorithm.ES256 :
return "P-256";
case Algorithm.ES384 :
return "P-384";
case Algorithm.ES512 :
return "P-521";
default :
return null;
}
}
}

View file

@ -0,0 +1,123 @@
/*
* Copyright 2024 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.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import java.security.spec.X509EncodedKeySpec;
public abstract class AbstractGeneratedEcKeyProviderFactory<T extends KeyProvider>
extends AbstractEcKeyProviderFactory<T> {
abstract protected String getDefaultEcEllipticCurve();
abstract protected String getEcEllipticCurveKey();
abstract protected String getEcEllipticCurveKey(String algorithm);
abstract protected ProviderConfigProperty getEcEllipticCurveProperty();
abstract protected String getEcPrivateKeyKey();
abstract protected String getEcPublicKeyKey();
abstract protected Logger getLogger();
abstract protected boolean isSupportedEcAlgorithm(String algorithm);
abstract protected boolean isValidKeyUse(KeyUse keyUse);
@Override
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
if (isValidKeyUse(keyUse) && isSupportedEcAlgorithm(algorithm)) {
RealmModel realm = session.getContext().getRealm();
ComponentModel generated = new ComponentModel();
generated.setName("fallback-" + algorithm);
generated.setParentId(realm.getId());
generated.setProviderId(getId());
generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle(Attributes.PRIORITY_KEY, "-100");
config.putSingle(getEcEllipticCurveKey(), getEcEllipticCurveKey(algorithm));
generated.setConfig(config);
realm.addComponentModel(generated);
return true;
} else {
return false;
}
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, realm, model);
ConfigurationValidationHelper.check(model).checkList(getEcEllipticCurveProperty(), false);
String ecInNistRep = model.get(getEcEllipticCurveKey());
if (ecInNistRep == null) ecInNistRep = getDefaultEcEllipticCurve();
if (!(model.contains(getEcPrivateKeyKey()) && model.contains(getEcPublicKeyKey()))) {
generateKeys(model, ecInNistRep);
getLogger().debugv("Generated keys for {0}", realm.getName());
} else {
String currentEc = getCurveFromPublicKey(model.getConfig().getFirst(getEcPublicKeyKey()));
if (!ecInNistRep.equals(currentEc)) {
generateKeys(model, ecInNistRep);
getLogger().debugv("Elliptic Curve changed, generating new keys for {0}", realm.getName());
}
}
}
protected void generateKeys(ComponentModel model, String ecInNistRep) {
KeyPair keyPair;
try {
keyPair = generateEcKeyPair(convertECDomainParmNistRepToSecRep(ecInNistRep));
model.put(getEcPrivateKeyKey(), Base64.encodeBytes(keyPair.getPrivate().getEncoded()));
model.put(getEcPublicKeyKey(), Base64.encodeBytes(keyPair.getPublic().getEncoded()));
model.put(getEcEllipticCurveKey(), ecInNistRep);
} catch (Throwable t) {
throw new ComponentValidationException("Failed to generate EC keys", t);
}
}
protected String getCurveFromPublicKey(String publicEcKeyBase64Encoded) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicEcKeyBase64Encoded));
ECPublicKey ecKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
return "P-" + ecKey.getParams().getCurve().getField().getFieldSize();
} catch (Throwable t) {
throw new ComponentValidationException("Failed to get EC from its public key", t);
}
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class GeneratedEcdhKeyProvider extends AbstractEcKeyProvider {
private static final Logger logger = Logger.getLogger(GeneratedEcdhKeyProvider.class);
public GeneratedEcdhKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model);
}
@Override
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
String privateEcdhKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_PRIVATE_KEY_KEY);
String publicEcdhKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_PUBLIC_KEY_KEY);
String ecdhAlgorithm = model.getConfig().getFirst(GeneratedEcdhKeyProviderFactory.ECDH_ALGORITHM_KEY);
try {
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(privateEcdhKeyBase64Encoded));
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey decodedPrivateKey = kf.generatePrivate(privateKeySpec);
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicEcdhKeyBase64Encoded));
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
return createKeyWrapper(keyPair, ecdhAlgorithm, KeyUse.ENC);
} catch (Exception e) {
logger.warnf("Exception at decodeEcdhPublicKey. %s", e.toString());
return null;
}
}
}

View file

@ -0,0 +1,155 @@
/*
* 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.keys;
import org.jboss.logging.Logger;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty;
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
import java.util.List;
public class GeneratedEcdhKeyProviderFactory extends AbstractGeneratedEcKeyProviderFactory<KeyProvider> {
// secp256r1,NIST P-256,X9.62 prime256v1,1.2.840.10045.3.1.7
public static final String DEFAULT_ECDH_ELLIPTIC_CURVE = DEFAULT_EC_ELLIPTIC_CURVE;
public static final String ECDH_ALGORITHM_KEY = "ecdhAlgorithm";
public static final String ECDH_ELLIPTIC_CURVE_KEY = "ecdhEllipticCurveKey";
public static final String ECDH_PRIVATE_KEY_KEY = "ecdhPrivateKey";
public static final String ECDH_PUBLIC_KEY_KEY = "ecdhPublicKey";
// only support NIST P-256 for ES256, P-384 for ES384, P-521 for ES512
protected static ProviderConfigProperty ECDH_ELLIPTIC_CURVE_PROPERTY = new ProviderConfigProperty(ECDH_ELLIPTIC_CURVE_KEY, "Elliptic Curve", "Elliptic Curve used in ECDH", LIST_TYPE,
String.valueOf(GeneratedEcdhKeyProviderFactory.DEFAULT_ECDH_ELLIPTIC_CURVE),
"P-256", "P-384", "P-521");
protected static ProviderConfigProperty ECDH_ALGORITHM_PROPERTY = new ProviderConfigProperty(ECDH_ALGORITHM_KEY,
"Algorithm", "Algorithm for processing the Content Encryption Key", LIST_TYPE, Algorithm.ECDH_ES,
Algorithm.ECDH_ES, Algorithm.ECDH_ES_A128KW, Algorithm.ECDH_ES_A192KW, Algorithm.ECDH_ES_A256KW);
private static final String HELP_TEXT = "Generates ECDH keys";
public static final String ID = "ecdh-generated";
private static final Logger logger = Logger.getLogger(GeneratedEcdhKeyProviderFactory.class);
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractGeneratedEcKeyProviderFactory.configurationBuilder()
.property(ECDH_ELLIPTIC_CURVE_PROPERTY)
.property(ECDH_ALGORITHM_PROPERTY)
.build();
public static String convertECDomainParmNistRepToJWEAlgorithm(String ecInNistRep) {
switch(ecInNistRep) {
case "P-256" :
return Algorithm.ECDH_ES_A128KW;
case "P-384" :
return Algorithm.ECDH_ES_A192KW;
case "P-521" :
return Algorithm.ECDH_ES_A256KW;
default :
return null;
}
}
public static String convertJWEAlgorithmToECDomainParmNistRep(String algorithm) {
switch(algorithm) {
case Algorithm.ECDH_ES_A128KW :
return "P-256";
case Algorithm.ECDH_ES_A192KW :
return "P-384";
case Algorithm.ECDH_ES_A256KW :
return "P-521";
default :
return null;
}
}
@Override
public KeyProvider create(KeycloakSession session, ComponentModel model) {
return new GeneratedEcdhKeyProvider(session.getContext().getRealm(), model);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return CONFIG_PROPERTIES;
}
@Override
protected String getDefaultEcEllipticCurve() {
return DEFAULT_ECDH_ELLIPTIC_CURVE;
}
@Override
protected String getEcEllipticCurveKey() {
return ECDH_ELLIPTIC_CURVE_KEY;
}
@Override
protected String getEcEllipticCurveKey(String algorithm) {
if (Algorithm.ECDH_ES.equals(algorithm)) {
return DEFAULT_ECDH_ELLIPTIC_CURVE;
}
return convertJWEAlgorithmToECDomainParmNistRep(algorithm);
}
@Override
protected ProviderConfigProperty getEcEllipticCurveProperty() {
return ECDH_ELLIPTIC_CURVE_PROPERTY;
}
@Override
protected String getEcPrivateKeyKey() {
return ECDH_PRIVATE_KEY_KEY;
}
@Override
protected String getEcPublicKeyKey() {
return ECDH_PUBLIC_KEY_KEY;
}
@Override
public String getHelpText() {
return HELP_TEXT;
}
@Override
public String getId() {
return ID;
}
@Override
protected Logger getLogger() {
return logger;
}
@Override
protected boolean isSupportedEcAlgorithm(String algorithm) {
return (algorithm.equals(Algorithm.ECDH_ES) || algorithm.equals(Algorithm.ECDH_ES_A128KW)
|| algorithm.equals(Algorithm.ECDH_ES_A192KW) || algorithm.equals(Algorithm.ECDH_ES_A256KW));
}
@Override
protected boolean isValidKeyUse(KeyUse keyUse) {
return KeyUse.ENC.equals(keyUse);
}
}

View file

@ -19,6 +19,7 @@ package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.component.ComponentModel;
import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
@ -29,15 +30,15 @@ import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class GeneratedEcdsaKeyProvider extends AbstractEcdsaKeyProvider {
public class GeneratedEcdsaKeyProvider extends AbstractEcKeyProvider {
private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProvider.class);
public GeneratedEcdsaKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model);
}
@Override
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
@Override
protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
String privateEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PRIVATE_KEY_KEY);
String publicEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY);
String ecInNistRep = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_ELLIPTIC_CURVE_KEY);
@ -51,8 +52,8 @@ public class GeneratedEcdsaKeyProvider extends AbstractEcdsaKeyProvider {
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
return createKeyWrapper(keyPair, ecInNistRep);
return createKeyWrapper(keyPair,
GeneratedEcdsaKeyProviderFactory.convertECDomainParmNistRepToJWSAlgorithm(ecInNistRep), KeyUse.SIG);
} catch (Exception e) {
logger.warnf("Exception at decodeEcdsaPublicKey. %s", e.toString());
return null;

View file

@ -17,35 +17,37 @@
package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyUse;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
import org.keycloak.provider.ProviderConfigProperty;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.interfaces.ECPublicKey;
import java.security.spec.X509EncodedKeySpec;
import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
import java.util.List;
public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFactory {
public class GeneratedEcdsaKeyProviderFactory extends AbstractGeneratedEcKeyProviderFactory<KeyProvider> {
private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProviderFactory.class);
public static final String ECDSA_PRIVATE_KEY_KEY = "ecdsaPrivateKey";
public static final String ECDSA_PUBLIC_KEY_KEY = "ecdsaPublicKey";
public static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
// only support NIST P-256 for ES256, P-384 for ES384, P-521 for ES512
protected static ProviderConfigProperty ECDSA_ELLIPTIC_CURVE_PROPERTY = new ProviderConfigProperty(ECDSA_ELLIPTIC_CURVE_KEY, "Elliptic Curve", "Elliptic Curve used in ECDSA", LIST_TYPE,
String.valueOf(GeneratedEcdsaKeyProviderFactory.DEFAULT_ECDSA_ELLIPTIC_CURVE),
"P-256", "P-384", "P-521");
public static final String ID = "ecdsa-generated";
private static final String HELP_TEXT = "Generates ECDSA keys";
// secp256r1,NIST P-256,X9.62 prime256v1,1.2.840.10045.3.1.7
public static final String DEFAULT_ECDSA_ELLIPTIC_CURVE = "P-256";
public static final String DEFAULT_ECDSA_ELLIPTIC_CURVE = DEFAULT_EC_ELLIPTIC_CURVE;
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractEcdsaKeyProviderFactory.configurationBuilder()
private static final List<ProviderConfigProperty> CONFIG_PROPERTIES = AbstractGeneratedEcKeyProviderFactory.configurationBuilder()
.property(ECDSA_ELLIPTIC_CURVE_PROPERTY)
.build();
@ -54,30 +56,6 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa
return new GeneratedEcdsaKeyProvider(session.getContext().getRealm(), model);
}
@Override
public boolean createFallbackKeys(KeycloakSession session, KeyUse keyUse, String algorithm) {
if (keyUse.equals(KeyUse.SIG) && (algorithm.equals(Algorithm.ES256) || algorithm.equals(Algorithm.ES384) || algorithm.equals(Algorithm.ES512))) {
RealmModel realm = session.getContext().getRealm();
ComponentModel generated = new ComponentModel();
generated.setName("fallback-" + algorithm);
generated.setParentId(realm.getId());
generated.setProviderId(ID);
generated.setProviderType(KeyProvider.class.getName());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle(Attributes.PRIORITY_KEY, "-100");
config.putSingle(ECDSA_ELLIPTIC_CURVE_KEY, convertAlgorithmToECDomainParmNistRep(algorithm));
generated.setConfig(config);
realm.addComponentModel(generated);
return true;
} else {
return false;
}
}
@Override
public String getHelpText() {
return HELP_TEXT;
@ -94,46 +72,74 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa
}
@Override
public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
super.validateConfiguration(session, realm, model);
protected Logger getLogger() {
return logger;
}
ConfigurationValidationHelper.check(model).checkList(ECDSA_ELLIPTIC_CURVE_PROPERTY, false);
@Override
protected boolean isValidKeyUse(KeyUse keyUse) {
return KeyUse.SIG.equals(keyUse);
}
String ecInNistRep = model.get(ECDSA_ELLIPTIC_CURVE_KEY);
if (ecInNistRep == null) ecInNistRep = DEFAULT_ECDSA_ELLIPTIC_CURVE;
@Override
protected boolean isSupportedEcAlgorithm(String algorithm) {
return (algorithm.equals(Algorithm.ES256) || algorithm.equals(Algorithm.ES384)
|| algorithm.equals(Algorithm.ES512));
}
if (!(model.contains(ECDSA_PRIVATE_KEY_KEY) && model.contains(ECDSA_PUBLIC_KEY_KEY))) {
generateKeys(model, ecInNistRep);
logger.debugv("Generated keys for {0}", realm.getName());
} else {
String currentEc = getCurveFromPublicKey(model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY));
if (!ecInNistRep.equals(currentEc)) {
generateKeys(model, ecInNistRep);
logger.debugv("Elliptic Curve changed, generating new keys for {0}", realm.getName());
}
@Override
protected String getEcEllipticCurveKey(String algorithm) {
return convertJWSAlgorithmToECDomainParmNistRep(algorithm);
}
@Override
protected ProviderConfigProperty getEcEllipticCurveProperty() {
return ECDSA_ELLIPTIC_CURVE_PROPERTY;
}
@Override
protected String getEcEllipticCurveKey() {
return ECDSA_ELLIPTIC_CURVE_KEY;
}
@Override
protected String getEcPrivateKeyKey() {
return ECDSA_PRIVATE_KEY_KEY;
}
@Override
protected String getEcPublicKeyKey() {
return ECDSA_PUBLIC_KEY_KEY;
}
@Override
protected String getDefaultEcEllipticCurve() {
return DEFAULT_ECDSA_ELLIPTIC_CURVE;
}
public static String convertECDomainParmNistRepToJWSAlgorithm(String ecInNistRep) {
switch(ecInNistRep) {
case "P-256" :
return Algorithm.ES256;
case "P-384" :
return Algorithm.ES384;
case "P-521" :
return Algorithm.ES512;
default :
return null;
}
}
private void generateKeys(ComponentModel model, String ecInNistRep) {
KeyPair keyPair;
try {
keyPair = generateEcdsaKeyPair(convertECDomainParmNistRepToSecRep(ecInNistRep));
model.put(ECDSA_PRIVATE_KEY_KEY, Base64.encodeBytes(keyPair.getPrivate().getEncoded()));
model.put(ECDSA_PUBLIC_KEY_KEY, Base64.encodeBytes(keyPair.getPublic().getEncoded()));
model.put(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
} catch (Throwable t) {
throw new ComponentValidationException("Failed to generate ECDSA keys", t);
}
}
private String getCurveFromPublicKey(String publicEcdsaKeyBase64Encoded) {
try {
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicEcdsaKeyBase64Encoded));
ECPublicKey ecKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
return "P-" + ecKey.getParams().getCurve().getField().getFieldSize();
} catch (Throwable t) {
throw new ComponentValidationException("Failed to get EC from its public key", t);
public static String convertJWSAlgorithmToECDomainParmNistRep(String algorithm) {
switch(algorithm) {
case Algorithm.ES256 :
return "P-256";
case Algorithm.ES384 :
return "P-384";
case Algorithm.ES512 :
return "P-521";
default :
return null;
}
}
}

View file

@ -1,3 +1,7 @@
org.keycloak.crypto.EcdhEsCekManagementProviderFactory
org.keycloak.crypto.EcdhEsA128KwCekManagementProviderFactory
org.keycloak.crypto.EcdhEsA192KwCekManagementProviderFactory
org.keycloak.crypto.EcdhEsA256KwCekManagementProviderFactory
org.keycloak.crypto.RsaesPkcs1CekManagementProviderFactory
org.keycloak.crypto.RsaesOaepCekManagementProviderFactory
org.keycloak.crypto.RsaesOaep256CekManagementProviderFactory

View file

@ -20,6 +20,7 @@ org.keycloak.keys.GeneratedAesKeyProviderFactory
org.keycloak.keys.GeneratedRsaKeyProviderFactory
org.keycloak.keys.JavaKeystoreKeyProviderFactory
org.keycloak.keys.ImportedRsaKeyProviderFactory
org.keycloak.keys.GeneratedEcdhKeyProviderFactory
org.keycloak.keys.GeneratedEcdsaKeyProviderFactory
org.keycloak.keys.GeneratedRsaEncKeyProviderFactory
org.keycloak.keys.ImportedRsaEncKeyProviderFactory

View file

@ -9,7 +9,9 @@ import org.keycloak.crypto.JavaAlgorithm;
import org.keycloak.crypto.KeyStatus;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.keys.AbstractEcdsaKeyProviderFactory;
import org.keycloak.keys.AbstractEcKeyProviderFactory;
import org.keycloak.keys.GeneratedEcdhKeyProviderFactory;
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
@ -44,8 +46,13 @@ public class KeyUtils {
try {
KeyPairGenerator kpg = CryptoIntegration.getProvider().getKeyPairGen("ECDSA");
String domainParamNistRep = AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(algorithm);
String curve = AbstractEcdsaKeyProviderFactory.convertECDomainParmNistRepToSecRep(domainParamNistRep);
String domainParamNistRep = GeneratedEcdsaKeyProviderFactory
.convertJWSAlgorithmToECDomainParmNistRep(algorithm);
if (domainParamNistRep == null) {
domainParamNistRep = GeneratedEcdhKeyProviderFactory
.convertJWEAlgorithmToECDomainParmNistRep(algorithm);
}
String curve = AbstractEcKeyProviderFactory.convertECDomainParmNistRepToSecRep(domainParamNistRep);
ECGenParameterSpec parameterSpec = new ECGenParameterSpec(curve);
kpg.initialize(parameterSpec);
return kpg.generateKeyPair();

View file

@ -0,0 +1,62 @@
/*
* 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.common.util.MultivaluedHashMap;
import org.keycloak.models.utils.DefaultKeyProviders;
import org.keycloak.representations.idm.ComponentExportRepresentation;
public abstract class AbstractKcOidcBrokerJWEEcdhEsTest extends KcOidcBrokerJWETest {
private final String crv;
private final String encAlg;
public AbstractKcOidcBrokerJWEEcdhEsTest(String crv, String encAlg, String encEnc, String sigAlg) {
super(encAlg, encEnc, sigAlg);
this.crv = crv;
this.encAlg = encAlg;
}
protected ComponentExportRepresentation getProviderKeyProvider() {
// create the ECDSA component for the signature in the specified alg
ComponentExportRepresentation component = new ComponentExportRepresentation();
component.setName("ecdsa-generated");
component.setProviderId("ecdsa-generated");
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
config.putSingle("ecdsaEllipticCurveKey", this.crv);
component.setConfig(config);
return component;
}
protected ComponentExportRepresentation getConsumerKeyProvider() {
// create the ECDH component for the encryption in the specified alg
ComponentExportRepresentation component = new ComponentExportRepresentation();
component.setName("ecdh-generated");
component.setProviderId("ecdh-generated");
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
config.putSingle("ecdhAlgorithm", this.encAlg);
config.putSingle("ecdhEllipticCurveKey", this.crv);
component.setConfig(config);
return component;
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test() {
super("P-256", JWEConstants.ECDH_ES_A128KW, JWEConstants.A128CBC_HS256, Algorithm.ES256);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest() {
super("P-256", JWEConstants.ECDH_ES_A128KW, JWEConstants.A128GCM, Algorithm.ES256);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test() {
super("P-384", JWEConstants.ECDH_ES_A192KW, JWEConstants.A192CBC_HS384, Algorithm.ES384);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest() {
super("P-384", JWEConstants.ECDH_ES_A192KW, JWEConstants.A192GCM, Algorithm.ES384);
}
}

View file

@ -0,0 +1,26 @@
/*
KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest.java * 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test() {
super("P-521", JWEConstants.ECDH_ES_A256KW, JWEConstants.A256CBC_HS512, Algorithm.ES512);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest() {
super("P-521", JWEConstants.ECDH_ES_A256KW, JWEConstants.A256GCM, Algorithm.ES512);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test() {
super("P-384", JWEConstants.ECDH_ES, JWEConstants.A192CBC_HS384, Algorithm.ES384);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.crypto.Algorithm;
import org.keycloak.jose.jwe.JWEConstants;
public class KcOidcBrokerJWEEcdhEsP384A192GcmTest extends AbstractKcOidcBrokerJWEEcdhEsTest {
public KcOidcBrokerJWEEcdhEsP384A192GcmTest() {
super("P-384", JWEConstants.ECDH_ES, JWEConstants.A192GCM, Algorithm.ES384);
}
}

View file

@ -78,6 +78,24 @@ public class KcOidcBrokerJWETest extends AbstractBrokerTest {
this.sigAlg = sigAlg;
}
protected ComponentExportRepresentation getConsumerKeyProvider() {
// create the RSA component for the encryption in the specified alg
ComponentExportRepresentation component = new ComponentExportRepresentation();
component.setName("rsa-enc-generated");
component.setProviderId("rsa-enc-generated");
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
config.putSingle("keyUse", KeyUse.ENC.name());
config.putSingle("algorithm", encAlg);
component.setConfig(config);
return component;
}
protected ComponentExportRepresentation getProviderKeyProvider() {
return null;
}
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration() {
@ -116,23 +134,34 @@ public class KcOidcBrokerJWETest extends AbstractBrokerTest {
RealmRepresentation realm = super.createConsumerRealm();
if (encAlg != null) {
// create the RSA component for the encryption in the specified alg
ComponentExportRepresentation component = new ComponentExportRepresentation();
component.setName("rsa-enc-generated");
component.setProviderId("rsa-enc-generated");
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
config.putSingle("keyUse", KeyUse.ENC.name());
config.putSingle("algorithm", encAlg);
component.setConfig(config);
MultivaluedHashMap<String, ComponentExportRepresentation> components = realm.getComponents();
if (components == null) {
components = new MultivaluedHashMap<>();
realm.setComponents(components);
ComponentExportRepresentation component = getConsumerKeyProvider();
if (component != null) {
MultivaluedHashMap<String, ComponentExportRepresentation> components = realm.getComponents();
if (components == null) {
components = new MultivaluedHashMap<>();
realm.setComponents(components);
}
components.add(KeyProvider.class.getName(), component);
}
}
return realm;
}
@Override
public RealmRepresentation createProviderRealm() {
RealmRepresentation realm = super.createProviderRealm();
if (sigAlg != null) {
ComponentExportRepresentation component = getProviderKeyProvider();
if (component != null) {
MultivaluedHashMap<String, ComponentExportRepresentation> components = realm.getComponents();
if (components == null) {
components = new MultivaluedHashMap<>();
realm.setComponents(components);
}
components.add(KeyProvider.class.getName(), component);
}
components.add(KeyProvider.class.getName(), component);
}
return realm;

View file

@ -0,0 +1,337 @@
/*
* 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.keys;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import java.security.KeyFactory;
import java.security.interfaces.ECPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.Response;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.KeyType;
import org.keycloak.crypto.KeyUse;
import org.keycloak.keys.Attributes;
import org.keycloak.keys.GeneratedEcdhKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.representations.idm.ComponentRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation;
import org.keycloak.representations.idm.KeysMetadataRepresentation.KeyMetadataRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractKeycloakTest;
import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
public class GeneratedEcdhKeyProviderTest extends AbstractKeycloakTest {
private static final String DEFAULT_EC = GeneratedEcdhKeyProviderFactory.DEFAULT_ECDH_ELLIPTIC_CURVE;
private static final String ECDH_ELLIPTIC_CURVE_KEY = GeneratedEcdhKeyProviderFactory.ECDH_ELLIPTIC_CURVE_KEY;
private static final String ECDH_ALGORITHM_KEY = GeneratedEcdhKeyProviderFactory.ECDH_ALGORITHM_KEY;
private static final String TEST_REALM_NAME = "test";
@Rule
public AssertEvents events = new AssertEvents(this);
@Page
protected AppPage appPage;
@Page
protected LoginPage loginPage;
@Override
public void addTestRealms(List<RealmRepresentation> testRealms) {
RealmRepresentation realm = loadJson(getClass().getResourceAsStream("/testrealm.json"), RealmRepresentation.class);
testRealms.add(realm);
}
@Test
public void defaultEcDirect() {
supportedEc(null, Algorithm.ECDH_ES);
}
@Test
public void supportedEcP521Direct() {
supportedEc("P-521", Algorithm.ECDH_ES);
}
@Test
public void supportedEcP384Direct() {
supportedEc("P-384", Algorithm.ECDH_ES);
}
@Test
public void supportedEcP256Direct() {
supportedEc("P-256", Algorithm.ECDH_ES);
}
@Test
public void unsupportedEcK163Direct() {
// NIST.FIPS.186-4 Koblitz Curve over Binary Field
unsupportedEc("K-163", Algorithm.ECDH_ES);
}
@Test
public void defaultEcKeyWrap128() {
supportedEc(null, Algorithm.ECDH_ES_A128KW);
}
@Test
public void defaultEcKeyWrap192() {
supportedEc(null, Algorithm.ECDH_ES_A192KW);
}
@Test
public void defaultEcKeyWrap256() {
supportedEc(null, Algorithm.ECDH_ES_A256KW);
}
@Test
public void supportedEcP521KeyWrap128() {
supportedEc("P-521", Algorithm.ECDH_ES_A128KW);
}
@Test
public void supportedEcP521KeyWrap192() {
supportedEc("P-521", Algorithm.ECDH_ES_A192KW);
}
@Test
public void supportedEcP521KeyWrap256() {
supportedEc("P-521", Algorithm.ECDH_ES_A256KW);
}
@Test
public void supportedEcP384KeyWrap128() {
supportedEc("P-384", Algorithm.ECDH_ES_A128KW);
}
@Test
public void supportedEcP384KeyWrap192() {
supportedEc("P-384", Algorithm.ECDH_ES_A192KW);
}
@Test
public void supportedEcP384KeyWrap256() {
supportedEc("P-384", Algorithm.ECDH_ES_A256KW);
}
@Test
public void supportedEcP256KeyWrap128() {
supportedEc("P-256", Algorithm.ECDH_ES_A128KW);
}
@Test
public void supportedEcP256KeyWrap192() {
supportedEc("P-256", Algorithm.ECDH_ES_A192KW);
}
@Test
public void supportedEcP256KeyWrap256() {
supportedEc("P-256", Algorithm.ECDH_ES_A256KW);
}
@Test
public void unsupportedEcK163KeyWrap128() {
// NIST.FIPS.186-4 Koblitz Curve over Binary Field
unsupportedEc("K-163", Algorithm.ECDH_ES_A128KW);
}
@Test
public void unsupportedEcK163KeyWrap192() {
// NIST.FIPS.186-4 Koblitz Curve over Binary Field
unsupportedEc("K-163", Algorithm.ECDH_ES_A192KW);
}
@Test
public void unsupportedEcK163KeyWrap256() {
// NIST.FIPS.186-4 Koblitz Curve over Binary Field
unsupportedEc("K-163", Algorithm.ECDH_ES_A256KW);
}
private String supportedEc(String ecInNistRep, String algorithm) {
long priority = System.currentTimeMillis();
ComponentRepresentation rep = createRep("valid", GeneratedEcdhKeyProviderFactory.ID);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
if (ecInNistRep != null) {
rep.getConfig().putSingle(ECDH_ELLIPTIC_CURVE_KEY, ecInNistRep);
} else {
ecInNistRep = DEFAULT_EC;
}
rep.getConfig().putSingle(ECDH_ALGORITHM_KEY, algorithm);
Response response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
String id = ApiUtil.getCreatedId(response);
getCleanup().addComponentId(id);
response.close();
ComponentRepresentation createdRep = adminClient.realm(TEST_REALM_NAME).components().component(id).toRepresentation();
// stands for the number of properties in the key provider config
assertEquals(3, createdRep.getConfig().size());
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst(Attributes.PRIORITY_KEY));
assertEquals(ecInNistRep, createdRep.getConfig().getFirst(ECDH_ELLIPTIC_CURVE_KEY));
assertEquals(algorithm, createdRep.getConfig().getFirst(ECDH_ALGORITHM_KEY));
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
for (KeyMetadataRepresentation k : keys.getKeys()) {
if (KeyType.EC.equals(k.getType()) && id.equals(k.getProviderId())) {
key = k;
break;
}
}
assertNotNull(key);
assertEquals(id, key.getProviderId());
assertEquals(KeyType.EC, key.getType());
assertEquals(KeyUse.ENC, key.getUse());
assertEquals(priority, key.getProviderPriority());
return id; // created key's component id
}
private void unsupportedEc(String ecInNistRep, String algorithmMode) {
long priority = System.currentTimeMillis();
ComponentRepresentation rep = createRep("valid", GeneratedEcdhKeyProviderFactory.ID);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
rep.getConfig().putSingle(ECDH_ELLIPTIC_CURVE_KEY, ecInNistRep);
rep.getConfig().putSingle(ECDH_ALGORITHM_KEY, algorithmMode);
boolean isEcAccepted = true;
Response response = null;
try {
response = adminClient.realm(TEST_REALM_NAME).components().add(rep);
String id = ApiUtil.getCreatedId(response);
getCleanup().addComponentId(id);
response.close();
} catch (WebApplicationException e) {
isEcAccepted = false;
} finally {
response.close();
}
assertEquals(isEcAccepted, false);
}
@Test
public void changeCurveFromP256ToP384Direct() throws Exception {
changeCurve("P-256", "P-384", Algorithm.ECDH_ES, Algorithm.ECDH_ES);
}
@Test
public void changeCurveFromP384ToP521Direct() throws Exception {
changeCurve("P-384", "P-521", Algorithm.ECDH_ES, Algorithm.ECDH_ES);
}
@Test
public void changeCurveFromP521ToP256Direct() throws Exception {
changeCurve("P-521", "P-256", Algorithm.ECDH_ES, Algorithm.ECDH_ES);
}
@Test
public void changeCurveFromP256ToP384KeyWrap() throws Exception {
changeCurve("P-256", "P-384", Algorithm.ECDH_ES_A128KW, Algorithm.ECDH_ES_A192KW);
}
@Test
public void changeCurveFromP384ToP521KeyWrap() throws Exception {
changeCurve("P-384", "P-521", Algorithm.ECDH_ES_A192KW, Algorithm.ECDH_ES_A256KW);
}
@Test
public void changeCurveFromP521ToP256KeyWrap() throws Exception {
changeCurve("P-521", "P-256", Algorithm.ECDH_ES_A256KW, Algorithm.ECDH_ES_A128KW);
}
private void changeCurve(String fromEcInNistRep, String toEcInNistRep, String fromAlgorithm, String toAlgorithm)
throws Exception {
String keyComponentId = supportedEc(fromEcInNistRep, fromAlgorithm);
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
KeysMetadataRepresentation.KeyMetadataRepresentation originalKey = null;
for (KeyMetadataRepresentation k : keys.getKeys()) {
if (KeyType.EC.equals(k.getType()) && keyComponentId.equals(k.getProviderId())) {
originalKey = k;
break;
}
}
ComponentRepresentation createdRep = adminClient.realm(TEST_REALM_NAME).components().component(keyComponentId).toRepresentation();
createdRep.getConfig().putSingle(ECDH_ELLIPTIC_CURVE_KEY, toEcInNistRep);
createdRep.getConfig().putSingle(ECDH_ALGORITHM_KEY, toAlgorithm);
adminClient.realm(TEST_REALM_NAME).components().component(keyComponentId).update(createdRep);
createdRep = adminClient.realm(TEST_REALM_NAME).components().component(keyComponentId).toRepresentation();
// stands for the number of properties in the key provider config
assertEquals(3, createdRep.getConfig().size());
assertEquals(toEcInNistRep, createdRep.getConfig().getFirst(ECDH_ELLIPTIC_CURVE_KEY));
assertEquals(toAlgorithm, createdRep.getConfig().getFirst(ECDH_ALGORITHM_KEY));
keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
KeysMetadataRepresentation.KeyMetadataRepresentation key = null;
for (KeyMetadataRepresentation k : keys.getKeys()) {
if (KeyType.EC.equals(k.getType()) && keyComponentId.equals(k.getProviderId())) {
key = k;
break;
}
}
assertNotNull(key);
assertEquals(keyComponentId, key.getProviderId());
assertNotEquals(originalKey.getKid(), key.getKid()); // kid is changed if key was regenerated
assertEquals(KeyType.EC, key.getType());
assertEquals(KeyUse.ENC, key.getUse());
assertEquals(originalKey.getAlgorithm(), fromAlgorithm);
assertEquals(key.getAlgorithm(), toAlgorithm);
assertEquals(toEcInNistRep, getCurveFromPublicKey(key.getPublicKey()));
}
protected ComponentRepresentation createRep(String name, String providerId) {
ComponentRepresentation rep = new ComponentRepresentation();
rep.setName(name);
rep.setParentId(adminClient.realm(TEST_REALM_NAME).toRepresentation().getId());
rep.setProviderId(providerId);
rep.setProviderType(KeyProvider.class.getName());
rep.setConfig(new MultivaluedHashMap<>());
return rep;
}
private String getCurveFromPublicKey(String publicEcKeyBase64Encoded) throws Exception {
KeyFactory kf = KeyFactory.getInstance("EC");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicEcKeyBase64Encoded));
ECPublicKey ecKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
return "P-" + ecKey.getParams().getCurve().getField().getFieldSize();
}
}

View file

@ -35,7 +35,7 @@ import org.junit.Test;
import org.keycloak.common.util.Base64;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.crypto.KeyType;
import org.keycloak.keys.AbstractEcdsaKeyProviderFactory;
import org.keycloak.keys.Attributes;
import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory;
import org.keycloak.keys.KeyProvider;
import org.keycloak.representations.idm.ComponentRepresentation;
@ -49,8 +49,8 @@ import org.keycloak.testsuite.pages.AppPage;
import org.keycloak.testsuite.pages.LoginPage;
public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
private static final String DEFAULT_EC = "P-256";
private static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
private static final String DEFAULT_EC = GeneratedEcdsaKeyProviderFactory.DEFAULT_ECDSA_ELLIPTIC_CURVE;
private static final String ECDSA_ELLIPTIC_CURVE_KEY = GeneratedEcdsaKeyProviderFactory.ECDSA_ELLIPTIC_CURVE_KEY;
private static final String TEST_REALM_NAME = "test";
@Rule
@ -99,7 +99,7 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
ComponentRepresentation rep = createRep("valid", GeneratedEcdsaKeyProviderFactory.ID);
rep.setConfig(new MultivaluedHashMap<>());
rep.getConfig().putSingle("priority", Long.toString(priority));
rep.getConfig().putSingle(Attributes.PRIORITY_KEY, Long.toString(priority));
if (ecInNistRep != null) {
rep.getConfig().putSingle(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
} else {
@ -115,7 +115,7 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
// stands for the number of properties in the key provider config
assertEquals(2, createdRep.getConfig().size());
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst("priority"));
assertEquals(Long.toString(priority), createdRep.getConfig().getFirst(Attributes.PRIORITY_KEY));
assertEquals(ecInNistRep, createdRep.getConfig().getFirst(ECDSA_ELLIPTIC_CURVE_KEY));
KeysMetadataRepresentation keys = adminClient.realm(TEST_REALM_NAME).keys().getKeyMetadata();
@ -210,7 +210,7 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest {
assertNotEquals(originalKey.getKid(), key.getKid()); // kid is changed if key was regenerated
assertEquals(KeyType.EC, key.getType());
assertNotEquals(originalKey.getAlgorithm(), key.getAlgorithm());
assertEquals(ToEcInNistRep, AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(key.getAlgorithm()));
assertEquals(ToEcInNistRep, GeneratedEcdsaKeyProviderFactory.convertJWSAlgorithmToECDomainParmNistRep(key.getAlgorithm()));
assertEquals(ToEcInNistRep, getCurveFromPublicKey(key.getPublicKey()));
}

View file

@ -56,7 +56,6 @@ import org.keycloak.testsuite.util.AdminClientUtil;
import org.keycloak.testsuite.util.ClientManager;
import org.keycloak.testsuite.util.OAuthClient;
import org.keycloak.testsuite.util.TokenSignatureUtil;
import org.keycloak.testsuite.wellknown.CustomOIDCWellKnownProviderFactory;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@ -144,15 +143,15 @@ public abstract class AbstractWellKnownProviderTest extends AbstractKeycloakTest
Assert.assertNames(oidcConfig.getAuthorizationSigningAlgValuesSupported(), Algorithm.PS256, Algorithm.PS384, Algorithm.PS512, Algorithm.RS256, Algorithm.RS384, Algorithm.RS512, Algorithm.ES256, Algorithm.ES384, Algorithm.ES512, Algorithm.HS256, Algorithm.HS384, Algorithm.HS512, Algorithm.EdDSA);
// request object encryption algorithms
Assert.assertNames(oidcConfig.getRequestObjectEncryptionAlgValuesSupported(), JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256, JWEConstants.RSA1_5);
Assert.assertNames(oidcConfig.getRequestObjectEncryptionAlgValuesSupported(), JWEConstants.ECDH_ES, JWEConstants.ECDH_ES_A128KW, JWEConstants.ECDH_ES_A192KW, JWEConstants.ECDH_ES_A256KW, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256, JWEConstants.RSA1_5);
Assert.assertNames(oidcConfig.getRequestObjectEncryptionEncValuesSupported(), JWEConstants.A256GCM, JWEConstants.A192GCM, JWEConstants.A128GCM, JWEConstants.A128CBC_HS256, JWEConstants.A192CBC_HS384, JWEConstants.A256CBC_HS512);
// Encryption algorithms
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
Assert.assertNames(oidcConfig.getIdTokenEncryptionAlgValuesSupported(), JWEConstants.ECDH_ES, JWEConstants.ECDH_ES_A128KW, JWEConstants.ECDH_ES_A192KW, JWEConstants.ECDH_ES_A256KW, JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
Assert.assertNames(oidcConfig.getIdTokenEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
Assert.assertNames(oidcConfig.getAuthorizationEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
Assert.assertNames(oidcConfig.getAuthorizationEncryptionAlgValuesSupported(), JWEConstants.ECDH_ES, JWEConstants.ECDH_ES_A128KW, JWEConstants.ECDH_ES_A192KW, JWEConstants.ECDH_ES_A256KW, JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
Assert.assertNames(oidcConfig.getAuthorizationEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
Assert.assertNames(oidcConfig.getUserInfoEncryptionAlgValuesSupported(), JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
Assert.assertNames(oidcConfig.getUserInfoEncryptionAlgValuesSupported(), JWEConstants.ECDH_ES, JWEConstants.ECDH_ES_A128KW, JWEConstants.ECDH_ES_A192KW, JWEConstants.ECDH_ES_A256KW, JWEConstants.RSA1_5, JWEConstants.RSA_OAEP, JWEConstants.RSA_OAEP_256);
Assert.assertNames(oidcConfig.getUserInfoEncryptionEncValuesSupported(), JWEConstants.A128CBC_HS256, JWEConstants.A128GCM, JWEConstants.A192CBC_HS384, JWEConstants.A192GCM, JWEConstants.A256CBC_HS512, JWEConstants.A256GCM);
// Client authentication

View file

@ -17,6 +17,14 @@ KcAdmTest
KcAdmCreateTest
SAMLServletAdapterTest
SamlSignatureTest
KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test
KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest
KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test
KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest
KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test
KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest
KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test
KcOidcBrokerJWEEcdhEsP384A192GcmTest
KcOidcBrokerJWETest
KcOidcBrokerJWEUserInfoJustEncryptedTest
KcSamlBrokerTest