Add ECDH-ES encyption algorithms to the java keystore key provider

Closes #32023

Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
rmartinc 2024-08-09 10:47:50 +02:00 committed by Marek Posolda
parent da0864682a
commit 347f595913
4 changed files with 46 additions and 28 deletions

View file

@ -103,3 +103,11 @@ no longer holds the list of identity providers. However, they are still availabl
when exporting a realm. when exporting a realm.
For information on how to migrate, see the link:{upgradingguide_link}[{upgradingguide_name}]. For information on how to migrate, see the link:{upgradingguide_link}[{upgradingguide_name}].
= Adding support for ECDH-ES encryption key management algorithms
Now {project_name} allows configuring ECDH-ES, ECDH-ES+A128KW, ECDH-ES+A192KW or ECDH-ES+A256KW as the encryption key management algorithm for clients. The Key Agreement with Elliptic Curve Diffie-Hellman Ephemeral Static (ECDH-ES) specification introduces three new header parameters for the JWT: `epk`, `apu` and `apv`. Currently {project_name} implementation only manages the compulsory `epk` while the other two (which are optional) are never added to the header. For more information about those algorithms please refer to the link:https://datatracker.ietf.org/doc/html/rfc7518#section-4.6[JSON Web Algorithms (JWA)].
ifeval::[{project_community}==true]
Many thanks to https://github.com/justin-tay[Justin Tay] for the contribution.
endif::[]

View file

