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:
parent
5b83a7993c
commit
966a454548
53 changed files with 2650 additions and 180 deletions
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
|
@ -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,18 +28,9 @@ 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 {
|
||||
|
||||
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 static final String DEFAULT_EC_ELLIPTIC_CURVE = "P-256";
|
||||
|
||||
public final static ProviderConfigurationBuilder configurationBuilder() {
|
||||
return ProviderConfigurationBuilder.create()
|
||||
|
@ -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");
|
||||
|
@ -88,31 +77,4 @@ public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFact
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,7 +30,7 @@ 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) {
|
||||
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
||||
ConfigurationValidationHelper.check(model).checkList(ECDSA_ELLIPTIC_CURVE_PROPERTY, false);
|
||||
|
||||
String ecInNistRep = model.get(ECDSA_ELLIPTIC_CURVE_KEY);
|
||||
if (ecInNistRep == null) ecInNistRep = DEFAULT_ECDSA_ELLIPTIC_CURVE;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
protected Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
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);
|
||||
@Override
|
||||
protected boolean isValidKeyUse(KeyUse keyUse) {
|
||||
return KeyUse.SIG.equals(keyUse);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSupportedEcAlgorithm(String algorithm) {
|
||||
return (algorithm.equals(Algorithm.ES256) || algorithm.equals(Algorithm.ES384)
|
||||
|| algorithm.equals(Algorithm.ES512));
|
||||
}
|
||||
|
||||
@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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,17 +134,8 @@ 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);
|
||||
|
||||
ComponentExportRepresentation component = getConsumerKeyProvider();
|
||||
if (component != null) {
|
||||
MultivaluedHashMap<String, ComponentExportRepresentation> components = realm.getComponents();
|
||||
if (components == null) {
|
||||
components = new MultivaluedHashMap<>();
|
||||
|
@ -134,6 +143,26 @@ public class KcOidcBrokerJWETest extends AbstractBrokerTest {
|
|||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return realm;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -17,6 +17,14 @@ KcAdmTest
|
|||
KcAdmCreateTest
|
||||
SAMLServletAdapterTest
|
||||
SamlSignatureTest
|
||||
KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test
|
||||
KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest
|
||||
KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test
|
||||
KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest
|
||||
KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test
|
||||
KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest
|
||||
KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test
|
||||
KcOidcBrokerJWEEcdhEsP384A192GcmTest
|
||||
KcOidcBrokerJWETest
|
||||
KcOidcBrokerJWEUserInfoJustEncryptedTest
|
||||
KcSamlBrokerTest
|
||||
|
|
Loading…
Reference in a new issue