diff --git a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java index 428a647352..5ab2ec6fa5 100644 --- a/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/GeneratedEcdsaKeyProviderFactory.java @@ -28,7 +28,10 @@ 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 java.util.List; public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFactory { @@ -103,7 +106,7 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa generateKeys(model, ecInNistRep); logger.debugv("Generated keys for {0}", realm.getName()); } else { - String currentEc = model.get(ECDSA_ELLIPTIC_CURVE_KEY); + 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()); @@ -123,4 +126,14 @@ public class GeneratedEcdsaKeyProviderFactory extends AbstractEcdsaKeyProviderFa } } + 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); + } + } } 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 9781e38a89..8e7a27292d 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 @@ -17,9 +17,13 @@ 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 javax.ws.rs.WebApplicationException; @@ -28,8 +32,10 @@ import javax.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.KeyType; +import org.keycloak.keys.AbstractEcdsaKeyProviderFactory; import org.keycloak.keys.GeneratedEcdsaKeyProviderFactory; import org.keycloak.keys.KeyProvider; import org.keycloak.representations.idm.ComponentRepresentation; @@ -87,8 +93,8 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest { // NIST.FIPS.186-4 Koblitz Curve over Binary Field unsupportedEc("K-163"); } - - private void supportedEc(String ecInNistRep) { + + private String supportedEc(String ecInNistRep) { long priority = System.currentTimeMillis(); ComponentRepresentation rep = createRep("valid", GeneratedEcdsaKeyProviderFactory.ID); @@ -127,6 +133,8 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest { assertEquals(id, key.getProviderId()); assertEquals(KeyType.EC, key.getType()); assertEquals(priority, key.getProviderPriority()); + + return id; // created key's component id } private void unsupportedEc(String ecInNistRep) { @@ -152,6 +160,60 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest { assertEquals(isEcAccepted, false); } + @Test + public void changeCurveFromP256ToP384() throws Exception { + changeCurve("P-256", "P-384"); + } + + @Test + public void changeCurveFromP384ToP521() throws Exception { + changeCurve("P-384", "P-521"); + } + + @Test + public void changeCurveFromP521ToP256() throws Exception { + changeCurve("P-521", "P-256"); + } + + private void changeCurve(String FromEcInNistRep, String ToEcInNistRep) throws Exception { + String keyComponentId = supportedEc(FromEcInNistRep); + 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(ECDSA_ELLIPTIC_CURVE_KEY, ToEcInNistRep); + 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(2, createdRep.getConfig().size()); + assertEquals(ToEcInNistRep, createdRep.getConfig().getFirst(ECDSA_ELLIPTIC_CURVE_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()); + assertNotEquals(originalKey.getAlgorithm(), key.getAlgorithm()); + assertEquals(ToEcInNistRep, AbstractEcdsaKeyProviderFactory.convertAlgorithmToECDomainParmNistRep(key.getAlgorithm())); + assertEquals(ToEcInNistRep, getCurveFromPublicKey(key.getPublicKey())); + } + protected ComponentRepresentation createRep(String name, String providerId) { ComponentRepresentation rep = new ComponentRepresentation(); rep.setName(name); @@ -161,4 +223,11 @@ public class GeneratedEcdsaKeyProviderTest extends AbstractKeycloakTest { rep.setConfig(new MultivaluedHashMap<>()); return rep; } + + private String getCurveFromPublicKey(String publicEcdsaKeyBase64Encoded) throws Exception { + 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(); + } }