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:
parent
da0864682a
commit
347f595913
4 changed files with 46 additions and 28 deletions
|
@ -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::[]
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue