Support certificate creation for EC keys (#31817)

fixes #31816

Signed-off-by: Captain-P-Goldfish <captain.p.goldfish@gmx.de>
This commit is contained in:
Pascal Knüppel 2024-08-02 11:52:48 +02:00 committed by GitHub
parent f537343545
commit 4a15e1c2b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 62 additions and 24 deletions

View file

@ -42,7 +42,6 @@ import java.security.Signature;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey; import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec; import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; 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.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.keycloak.common.util.CertificateUtils.generateV1SelfSignedCertificate; 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) * 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); ECGenParameterSpec ecSpec = new ECGenParameterSpec(algorithm);
keyGen.initialize(ecSpec, randomGen); keyGen.initialize(ecSpec, randomGen);
KeyPair keyPair = keyGen.generateKeyPair(); 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(); ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();

View file

@ -130,7 +130,17 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0)); certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
// Content Signer // 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 // Certificate
return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen)); return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen));
@ -185,6 +195,7 @@ public class BCCertificateUtilsProvider implements CertificateUtilsProvider {
.setProvider(BouncyIntegration.PROVIDER); .setProvider(BouncyIntegration.PROVIDER);
break; break;
} }
case "EC":
case "ECDSA": { case "ECDSA": {
signerBuilder = new JcaContentSignerBuilder("SHA256WithECDSA") signerBuilder = new JcaContentSignerBuilder("SHA256WithECDSA")
.setProvider(BouncyIntegration.PROVIDER); .setProvider(BouncyIntegration.PROVIDER);

View file

@ -71,9 +71,9 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
* @param caPrivateKey the CA private key * @param caPrivateKey the CA private key
* @param caCert the CA certificate * @param caCert the CA certificate
* @param subject the subject name * @param subject the subject name
* *
* @return the x509 certificate * @return the x509 certificate
* *
* @throws Exception the exception * @throws Exception the exception
*/ */
@Override @Override
@ -112,8 +112,6 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
.setSerialNumber(serialNumber) .setSerialNumber(serialNumber)
.setSignatureAlgorithmName("SHA256withRSA")
.setSigningKey(caPrivateKey) .setSigningKey(caPrivateKey)
// Subject Key Identifier Extension // Subject Key Identifier Extension
@ -135,6 +133,14 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
// Basic Constraints // Basic Constraints
.addExtension(new BasicConstraintsExtension(true, true, 0)); .addExtension(new BasicConstraintsExtension(true, true, 0));
switch (caPrivateKey.getAlgorithm()){
case "EC":
cbuilder.setSignatureAlgorithmName("SHA256withECDSA");
break;
default:
cbuilder.setSignatureAlgorithmName("SHA256withRSA");
}
return cbuilder.build(); return cbuilder.build();
} catch (Exception e) { } catch (Exception e) {
@ -147,9 +153,9 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
* *
* @param caKeyPair the CA key pair * @param caKeyPair the CA key pair
* @param subject the subject name * @param subject the subject name
* *
* @return the x509 certificate * @return the x509 certificate
* *
* @throws Exception the exception * @throws Exception the exception
*/ */
@Override @Override
@ -182,10 +188,16 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
.setSigningKey(caKeyPair.getPrivate()) .setSigningKey(caKeyPair.getPrivate())
.setPublicKey(caKeyPair.getPublic()) .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(); return cbuilder.build();
} catch (Exception e) { } catch (Exception e) {
@ -211,14 +223,14 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
int type = decPolicy.peekType(); int type = decPolicy.peekType();
System.out.println("type " + type); System.out.println("type " + type);
DERDecoder der = new DERDecoder(decPolicy.decodeOctetString()); DERDecoder der = new DERDecoder(decPolicy.decodeOctetString());
List<String> policyList =new ArrayList<>(); List<String> policyList =new ArrayList<>();
while (der.hasNextElement()) { while (der.hasNextElement()) {
switch (der.peekType()) { switch (der.peekType()) {
case ASN1.SEQUENCE_TYPE: case ASN1.SEQUENCE_TYPE:
der.startSequence(); der.startSequence();
break; break;
case ASN1.OBJECT_IDENTIFIER_TYPE: case ASN1.OBJECT_IDENTIFIER_TYPE:
@ -247,7 +259,7 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
while ( der.hasNextElement() ) { while ( der.hasNextElement() ) {
switch (der.peekType()) { switch (der.peekType()) {
case ASN1.SEQUENCE_TYPE: case ASN1.SEQUENCE_TYPE:
der.startSequence(); der.startSequence();
break; break;
case ASN1.UTF8_STRING_TYPE: case ASN1.UTF8_STRING_TYPE:
@ -290,22 +302,22 @@ public class ElytronCertificateUtilsProvider implements CertificateUtilsProvider
try { try {
X500Principal subjectdn = subjectToX500Principle(dn); X500Principal subjectdn = subjectToX500Principle(dn);
X500Principal issuerdn = subjectToX500Principle(dn); X500Principal issuerdn = subjectToX500Principle(dn);
ZonedDateTime notValidBefore = ZonedDateTime.ofInstant(startDate.toInstant(), ZoneId.systemDefault()); ZonedDateTime notValidBefore = ZonedDateTime.ofInstant(startDate.toInstant(), ZoneId.systemDefault());
ZonedDateTime notValidAfter = ZonedDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault()); ZonedDateTime notValidAfter = ZonedDateTime.ofInstant(expiryDate.toInstant(), ZoneId.systemDefault());
X509CertificateBuilder cbuilder = new X509CertificateBuilder() X509CertificateBuilder cbuilder = new X509CertificateBuilder()
.setSubjectDn(subjectdn) .setSubjectDn(subjectdn)
.setIssuerDn(issuerdn) .setIssuerDn(issuerdn)
.setNotValidBefore(notValidBefore) .setNotValidBefore(notValidBefore)
.setNotValidAfter(notValidAfter) .setNotValidAfter(notValidAfter)
.setSigningKey(keyPair.getPrivate()) .setSigningKey(keyPair.getPrivate())
.setPublicKey(keyPair.getPublic()) .setPublicKey(keyPair.getPublic())
.addExtension(createPoliciesExtension(certificatePolicyOid)) .addExtension(createPoliciesExtension(certificatePolicyOid))
.setSignatureAlgorithmName("SHA256withRSA"); .setSignatureAlgorithmName("SHA256withRSA");
return cbuilder.build(); return cbuilder.build();

View file

@ -49,6 +49,7 @@ import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.keycloak.common.util.BouncyIntegration; import org.keycloak.common.util.BouncyIntegration;
import org.keycloak.common.crypto.CertificateUtilsProvider; import org.keycloak.common.crypto.CertificateUtilsProvider;
import org.keycloak.crypto.Algorithm;
import org.keycloak.crypto.JavaAlgorithm; import org.keycloak.crypto.JavaAlgorithm;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
@ -83,9 +84,9 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
* @param caPrivateKey the CA private key * @param caPrivateKey the CA private key
* @param caCert the CA certificate * @param caCert the CA certificate
* @param subject the subject name * @param subject the subject name
* *
* @return the x509 certificate * @return the x509 certificate
* *
* @throws Exception the exception * @throws Exception the exception
*/ */
public X509Certificate generateV3Certificate(KeyPair keyPair, PrivateKey caPrivateKey, X509Certificate caCert, 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)); certGen.addExtension(Extension.basicConstraints, true, new BasicConstraints(0));
// Content Signer // 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 // Certificate
return new JcaX509CertificateConverter().setProvider(BouncyIntegration.PROVIDER).getCertificate(certGen.build(sigGen)); 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 caKeyPair the CA key pair
* @param subject the subject name * @param subject the subject name
* *
* @return the x509 certificate * @return the x509 certificate
* *
* @throws Exception the exception * @throws Exception the exception
*/ */
public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) { public X509Certificate generateV1SelfSignedCertificate(KeyPair caKeyPair, String subject) {
@ -213,7 +223,7 @@ public class BCFIPSCertificateUtilsProvider implements CertificateUtilsProvider{
@Override @Override
public List<String> getCertificatePolicyList(X509Certificate cert) throws GeneralSecurityException { public List<String> getCertificatePolicyList(X509Certificate cert) throws GeneralSecurityException {
Extensions certExtensions = new JcaX509CertificateHolder(cert).getExtensions(); Extensions certExtensions = new JcaX509CertificateHolder(cert).getExtensions();
if (certExtensions == null) if (certExtensions == null)
throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate extensions were found"); throw new GeneralSecurityException("Certificate Policy validation was expected, but no certificate extensions were found");