From 4a15e1c2b064014a9d3518caad1c9aba4f9479f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pascal=20Kn=C3=BCppel?= Date: Fri, 2 Aug 2024 11:52:48 +0200 Subject: [PATCH] Support certificate creation for EC keys (#31817) fixes #31816 Signed-off-by: Captain-P-Goldfish --- .../java/org/keycloak/jose/jwk/JWKTest.java | 7 ++- .../def/BCCertificateUtilsProvider.java | 13 +++++- .../ElytronCertificateUtilsProvider.java | 44 ++++++++++++------- .../fips/BCFIPSCertificateUtilsProvider.java | 22 +++++++--- 4 files changed, 62 insertions(+), 24 deletions(-) diff --git a/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java b/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java index 92ea511eafd..48266878d1e 100644 --- a/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java +++ b/core/src/test/java/org/keycloak/jose/jwk/JWKTest.java @@ -42,7 +42,6 @@ import java.security.Signature; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECPoint; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -50,6 +49,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.keycloak.common.util.CertificateUtils.generateV1SelfSignedCertificate; +import static org.keycloak.common.util.CertificateUtils.generateV3Certificate; /** * This is not tested in keycloak-core. The subclasses should be created in the crypto modules to make sure it is tested with corresponding modules (bouncycastle VS bouncycastle-fips) @@ -143,6 +143,11 @@ public abstract class JWKTest { ECGenParameterSpec ecSpec = new ECGenParameterSpec(algorithm); keyGen.initialize(ecSpec, randomGen); KeyPair keyPair = keyGen.generateKeyPair(); + KeyPair keyPair2 = keyGen.generateKeyPair(); + X509Certificate certificate = generateV1SelfSignedCertificate(keyPair, "root"); + X509Certificate certificate2 = generateV3Certificate(keyPair2, keyPair.getPrivate(), certificate, "child"); + certificate.verify(keyPair.getPublic()); + certificate2.verify(keyPair.getPublic()); ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic(); diff --git a/crypto/default/src/main/java/org/keycloak/crypto/def/BCCertificateUtilsProvider.java b/crypto/default/src/main/java/org/keycloak/crypto/def/BCCertificateUtilsProvider.java index b9aa926633d..ab99ef756bd 100755 --- a/crypto/default/src/main/java/org/keycloak/crypto/def/BCCertificateUtilsProvider.java +++ b/crypto/default/src/main/java/org/keycloak/crypto/def/BCCertificateUtilsProvider.java @@ -130,7 +130,17 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider { certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0)); // Content Signer - ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER).build(caPrivateKey); + ContentSigner sigGen; + switch (keyPair.getPublic().getAlgorithm()) + { + case "EC": + sigGen = new JcaContentSignerBuilder("SHA256WithECDSA").setProvider(BouncyIntegration.PROVIDER) + .build(caPrivateKey); + break; + default: + sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER) + .build(caPrivateKey); + } // Certificate return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen)); @@ -185,6 +195,7 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider { .setProvider(BouncyIntegration.PROVIDER); break; } + case "EC": case "ECDSA": { signerBuilder = new JcaContentSignerBuilder("SHA256WithECDSA") .setProvider(BouncyIntegration.PROVIDER); diff --git a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtilsProvider.java b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtilsProvider.java index 8ecb64f3c45..cfabff65c6c 100644 --- a/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtilsProvider.java +++ b/crypto/elytron/src/main/java/org/keycloak/crypto/elytron/ElytronCertificateUtilsProvider.java @@ -71,9 +71,9 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider * @param caPrivateKey the CA private key * @param caCert the CA certificate * @param subject the subject name - * + * * @return the x509 certificate - * + * * @throws Exception the exception */ @Override @@ -112,8 +112,6 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider .setSerialNumber(serialNumber) - .setSignatureAlgorithmName("SHA256withRSA") - .setSigningKey(caPrivateKey) // Subject Key Identifier Extension @@ -135,6 +133,14 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider // Basic Constraints .addExtension(new BasicConstraintsExtension(true, true, 0)); + switch (caPrivateKey.getAlgorithm()){ + case "EC": + cbuilder.setSignatureAlgorithmName("SHA256withECDSA"); + break; + default: + cbuilder.setSignatureAlgorithmName("SHA256withRSA"); + } + return cbuilder.build(); } catch (Exception e) { @@ -147,9 +153,9 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider * * @param caKeyPair the CA key pair * @param subject the subject name - * + * * @return the x509 certificate - * + * * @throws Exception the exception */ @Override @@ -182,10 +188,16 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider .setSigningKey(caKeyPair.getPrivate()) .setPublicKey(caKeyPair.getPublic()) - .setSerialNumber(serialNumber) + .setSerialNumber(serialNumber); + + switch (caKeyPair.getPrivate().getAlgorithm()){ + case "EC": + cbuilder.setSignatureAlgorithmName("SHA256withECDSA"); + break; + default: + cbuilder.setSignatureAlgorithmName("SHA256withRSA"); + } - .setSignatureAlgorithmName("SHA256withRSA"); - return cbuilder.build(); } catch (Exception e) { @@ -211,14 +223,14 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider int type = decPolicy.peekType(); System.out.println("type " + type); - + DERDecoder der = new DERDecoder(decPolicy.decodeOctetString()); List policyList =new ArrayList<>(); while (der.hasNextElement()) { switch (der.peekType()) { - case ASN1.SEQUENCE_TYPE: + case ASN1.SEQUENCE_TYPE: der.startSequence(); break; case ASN1.OBJECT_IDENTIFIER_TYPE: @@ -247,7 +259,7 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider while ( der.hasNextElement() ) { switch (der.peekType()) { - case ASN1.SEQUENCE_TYPE: + case ASN1.SEQUENCE_TYPE: der.startSequence(); break; case ASN1.UTF8_STRING_TYPE: @@ -290,22 +302,22 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider try { X500Principal subjectdn = subjectToX500Principle(dn); X500Principal issuerdn = subjectToX500Principle(dn); - + ZonedDateTime notValidBefore = ZonedDateTime.ofInstant(startDate.toInstant(), ZoneId.systemDefault()); ZonedDateTime notValidAfter = ZonedDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()); X509CertificateBuilder cbuilder = new X509CertificateBuilder() .setSubjectDn(subjectdn) .setIssuerDn(issuerdn) - + .setNotValidBefore(notValidBefore) .setNotValidAfter(notValidAfter) - + .setSigningKey(keyPair.getPrivate()) .setPublicKey(keyPair.getPublic()) .addExtension(createPoliciesExtension(certificatePolicyOid)) - + .setSignatureAlgorithmName("SHA256withRSA"); return cbuilder.build(); diff --git a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSCertificateUtilsProvider.java b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSCertificateUtilsProvider.java index e9de665cf9e..5885bae79a8 100755 --- a/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSCertificateUtilsProvider.java +++ b/crypto/fips1402/src/main/java/org/keycloak/crypto/fips/BCFIPSCertificateUtilsProvider.java @@ -49,6 +49,7 @@ import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.crypto.CertificateUtilsProvider; +import org.keycloak.crypto.Algorithm; import org.keycloak.crypto.JavaAlgorithm; import java.io.ByteArrayInputStream; @@ -83,9 +84,9 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{ * @param caPrivateKey the CA private key * @param caCert the CA certificate * @param subject the subject name - * + * * @return the x509 certificate - * + * * @throws Exception the exception */ public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert, @@ -132,7 +133,16 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{ certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0)); // Content Signer - ContentSigner sigGen = new JcaContentSignerBuilder("SHA1WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER).build(caPrivateKey); + ContentSigner sigGen; + switch (caPrivateKey.getAlgorithm()){ + case "EC": + sigGen = new JcaContentSignerBuilder("SHA256WithECDSA").setProvider(BouncyIntegration.PROVIDER) + .build(caPrivateKey); + break; + default: + sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BouncyIntegration.PROVIDER) + .build(caPrivateKey); + } // Certificate return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen)); @@ -146,9 +156,9 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{ * * @param caKeyPair the CA key pair * @param subject the subject name - * + * * @return the x509 certificate - * + * * @throws Exception the exception */ public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) { @@ -213,7 +223,7 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{ @Override public List getCertificatePolicyList(X509Certificate cert) throws GeneralSecurityException { - + Extensions certExtensions = new JcaX509CertificateHolder(cert).getExtensions(); if (certExtensions == null) throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate extensions were found");