@ -107,6 +107,8 @@ public class JavaKeystoreKeyProvider implements KeyProvider {
loadRSAKey(keyStore, keyAlias, KeyUse.ENC); loadRSAKey(keyStore, keyAlias, KeyUse.ENC);
case Algorithm.ES256, Algorithm.ES384, Algorithm.ES512 -> case Algorithm.ES256, Algorithm.ES384, Algorithm.ES512 ->
loadECKey(keyStore, keyAlias, KeyUse.SIG); loadECKey(keyStore, keyAlias, KeyUse.SIG);
case Algorithm.ECDH_ES, Algorithm.ECDH_ES_A128KW, Algorithm.ECDH_ES_A192KW, Algorithm.ECDH_ES_A256KW ->
loadECKey(keyStore, keyAlias, KeyUse.ENC);
case Algorithm.EdDSA -> case Algorithm.EdDSA ->
loadEdDSAKey(keyStore, keyAlias, KeyUse.SIG); loadEdDSAKey(keyStore, keyAlias, KeyUse.SIG);
case Algorithm.AES -> case Algorithm.AES ->

View file

@ -123,7 +123,8 @@ public class JavaKeystoreKeyProviderFactory implements KeyProviderFactory {
List.of(Algorithm.ES256, Algorithm.ES384, Algorithm.ES512), List.of(Algorithm.ES256, Algorithm.ES384, Algorithm.ES512),
Attributes.HS_ALGORITHM_PROPERTY.getOptions(), Attributes.HS_ALGORITHM_PROPERTY.getOptions(),
Attributes.RS_ALGORITHM_PROPERTY.getOptions(), Attributes.RS_ALGORITHM_PROPERTY.getOptions(),
Attributes.RS_ENC_ALGORITHM_PROPERTY.getOptions()) Attributes.RS_ENC_ALGORITHM_PROPERTY.getOptions(),
GeneratedEcdhKeyProviderFactory.ECDH_ALGORITHM_PROPERTY.getOptions())
.flatMap(Collection::stream) .flatMap(Collection::stream)
.toList(); .toList();
return new ProviderConfigProperty(Attributes.RS_ALGORITHM_PROPERTY.getName(), Attributes.RS_ALGORITHM_PROPERTY.getLabel(), return new ProviderConfigProperty(Attributes.RS_ALGORITHM_PROPERTY.getName(), Attributes.RS_ALGORITHM_PROPERTY.getLabel(),

View file

@ -84,54 +84,69 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void createJksRSA() throws Exception { public void createJksRSA() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.RSA, false); createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.RSA, Algorithm.RS256, false);
} }
@Test @Test
public void createPkcs12RSA() throws Exception { public void createPkcs12RSA() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.RSA, true); createSuccess(KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.RSA, Algorithm.RS256, true);
} }
@Test @Test
public void createBcfksRSA() throws Exception { public void createBcfksRSA() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.RSA, false); createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.RSA, Algorithm.RS256, false);
} }
@Test @Test
public void createJksECDSA() throws Exception { public void createJksECDSA() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.ECDSA, true); createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.ECDSA, Algorithm.ES256, true);
} }
@Test @Test
public void createPkcs12ECDSA() throws Exception { public void createPkcs12ECDSA() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.ECDSA, false); createSuccess(KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.ECDSA, Algorithm.ES256, false);
} }
@Test @Test
public void createBcfksECDSA() throws Exception { public void createBcfksECDSA() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.ECDSA, true); createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.ECDSA, Algorithm.ES256, true);
}
@Test
public void createJksECDSAECDHES() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.ECDSA, Algorithm.ECDH_ES, true);
}
@Test
public void createPkcs12ECDSAECDHESA192KW() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.ECDSA, Algorithm.ECDH_ES_A192KW, false);
}
@Test
public void createBcfksECDSAECDHESA256KW() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.ECDSA, Algorithm.ECDH_ES_A256KW, true);
} }
@Test @Test
public void createBcfksAES() throws Exception { public void createBcfksAES() throws Exception {
createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.AES, false); createSuccess(KeystoreUtil.KeystoreFormat.BCFKS, AlgorithmType.AES, Algorithm.AES, false);
} }
@Test @Test
public void createHMAC() throws Exception { public void createHMAC() throws Exception {
// BC provider fails storing HMAC in BCFKS (although BCFIPS works) // BC provider fails storing HMAC in BCFKS (although BCFIPS works)
createSuccess(isFips()? KeystoreUtil.KeystoreFormat.BCFKS : KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.HMAC, true); createSuccess(isFips()? KeystoreUtil.KeystoreFormat.BCFKS : KeystoreUtil.KeystoreFormat.PKCS12, AlgorithmType.HMAC, Algorithm.HS256, true);
} }
@Test @Test
public void createJksEdDSA() throws Exception { public void createJksEdDSA() throws Exception {
// BCFIPS does not support EdEC keys as it does not implement JDK interfaces // BCFIPS does not support EdEC keys as it does not implement JDK interfaces
createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.EDDSA, true); createSuccess(KeystoreUtil.KeystoreFormat.JKS, AlgorithmType.EDDSA, Algorithm.EdDSA, true);
} }
private void createSuccess(KeystoreUtil.KeystoreFormat keystoreType, AlgorithmType algorithmType, boolean vault) throws Exception { private void createSuccess(KeystoreUtil.KeystoreFormat keystoreType, AlgorithmType algorithmType, String keyAlgorithm, boolean vault) throws Exception {
KeystoreUtils.assumeKeystoreTypeSupported(keystoreType); KeystoreUtils.assumeKeystoreTypeSupported(keystoreType);
generateKeystore(keystoreType, algorithmType); generateKeystore(keystoreType, algorithmType, keyAlgorithm);
long priority = System.currentTimeMillis(); long priority = System.currentTimeMillis();
@ -182,7 +197,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeystore() throws Exception { public void invalidKeystore() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType()); generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.RSA, Algorithm.RS256);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
rep.getConfig().putSingle("keystore", "/nosuchfile"); rep.getConfig().putSingle("keystore", "/nosuchfile");
@ -192,7 +207,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeystorePassword() throws Exception { public void invalidKeystorePassword() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType()); generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.RSA, Algorithm.RS256);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
rep.getConfig().putSingle("keystore", "invalid"); rep.getConfig().putSingle("keystore", "invalid");
@ -202,7 +217,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeyAlias() throws Exception { public void invalidKeyAlias() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType()); generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.RSA, Algorithm.RS256);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
rep.getConfig().putSingle("keyAlias", "invalid"); rep.getConfig().putSingle("keyAlias", "invalid");
@ -222,7 +237,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
keystoreType = Enum.valueOf(KeystoreUtil.KeystoreFormat.class, supportedKsTypes[1]); keystoreType = Enum.valueOf(KeystoreUtil.KeystoreFormat.class, supportedKsTypes[1]);
log.infof("Fallback to keystore type '%s' for the invalidKeyPassword() test", keystoreType); log.infof("Fallback to keystore type '%s' for the invalidKeyPassword() test", keystoreType);
} }
generateKeystore(keystoreType); generateKeystore(keystoreType, AlgorithmType.RSA, Algorithm.RS256);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), keyAlgorithm);
rep.getConfig().putSingle("keyPassword", "invalid"); rep.getConfig().putSingle("keyPassword", "invalid");
@ -233,7 +248,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeyAlgorithmCreatedECButRegisteredRSA() throws Exception { public void invalidKeyAlgorithmCreatedECButRegisteredRSA() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.ECDSA); generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.ECDSA, Algorithm.RS256);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), Algorithm.RS256); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), Algorithm.RS256);
Response response = adminClient.realm("test").components().add(rep); Response response = adminClient.realm("test").components().add(rep);
@ -242,7 +257,7 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
@Test @Test
public void invalidKeyUsageForRS256() throws Exception { public void invalidKeyUsageForRS256() throws Exception {
generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.RSA); generateKeystore(KeystoreUtils.getPreferredKeystoreType(), AlgorithmType.RSA, Algorithm.RS256);
ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), Algorithm.RS256); ComponentRepresentation rep = createRep("valid", System.currentTimeMillis(), Algorithm.RS256);
rep.getConfig().putSingle(Attributes.KEY_USE, "enc"); rep.getConfig().putSingle(Attributes.KEY_USE, "enc");
@ -280,35 +295,27 @@ public class JavaKeystoreKeyProviderTest extends AbstractKeycloakTest {
return rep; return rep;
} }
private void generateKeystore(KeystoreUtil.KeystoreFormat keystoreType) throws Exception { private void generateKeystore(KeystoreUtil.KeystoreFormat keystoreType, AlgorithmType algorithmType, String keyAlgorithm) throws Exception {
generateKeystore(keystoreType, AlgorithmType.RSA); this.keyAlgorithm = keyAlgorithm;
}
private void generateKeystore(KeystoreUtil.KeystoreFormat keystoreType, AlgorithmType algorithmType) throws Exception {
switch (algorithmType) { switch (algorithmType) {
case RSA -> { case RSA -> {
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password"); this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password");
this.keyAlgorithm = Algorithm.RS256;
} }
case ECDSA -> { case ECDSA -> {
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password", this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password",
KeyUtils.generateECKey(Algorithm.ES256)); KeyUtils.generateECKey(Algorithm.ES256));
this.keyAlgorithm = Algorithm.ES256;
} }
case AES -> { case AES -> {
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password", this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password",
KeyUtils.generateSecretKey(Algorithm.AES, 256)); KeyUtils.generateSecretKey(Algorithm.AES, 256));
this.keyAlgorithm = Algorithm.AES;
} }
case HMAC -> { case HMAC -> {
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password", this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password",
KeyUtils.generateSecretKey(Algorithm.HS256, 256)); KeyUtils.generateSecretKey(Algorithm.HS256, 256));
this.keyAlgorithm = Algorithm.HS256;
} }
case EDDSA -> { case EDDSA -> {
this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password", this.generatedKeystore = KeystoreUtils.generateKeystore(folder, keystoreType, "keyalias", "password", "password",
KeyUtils.generateEdDSAKey(Algorithm.Ed25519)); KeyUtils.generateEdDSAKey(Algorithm.Ed25519));
this.keyAlgorithm = Algorithm.EdDSA;
} }
} }
} }