From 966a4545487c9f752d0c4b14cb106c968f5a4abd Mon Sep 17 00:00:00 2001
From: Justin Tay <49700559+justin-tay@users.noreply.github.com>
Date: Thu, 8 Aug 2024 23:29:35 +0800
Subject: [PATCH] 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>
---
.../common/crypto/CryptoConstants.java | 4 +
.../java/org/keycloak/crypto/Algorithm.java | 5 +
.../main/java/org/keycloak/jose/jwe/JWE.java | 7 +-
.../org/keycloak/jose/jwe/JWEConstants.java | 4 +
.../java/org/keycloak/jose/jwe/JWEHeader.java | 114 +++++-
.../org/keycloak/jose/jwe/JWERegistry.java | 2 +
.../jose/jwe/alg/DirectAlgorithmProvider.java | 6 +-
.../jose/jwe/alg/JWEAlgorithmProvider.java | 6 +-
.../def/AesKeyWrapAlgorithmProvider.java | 6 +-
.../crypto/def/BCEcdhEsAlgorithmProvider.java | 278 +++++++++++++++
.../crypto/def/DefaultCryptoProvider.java | 4 +
...tRsaKeyEncryptionJWEAlgorithmProvider.java | 6 +-
.../test/BCEcdhEsAlgorithmProviderTest.java | 142 ++++++++
.../elytron/AesKeyWrapAlgorithmProvider.java | 6 +-
.../ElytronEcdhEsAlgorithmProvider.java | 252 +++++++++++++
...nRsaKeyEncryptionJWEAlgorithmProvider.java | 6 +-
.../elytron/WildFlyElytronProvider.java | 4 +
.../ElytronEcdhEsAlgorithmProviderTest.java | 104 ++++++
.../fips/BCFIPSEcdhEsAlgorithmProvider.java | 262 ++++++++++++++
.../crypto/fips/FIPS1402Provider.java | 4 +
.../fips/FIPSAesKeyWrapAlgorithmProvider.java | 6 +-
...SRsaKeyEncryptionJWEAlgorithmProvider.java | 6 +-
.../test/BCFIPSECDSACryptoProviderTest.java | 11 +-
.../BCFIPSEcdhEsAlgorithmProviderTest.java | 117 ++++++
...hEsA128KwCekManagementProviderFactory.java | 37 ++
...hEsA192KwCekManagementProviderFactory.java | 37 ++
...hEsA256KwCekManagementProviderFactory.java | 37 ++
.../crypto/EcdhEsCekManagementProvider.java | 46 +++
.../EcdhEsCekManagementProviderFactory.java | 37 ++
...ovider.java => AbstractEcKeyProvider.java} | 10 +-
...java => AbstractEcKeyProviderFactory.java} | 50 +--
...AbstractGeneratedEcKeyProviderFactory.java | 123 +++++++
.../keys/GeneratedEcdhKeyProvider.java | 64 ++++
.../keys/GeneratedEcdhKeyProviderFactory.java | 155 ++++++++
.../keys/GeneratedEcdsaKeyProvider.java | 11 +-
.../GeneratedEcdsaKeyProviderFactory.java | 146 ++++----
...ycloak.crypto.CekManagementProviderFactory | 4 +
.../org.keycloak.keys.KeyProviderFactory | 1 +
.../org/keycloak/testsuite/util/KeyUtils.java | 13 +-
.../AbstractKcOidcBrokerJWEEcdhEsTest.java | 62 ++++
...erJWEEcdhEsA128KwP256A128CbcHs256Test.java | 26 ++
...cBrokerJWEEcdhEsA128KwP256A128GcmTest.java | 26 ++
...erJWEEcdhEsA192KwP384A192CbcHs384Test.java | 26 ++
...cBrokerJWEEcdhEsA192KwP384A192GcmTest.java | 26 ++
...erJWEEcdhEsA256KwP521A256CbcHs512Test.java | 26 ++
...cBrokerJWEEcdhEsA256KwP521A256GcmTest.java | 26 ++
...dcBrokerJWEEcdhEsP384A192CbcHs384Test.java | 26 ++
.../KcOidcBrokerJWEEcdhEsP384A192GcmTest.java | 26 ++
.../testsuite/broker/KcOidcBrokerJWETest.java | 61 +++-
.../keys/GeneratedEcdhKeyProviderTest.java | 337 ++++++++++++++++++
.../keys/GeneratedEcdsaKeyProviderTest.java | 12 +-
.../oidc/AbstractWellKnownProviderTest.java | 9 +-
.../tests/base/testsuites/fips-suite | 8 +
53 files changed, 2650 insertions(+), 180 deletions(-)
create mode 100644 crypto/default/src/main/java/org/keycloak/crypto/def/BCEcdhEsAlgorithmProvider.java
create mode 100644 crypto/default/src/test/java/org/keycloak/crypto/def/test/BCEcdhEsAlgorithmProviderTest.java
create mode 100644 crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronEcdhEsAlgorithmProvider.java
create mode 100644 crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronEcdhEsAlgorithmProviderTest.java
create mode 100644 crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSEcdhEsAlgorithmProvider.java
create mode 100644 crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSEcdhEsAlgorithmProviderTest.java
create mode 100644 services/src/main/java/org/keycloak/crypto/EcdhEsA128KwCekManagementProviderFactory.java
create mode 100644 services/src/main/java/org/keycloak/crypto/EcdhEsA192KwCekManagementProviderFactory.java
create mode 100644 services/src/main/java/org/keycloak/crypto/EcdhEsA256KwCekManagementProviderFactory.java
create mode 100644 services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProvider.java
create mode 100644 services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProviderFactory.java
rename services/src/main/java/org/keycloak/keys/{AbstractEcdsaKeyProvider.java => AbstractEcKeyProvider.java} (87%)
rename services/src/main/java/org/keycloak/keys/{AbstractEcdsaKeyProviderFactory.java => AbstractEcKeyProviderFactory.java} (60%)
create mode 100644 services/src/main/java/org/keycloak/keys/AbstractGeneratedEcKeyProviderFactory.java
create mode 100644 services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProvider.java
create mode 100644 services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProviderFactory.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerJWEEcdhEsTest.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192GcmTest.java
create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdhKeyProviderTest.java
diff --git a/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java b/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java
index 90a06e2e661..1c53e6186cd 100644
--- a/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java
+++ b/common/src/main/java/org/keycloak/common/crypto/CryptoConstants.java
@@ -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";
diff --git a/core/src/main/java/org/keycloak/crypto/Algorithm.java b/core/src/main/java/org/keycloak/crypto/Algorithm.java
index 7aa20a2c5cc..ab6efb6e2a0 100755
--- a/core/src/main/java/org/keycloak/crypto/Algorithm.java
+++ b/core/src/main/java/org/keycloak/crypto/Algorithm.java
@@ -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;
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWE.java b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
index e3f7eba873a..99d3d109fe3 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWE.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWE.java
@@ -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);
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
index ad33e73f7bf..89406e44290 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEConstants.java
@@ -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";
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
index 55cb782080f..c3b84011b86 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWEHeader.java
@@ -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 Marek Posolda
@@ -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);
+ }
+ }
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java b/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
index dc9aa1f80ea..2785f33910f 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/JWERegistry.java
@@ -38,6 +38,8 @@ class JWERegistry {
private static final Map 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());
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
index ef3ca0242ab..ed949bbb02a 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/DirectAlgorithmProvider.java
@@ -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];
}
}
diff --git a/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java b/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
index 80347f8e686..4ad5653764b 100644
--- a/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
+++ b/core/src/main/java/org/keycloak/jose/jwe/alg/JWEAlgorithmProvider.java
@@ -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;
}
diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/AesKeyWrapAlgorithmProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/AesKeyWrapAlgorithmProvider.java
index 36fb50b8caa..f623de86276 100644
--- a/crypto/default/src/main/java/org/keycloak/crypto/def/AesKeyWrapAlgorithmProvider.java
+++ b/crypto/default/src/main/java/org/keycloak/crypto/def/AesKeyWrapAlgorithmProvider.java
@@ -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();
diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/BCEcdhEsAlgorithmProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/BCEcdhEsAlgorithmProvider.java
new file mode 100644
index 00000000000..d9a1db967c7
--- /dev/null
+++ b/crypto/default/src/main/java/org/keycloak/crypto/def/BCEcdhEsAlgorithmProvider.java
@@ -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 Key
+ * Derivation for ECDH Key Agreement
+ */
+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);
+ }
+ }
+}
diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java
index bd0d0c9c925..b28bac33fd3 100644
--- a/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java
+++ b/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultCryptoProvider.java
@@ -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);
diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultRsaKeyEncryptionJWEAlgorithmProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultRsaKeyEncryptionJWEAlgorithmProvider.java
index b0bc523e7ca..f8875e2277c 100644
--- a/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultRsaKeyEncryptionJWEAlgorithmProvider.java
+++ b/crypto/default/src/main/java/org/keycloak/crypto/def/DefaultRsaKeyEncryptionJWEAlgorithmProvider.java
@@ -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();
diff --git a/crypto/default/src/test/java/org/keycloak/crypto/def/test/BCEcdhEsAlgorithmProviderTest.java b/crypto/default/src/test/java/org/keycloak/crypto/def/test/BCEcdhEsAlgorithmProviderTest.java
new file mode 100644
index 00000000000..d57c5faa2f1
--- /dev/null
+++ b/crypto/default/src/test/java/org/keycloak/crypto/def/test/BCEcdhEsAlgorithmProviderTest.java
@@ -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 Example
+ * ECDH-ES Key Agreement Computation
+ * @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");
+ }
+ }
+}
\ No newline at end of file
diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java
index ffc16e19610..0a5a3839588 100644
--- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java
+++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/AesKeyWrapAlgorithmProvider.java
@@ -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));
diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronEcdhEsAlgorithmProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronEcdhEsAlgorithmProvider.java
new file mode 100644
index 00000000000..436d7c8125d
--- /dev/null
+++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronEcdhEsAlgorithmProvider.java
@@ -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 Key
+ * Derivation for ECDH Key Agreement
+ */
+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");
+ }
+ }
+}
diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java
index 42431bc48ac..94e0b6f1525 100644
--- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java
+++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronRsaKeyEncryptionJWEAlgorithmProvider.java
@@ -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();
diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java
index 9a61e553a44..9cd84f291a2 100644
--- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java
+++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/WildFlyElytronProvider.java
@@ -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
diff --git a/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronEcdhEsAlgorithmProviderTest.java b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronEcdhEsAlgorithmProviderTest.java
new file mode 100644
index 00000000000..b07eeceb162
--- /dev/null
+++ b/crypto/elytron/src/test/java/org/keycloak/crypto/elytron/test/ElytronEcdhEsAlgorithmProviderTest.java
@@ -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 Example
+ * ECDH-ES Key Agreement Computation
+ * @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));
+ }
+}
\ No newline at end of file
diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSEcdhEsAlgorithmProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSEcdhEsAlgorithmProvider.java
new file mode 100644
index 00000000000..5506bd14141
--- /dev/null
+++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSEcdhEsAlgorithmProvider.java
@@ -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 Key
+ * Derivation for ECDH Key Agreement
+ */
+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 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 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 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);
+ }
+ }
+}
diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java
index 991c14ccc15..bdc672579b7 100644
--- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java
+++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPS1402Provider.java
@@ -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) {
diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSAesKeyWrapAlgorithmProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSAesKeyWrapAlgorithmProvider.java
index d563be8bdb7..59f527ca805 100644
--- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSAesKeyWrapAlgorithmProvider.java
+++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSAesKeyWrapAlgorithmProvider.java
@@ -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);
diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSRsaKeyEncryptionJWEAlgorithmProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSRsaKeyEncryptionJWEAlgorithmProvider.java
index a9a23c08e54..4fc6e7b25ef 100644
--- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSRsaKeyEncryptionJWEAlgorithmProvider.java
+++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/FIPSRsaKeyEncryptionJWEAlgorithmProvider.java
@@ -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();
diff --git a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSECDSACryptoProviderTest.java b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSECDSACryptoProviderTest.java
index 79e58f92766..d3e41dd9a49 100644
--- a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSECDSACryptoProviderTest.java
+++ b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSECDSACryptoProviderTest.java
@@ -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();
diff --git a/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSEcdhEsAlgorithmProviderTest.java b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSEcdhEsAlgorithmProviderTest.java
new file mode 100644
index 00000000000..6124220908a
--- /dev/null
+++ b/crypto/fips1402/src/test/java/org/keycloak/crypto/fips/test/BCFIPSEcdhEsAlgorithmProviderTest.java
@@ -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 Example
+ * ECDH-ES Key Agreement Computation
+ * @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);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/crypto/EcdhEsA128KwCekManagementProviderFactory.java b/services/src/main/java/org/keycloak/crypto/EcdhEsA128KwCekManagementProviderFactory.java
new file mode 100644
index 00000000000..3fa6e3334a6
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/EcdhEsA128KwCekManagementProviderFactory.java
@@ -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);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/EcdhEsA192KwCekManagementProviderFactory.java b/services/src/main/java/org/keycloak/crypto/EcdhEsA192KwCekManagementProviderFactory.java
new file mode 100644
index 00000000000..b07269f427e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/EcdhEsA192KwCekManagementProviderFactory.java
@@ -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);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/EcdhEsA256KwCekManagementProviderFactory.java b/services/src/main/java/org/keycloak/crypto/EcdhEsA256KwCekManagementProviderFactory.java
new file mode 100644
index 00000000000..135b2ad3295
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/EcdhEsA256KwCekManagementProviderFactory.java
@@ -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);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProvider.java b/services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProvider.java
new file mode 100644
index 00000000000..41a21c3f36e
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProvider.java
@@ -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;
+ }
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProviderFactory.java b/services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProviderFactory.java
new file mode 100644
index 00000000000..ff5ad053c32
--- /dev/null
+++ b/services/src/main/java/org/keycloak/crypto/EcdhEsCekManagementProviderFactory.java
@@ -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);
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/AbstractEcKeyProvider.java
similarity index 87%
rename from services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
rename to services/src/main/java/org/keycloak/keys/AbstractEcKeyProvider.java
index 647adb3c305..5ccbf7bf6bf 100644
--- a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/AbstractEcKeyProvider.java
@@ -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());
diff --git a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/AbstractEcKeyProviderFactory.java
similarity index 60%
rename from services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProviderFactory.java
rename to services/src/main/java/org/keycloak/keys/AbstractEcKeyProviderFactory.java
index f704292c4c9..e9b0f0c32c3 100644
--- a/services/src/main/java/org/keycloak/keys/AbstractEcdsaKeyProviderFactory.java
+++ b/services/src/main/java/org/keycloak/keys/AbstractEcKeyProviderFactory.java
@@ -18,11 +18,9 @@ package org.keycloak.keys;
import org.keycloak.component.ComponentModel;
import org.keycloak.component.ComponentValidationException;
-import org.keycloak.crypto.Algorithm;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.ConfigurationValidationHelper;
-import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.provider.ProviderConfigurationBuilder;
import java.security.KeyPair;
@@ -30,19 +28,10 @@ import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
-import static org.keycloak.provider.ProviderConfigProperty.LIST_TYPE;
+public abstract class AbstractEcKeyProviderFactory implements KeyProviderFactory {
-public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFactory {
+ public static final String DEFAULT_EC_ELLIPTIC_CURVE = "P-256";
- protected static final String ECDSA_PRIVATE_KEY_KEY = "ecdsaPrivateKey";
- protected static final String ECDSA_PUBLIC_KEY_KEY = "ecdsaPublicKey";
- protected static final String ECDSA_ELLIPTIC_CURVE_KEY = "ecdsaEllipticCurveKey";
-
- // only support NIST P-256 for ES256, P-384 for ES384, P-521 for ES512
- protected static ProviderConfigProperty ECDSA_ELLIPTIC_CURVE_PROPERTY = new ProviderConfigProperty(ECDSA_ELLIPTIC_CURVE_KEY, "Elliptic Curve", "Elliptic Curve used in ECDSA", LIST_TYPE,
- String.valueOf(GeneratedEcdsaKeyProviderFactory.DEFAULT_ECDSA_ELLIPTIC_CURVE),
- "P-256", "P-384", "P-521");
-
public final static ProviderConfigurationBuilder configurationBuilder() {
return ProviderConfigurationBuilder.create()
.property(Attributes.PRIORITY_PROPERTY)
@@ -58,7 +47,7 @@ public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFact
.checkBoolean(Attributes.ACTIVE_PROPERTY, false);
}
- public static KeyPair generateEcdsaKeyPair(String keySpecName) {
+ public static KeyPair generateEcKeyPair(String keySpecName) {
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom randomGen = SecureRandom.getInstance("SHA1PRNG");
@@ -75,44 +64,17 @@ public abstract class AbstractEcdsaKeyProviderFactory implements KeyProviderFact
String ecInSecRep = null;
switch(ecInNistRep) {
case "P-256" :
- ecInSecRep = "secp256r1";
+ ecInSecRep = "secp256r1";
break;
case "P-384" :
- ecInSecRep = "secp384r1";
+ ecInSecRep = "secp384r1";
break;
case "P-521" :
- ecInSecRep = "secp521r1";
+ ecInSecRep = "secp521r1";
break;
default :
// return null
}
return ecInSecRep;
}
-
- public static String convertECDomainParmNistRepToAlgorithm(String ecInNistRep) {
- switch(ecInNistRep) {
- case "P-256" :
- return Algorithm.ES256;
- case "P-384" :
- return Algorithm.ES384;
- case "P-521" :
- return Algorithm.ES512;
- default :
- return null;
- }
- }
-
- public static String convertAlgorithmToECDomainParmNistRep(String algorithm) {
- switch(algorithm) {
- case Algorithm.ES256 :
- return "P-256";
- case Algorithm.ES384 :
- return "P-384";
- case Algorithm.ES512 :
- return "P-521";
- default :
- return null;
- }
- }
-
}
diff --git a/services/src/main/java/org/keycloak/keys/AbstractGeneratedEcKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/AbstractGeneratedEcKeyProviderFactory.java
new file mode 100644
index 00000000000..dbcac7dedea
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/AbstractGeneratedEcKeyProviderFactory.java
@@ -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
+ extends AbstractEcKeyProviderFactory {
+
+ 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 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);
+ }
+ }
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProvider.java
new file mode 100644
index 00000000000..fae1aa82212
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProvider.java
@@ -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;
+ }
+
+ }
+
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProviderFactory.java
new file mode 100644
index 00000000000..967e7c987b3
--- /dev/null
+++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdhKeyProviderFactory.java
@@ -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 {
+
+ // 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 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 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);
+ }
+}
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java
index 94603215165..304281b326e 100644
--- a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java
+++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProvider.java
@@ -19,6 +19,7 @@ package org.keycloak.keys;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Base64;
import org.keycloak.component.ComponentModel;
+import org.keycloak.crypto.KeyUse;
import org.keycloak.crypto.KeyWrapper;
import org.keycloak.models.RealmModel;
@@ -29,15 +30,15 @@ import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
-public class GeneratedEcdsaKeyProvider extends AbstractEcdsaKeyProvider {
+public class GeneratedEcdsaKeyProvider extends AbstractEcKeyProvider {
private static final Logger logger = Logger.getLogger(GeneratedEcdsaKeyProvider.class);
public GeneratedEcdsaKeyProvider(RealmModel realm, ComponentModel model) {
super(realm, model);
}
- @Override
- protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
+ @Override
+ protected KeyWrapper loadKey(RealmModel realm, ComponentModel model) {
String privateEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PRIVATE_KEY_KEY);
String publicEcdsaKeyBase64Encoded = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY);
String ecInNistRep = model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_ELLIPTIC_CURVE_KEY);
@@ -51,8 +52,8 @@ public class GeneratedEcdsaKeyProvider extends AbstractEcdsaKeyProvider {
PublicKey decodedPublicKey = kf.generatePublic(publicKeySpec);
KeyPair keyPair = new KeyPair(decodedPublicKey, decodedPrivateKey);
-
- return createKeyWrapper(keyPair, ecInNistRep);
+ return createKeyWrapper(keyPair,
+ GeneratedEcdsaKeyProviderFactory.convertECDomainParmNistRepToJWSAlgorithm(ecInNistRep), KeyUse.SIG);
} catch (Exception e) {
logger.warnf("Exception at decodeEcdsaPublicKey. %s", e.toString());
return null;
diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java
index 5ab2ec6fa52..f1def90fa93 100644
--- a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java
+++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java
@@ -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 {
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 CONFIG_PROPERTIES = AbstractEcdsaKeyProviderFactory.configurationBuilder()
+ private static final List 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 config = new MultivaluedHashMap<>();
- config.putSingle(Attributes.PRIORITY_KEY, "-100");
- config.putSingle(ECDSA_ELLIPTIC_CURVE_KEY, convertAlgorithmToECDomainParmNistRep(algorithm));
- generated.setConfig(config);
-
- realm.addComponentModel(generated);
-
- return true;
- } else {
- return false;
- }
- }
-
@Override
public String getHelpText() {
return HELP_TEXT;
@@ -94,46 +72,74 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa
}
@Override
- public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel model) throws ComponentValidationException {
- super.validateConfiguration(session, realm, model);
+ protected Logger getLogger() {
+ return logger;
+ }
- ConfigurationValidationHelper.check(model).checkList(ECDSA_ELLIPTIC_CURVE_PROPERTY, false);
+ @Override
+ protected boolean isValidKeyUse(KeyUse keyUse) {
+ return KeyUse.SIG.equals(keyUse);
+ }
- String ecInNistRep = model.get(ECDSA_ELLIPTIC_CURVE_KEY);
- if (ecInNistRep == null) ecInNistRep = DEFAULT_ECDSA_ELLIPTIC_CURVE;
+ @Override
+ protected boolean isSupportedEcAlgorithm(String algorithm) {
+ return (algorithm.equals(Algorithm.ES256) || algorithm.equals(Algorithm.ES384)
+ || algorithm.equals(Algorithm.ES512));
+ }
- if (!(model.contains(ECDSA_PRIVATE_KEY_KEY) && model.contains(ECDSA_PUBLIC_KEY_KEY))) {
- generateKeys(model, ecInNistRep);
- logger.debugv("Generated keys for {0}", realm.getName());
- } else {
- String currentEc = getCurveFromPublicKey(model.getConfig().getFirst(GeneratedEcdsaKeyProviderFactory.ECDSA_PUBLIC_KEY_KEY));
- if (!ecInNistRep.equals(currentEc)) {
- generateKeys(model, ecInNistRep);
- logger.debugv("Elliptic Curve changed, generating new keys for {0}", realm.getName());
- }
+ @Override
+ protected String getEcEllipticCurveKey(String algorithm) {
+ return convertJWSAlgorithmToECDomainParmNistRep(algorithm);
+ }
+
+ @Override
+ protected ProviderConfigProperty getEcEllipticCurveProperty() {
+ return ECDSA_ELLIPTIC_CURVE_PROPERTY;
+ }
+
+ @Override
+ protected String getEcEllipticCurveKey() {
+ return ECDSA_ELLIPTIC_CURVE_KEY;
+ }
+
+ @Override
+ protected String getEcPrivateKeyKey() {
+ return ECDSA_PRIVATE_KEY_KEY;
+ }
+
+ @Override
+ protected String getEcPublicKeyKey() {
+ return ECDSA_PUBLIC_KEY_KEY;
+ }
+
+ @Override
+ protected String getDefaultEcEllipticCurve() {
+ return DEFAULT_ECDSA_ELLIPTIC_CURVE;
+ }
+
+ public static String convertECDomainParmNistRepToJWSAlgorithm(String ecInNistRep) {
+ switch(ecInNistRep) {
+ case "P-256" :
+ return Algorithm.ES256;
+ case "P-384" :
+ return Algorithm.ES384;
+ case "P-521" :
+ return Algorithm.ES512;
+ default :
+ return null;
}
}
- private void generateKeys(ComponentModel model, String ecInNistRep) {
- KeyPair keyPair;
- try {
- keyPair = generateEcdsaKeyPair(convertECDomainParmNistRepToSecRep(ecInNistRep));
- model.put(ECDSA_PRIVATE_KEY_KEY, Base64.encodeBytes(keyPair.getPrivate().getEncoded()));
- model.put(ECDSA_PUBLIC_KEY_KEY, Base64.encodeBytes(keyPair.getPublic().getEncoded()));
- model.put(ECDSA_ELLIPTIC_CURVE_KEY, ecInNistRep);
- } catch (Throwable t) {
- throw new ComponentValidationException("Failed to generate ECDSA keys", t);
- }
- }
-
- private String getCurveFromPublicKey(String publicEcdsaKeyBase64Encoded) {
- try {
- KeyFactory kf = KeyFactory.getInstance("EC");
- X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.decode(publicEcdsaKeyBase64Encoded));
- ECPublicKey ecKey = (ECPublicKey) kf.generatePublic(publicKeySpec);
- return "P-" + ecKey.getParams().getCurve().getField().getFieldSize();
- } catch (Throwable t) {
- throw new ComponentValidationException("Failed to get EC from its public key", t);
+ public static String convertJWSAlgorithmToECDomainParmNistRep(String algorithm) {
+ switch(algorithm) {
+ case Algorithm.ES256 :
+ return "P-256";
+ case Algorithm.ES384 :
+ return "P-384";
+ case Algorithm.ES512 :
+ return "P-521";
+ default :
+ return null;
}
}
}
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory
index 191513341dc..57575695f78 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.crypto.CekManagementProviderFactory
@@ -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
\ No newline at end of file
diff --git a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
index 0efff00b868..e0becc2b8a4 100644
--- a/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
+++ b/services/src/main/resources/META-INF/services/org.keycloak.keys.KeyProviderFactory
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java
index 143c0b490bc..b788904a649 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/KeyUtils.java
@@ -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();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerJWEEcdhEsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerJWEEcdhEsTest.java
new file mode 100644
index 00000000000..b55516011d1
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractKcOidcBrokerJWEEcdhEsTest.java
@@ -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 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 config = new MultivaluedHashMap<>();
+ config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
+ config.putSingle("ecdhAlgorithm", this.encAlg);
+ config.putSingle("ecdhEllipticCurveKey", this.crv);
+ component.setConfig(config);
+
+ return component;
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test.java
new file mode 100644
index 00000000000..1d514f9624c
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest.java
new file mode 100644
index 00000000000..cc6229cacee
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test.java
new file mode 100644
index 00000000000..ba8f2f5a893
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest.java
new file mode 100644
index 00000000000..eac7af5a10d
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test.java
new file mode 100644
index 00000000000..4163027ed10
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest.java
new file mode 100644
index 00000000000..eb1dcd6616a
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test.java
new file mode 100644
index 00000000000..bd753385522
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192GcmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192GcmTest.java
new file mode 100644
index 00000000000..3721b0345d7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWEEcdhEsP384A192GcmTest.java
@@ -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);
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWETest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWETest.java
index 1e8c673e8ea..13cf24c5f98 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWETest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerJWETest.java
@@ -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 config = new MultivaluedHashMap<>();
+ config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
+ config.putSingle("keyUse", KeyUse.ENC.name());
+ config.putSingle("algorithm", encAlg);
+ component.setConfig(config);
+ return component;
+ }
+
+ protected ComponentExportRepresentation getProviderKeyProvider() {
+ return null;
+ }
+
@Override
protected BrokerConfiguration getBrokerConfiguration() {
return new KcOidcBrokerConfiguration() {
@@ -116,23 +134,34 @@ public class KcOidcBrokerJWETest extends AbstractBrokerTest {
RealmRepresentation realm = super.createConsumerRealm();
if (encAlg != null) {
- // create the RSA component for the encryption in the specified alg
- ComponentExportRepresentation component = new ComponentExportRepresentation();
- component.setName("rsa-enc-generated");
- component.setProviderId("rsa-enc-generated");
-
- MultivaluedHashMap config = new MultivaluedHashMap<>();
- config.putSingle("priority", DefaultKeyProviders.DEFAULT_PRIORITY);
- config.putSingle("keyUse", KeyUse.ENC.name());
- config.putSingle("algorithm", encAlg);
- component.setConfig(config);
-
- MultivaluedHashMap components = realm.getComponents();
- if (components == null) {
- components = new MultivaluedHashMap<>();
- realm.setComponents(components);
+ ComponentExportRepresentation component = getConsumerKeyProvider();
+ if (component != null) {
+ MultivaluedHashMap components = realm.getComponents();
+ if (components == null) {
+ components = new MultivaluedHashMap<>();
+ realm.setComponents(components);
+ }
+ components.add(KeyProvider.class.getName(), component);
+ }
+ }
+
+ return realm;
+ }
+
+ @Override
+ public RealmRepresentation createProviderRealm() {
+ RealmRepresentation realm = super.createProviderRealm();
+
+ if (sigAlg != null) {
+ ComponentExportRepresentation component = getProviderKeyProvider();
+ if (component != null) {
+ MultivaluedHashMap components = realm.getComponents();
+ if (components == null) {
+ components = new MultivaluedHashMap<>();
+ realm.setComponents(components);
+ }
+ components.add(KeyProvider.class.getName(), component);
}
- components.add(KeyProvider.class.getName(), component);
}
return realm;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdhKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdhKeyProviderTest.java
new file mode 100644
index 00000000000..5c16a20fecf
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdhKeyProviderTest.java
@@ -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 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();
+ }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java
index 761e1e8f74e..653696770fd 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/keys/GeneratedEcdsaKeyProviderTest.java
@@ -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()));
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AbstractWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AbstractWellKnownProviderTest.java
index 6ea0d26099f..2e117c2ccb4 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AbstractWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/AbstractWellKnownProviderTest.java
@@ -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
diff --git a/testsuite/integration-arquillian/tests/base/testsuites/fips-suite b/testsuite/integration-arquillian/tests/base/testsuites/fips-suite
index a4b646c1530..f22d3f47167 100644
--- a/testsuite/integration-arquillian/tests/base/testsuites/fips-suite
+++ b/testsuite/integration-arquillian/tests/base/testsuites/fips-suite
@@ -17,6 +17,14 @@ KcAdmTest
KcAdmCreateTest
SAMLServletAdapterTest
SamlSignatureTest
+KcOidcBrokerJWEEcdhEsA128KwP256A128CbcHs256Test
+KcOidcBrokerJWEEcdhEsA128KwP256A128GcmTest
+KcOidcBrokerJWEEcdhEsA192KwP384A192CbcHs384Test
+KcOidcBrokerJWEEcdhEsA192KwP384A192GcmTest
+KcOidcBrokerJWEEcdhEsA256KwP521A256CbcHs512Test
+KcOidcBrokerJWEEcdhEsA256KwP521A256GcmTest
+KcOidcBrokerJWEEcdhEsP384A192CbcHs384Test
+KcOidcBrokerJWEEcdhEsP384A192GcmTest
KcOidcBrokerJWETest
KcOidcBrokerJWEUserInfoJustEncryptedTest
KcSamlBrokerTest