Specify algorithm for key pair in self signed certificate to generate EC or RSA based certificate. (#10223)

Motivation:

EC is better than RSA because of the small key size, efficient and secure which makes it perfect for testing purposes.

Modification:

Added support to specify an algorithm (EC or RSA) in constructors for key pair generation. The default key size is 256-bits as promulgated by NSA Suite B.

Result:
Able to generate a self-signed certificate of EC or RSA.
This commit is contained in:
Aayush Atharva 2020-04-29 20:22:07 +05:30 committed by Norman Maurer
parent a62fcd9d50
commit f22993a530
3 changed files with 124 additions and 41 deletions

View File

@ -42,8 +42,8 @@ final class BouncyCastleSelfSignedCertGenerator {
private static final Provider PROVIDER = new BouncyCastleProvider(); private static final Provider PROVIDER = new BouncyCastleProvider();
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter) static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
throws Exception { String algorithm) throws Exception {
PrivateKey key = keypair.getPrivate(); PrivateKey key = keypair.getPrivate();
// Prepare the information required for generating an X.509 certificate. // Prepare the information required for generating an X.509 certificate.
@ -51,7 +51,8 @@ final class BouncyCastleSelfSignedCertGenerator {
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder( X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
owner, new BigInteger(64, random), notBefore, notAfter, owner, keypair.getPublic()); owner, new BigInteger(64, random), notBefore, notAfter, owner, keypair.getPublic());
ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(key); ContentSigner signer = new JcaContentSignerBuilder(
algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256WithRSAEncryption").build(key);
X509CertificateHolder certHolder = builder.build(signer); X509CertificateHolder certHolder = builder.build(signer);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder); X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder);
cert.verify(keypair.getPublic()); cert.verify(keypair.getPublic());

View File

@ -42,8 +42,8 @@ import static io.netty.handler.ssl.util.SelfSignedCertificate.*;
*/ */
final class OpenJdkSelfSignedCertGenerator { final class OpenJdkSelfSignedCertGenerator {
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter) static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
throws Exception { String algorithm) throws Exception {
PrivateKey key = keypair.getPrivate(); PrivateKey key = keypair.getPrivate();
// Prepare the information required for generating an X.509 certificate. // Prepare the information required for generating an X.509 certificate.
@ -68,12 +68,12 @@ final class OpenJdkSelfSignedCertGenerator {
// Sign the cert to identify the algorithm that's used. // Sign the cert to identify the algorithm that's used.
X509CertImpl cert = new X509CertImpl(info); X509CertImpl cert = new X509CertImpl(info);
cert.sign(key, "SHA256withRSA"); cert.sign(key, algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
// Update the algorithm and sign again. // Update the algorithm and sign again.
info.set(CertificateAlgorithmId.NAME + '.' + CertificateAlgorithmId.ALGORITHM, cert.get(X509CertImpl.SIG_ALG)); info.set(CertificateAlgorithmId.NAME + '.' + CertificateAlgorithmId.ALGORITHM, cert.get(X509CertImpl.SIG_ALG));
cert = new X509CertImpl(info); cert = new X509CertImpl(info);
cert.sign(key, "SHA256withRSA"); cert.sign(key, algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
cert.verify(keypair.getPublic()); cert.verify(keypair.getPublic());
return newSelfSignedCertificate(fqdn, key, cert); return newSelfSignedCertificate(fqdn, key, cert);

View File

@ -49,7 +49,7 @@ import java.util.Date;
* It is purely for testing purposes, and thus it is very insecure. * It is purely for testing purposes, and thus it is very insecure.
* It even uses an insecure pseudo-random generator for faster generation internally. * It even uses an insecure pseudo-random generator for faster generation internally.
* </p><p> * </p><p>
* An X.509 certificate file and a RSA private key file are generated in a system's temporary directory using * An X.509 certificate file and a EC/RSA private key file are generated in a system's temporary directory using
* {@link java.io.File#createTempFile(String, String)}, and they are deleted when the JVM exits using * {@link java.io.File#createTempFile(String, String)}, and they are deleted when the JVM exits using
* {@link java.io.File#deleteOnExit()}. * {@link java.io.File#deleteOnExit()}.
* </p><p> * </p><p>
@ -69,7 +69,7 @@ public final class SelfSignedCertificate {
"io.netty.selfSignedCertificate.defaultNotAfter", 253402300799000L)); "io.netty.selfSignedCertificate.defaultNotAfter", 253402300799000L));
/** /**
* FIPS 140-2 encryption requires the key length to be 2048 bits or greater. * FIPS 140-2 encryption requires the RSA key length to be 2048 bits or greater.
* Let's use that as a sane default but allow the default to be set dynamically * Let's use that as a sane default but allow the default to be set dynamically
* for those that need more stringent security requirements. * for those that need more stringent security requirements.
*/ */
@ -83,31 +83,61 @@ public final class SelfSignedCertificate {
/** /**
* Creates a new instance. * Creates a new instance.
* <p> Algorithm: RSA </p>
*/ */
public SelfSignedCertificate() throws CertificateException { public SelfSignedCertificate() throws CertificateException {
this(DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER); this(DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
} }
/** /**
* Creates a new instance. * Creates a new instance.
* <p> Algorithm: RSA </p>
*
* @param notBefore Certificate is not valid before this time * @param notBefore Certificate is not valid before this time
* @param notAfter Certificate is not valid after this time * @param notAfter Certificate is not valid after this time
*/ */
public SelfSignedCertificate(Date notBefore, Date notAfter) throws CertificateException { public SelfSignedCertificate(Date notBefore, Date notAfter)
this("example.com", notBefore, notAfter); throws CertificateException {
this("localhost", notBefore, notAfter, "RSA", DEFAULT_KEY_LENGTH_BITS);
}
/**
* Creates a new instance.
*
* @param notBefore Certificate is not valid before this time
* @param notAfter Certificate is not valid after this time
* @param algorithm Key pair algorithm
* @param bits the number of bits of the generated private key
*/
public SelfSignedCertificate(Date notBefore, Date notAfter, String algorithm, int bits)
throws CertificateException {
this("localhost", notBefore, notAfter, algorithm, bits);
}
/**
* Creates a new instance.
* <p> Algorithm: RSA </p>
*
* @param fqdn a fully qualified domain name
*/
public SelfSignedCertificate(String fqdn) throws CertificateException {
this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
} }
/** /**
* Creates a new instance. * Creates a new instance.
* *
* @param fqdn a fully qualified domain name * @param fqdn a fully qualified domain name
* @param algorithm Key pair algorithm
* @param bits the number of bits of the generated private key
*/ */
public SelfSignedCertificate(String fqdn) throws CertificateException { public SelfSignedCertificate(String fqdn, String algorithm, int bits) throws CertificateException {
this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER); this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm, bits);
} }
/** /**
* Creates a new instance. * Creates a new instance.
* <p> Algorithm: RSA </p>
* *
* @param fqdn a fully qualified domain name * @param fqdn a fully qualified domain name
* @param notBefore Certificate is not valid before this time * @param notBefore Certificate is not valid before this time
@ -116,51 +146,103 @@ public final class SelfSignedCertificate {
public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException { public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException {
// Bypass entropy collection by using insecure random generator. // Bypass entropy collection by using insecure random generator.
// We just want to generate it without any delay because it's for testing purposes only. // We just want to generate it without any delay because it's for testing purposes only.
this(fqdn, ThreadLocalInsecureRandom.current(), DEFAULT_KEY_LENGTH_BITS, notBefore, notAfter); this(fqdn, ThreadLocalInsecureRandom.current(), DEFAULT_KEY_LENGTH_BITS, notBefore, notAfter, "RSA");
} }
/** /**
* Creates a new instance. * Creates a new instance.
* *
* @param fqdn a fully qualified domain name * @param fqdn a fully qualified domain name
* @param random the {@link java.security.SecureRandom} to use * @param notBefore Certificate is not valid before this time
* @param notAfter Certificate is not valid after this time
* @param algorithm Key pair algorithm
* @param bits the number of bits of the generated private key * @param bits the number of bits of the generated private key
*/ */
public SelfSignedCertificate(String fqdn, SecureRandom random, int bits) throws CertificateException { public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter, String algorithm, int bits)
this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER); throws CertificateException {
// Bypass entropy collection by using insecure random generator.
// We just want to generate it without any delay because it's for testing purposes only.
this(fqdn, ThreadLocalInsecureRandom.current(), bits, notBefore, notAfter, algorithm);
}
/**
* Creates a new instance.
* <p> Algorithm: RSA </p>
*
* @param fqdn a fully qualified domain name
* @param random the {@link SecureRandom} to use
* @param bits the number of bits of the generated private key
*/
public SelfSignedCertificate(String fqdn, SecureRandom random, int bits)
throws CertificateException {
this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA");
} }
/** /**
* Creates a new instance. * Creates a new instance.
* *
* @param fqdn a fully qualified domain name * @param fqdn a fully qualified domain name
* @param random the {@link java.security.SecureRandom} to use * @param random the {@link SecureRandom} to use
* @param algorithm Key pair algorithm
* @param bits the number of bits of the generated private key
*/
public SelfSignedCertificate(String fqdn, SecureRandom random, String algorithm, int bits)
throws CertificateException {
this(fqdn, random, bits, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm);
}
/**
* Creates a new instance.
* <p> Algorithm: RSA </p>
*
* @param fqdn a fully qualified domain name
* @param random the {@link SecureRandom} to use
* @param bits the number of bits of the generated private key * @param bits the number of bits of the generated private key
* @param notBefore Certificate is not valid before this time * @param notBefore Certificate is not valid before this time
* @param notAfter Certificate is not valid after this time * @param notAfter Certificate is not valid after this time
*/ */
public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter) public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter)
throws CertificateException { throws CertificateException {
// Generate an RSA key pair. this(fqdn, random, bits, notBefore, notAfter, "RSA");
}
/**
* Creates a new instance.
*
* @param fqdn a fully qualified domain name
* @param random the {@link SecureRandom} to use
* @param bits the number of bits of the generated private key
* @param notBefore Certificate is not valid before this time
* @param notAfter Certificate is not valid after this time
* @param algorithm Key pair algorithm
*/
public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter,
String algorithm) throws CertificateException {
if (!algorithm.equalsIgnoreCase("EC") && !algorithm.equalsIgnoreCase("RSA")) {
throw new IllegalArgumentException("Algorithm not valid: " + algorithm);
}
final KeyPair keypair; final KeyPair keypair;
try { try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
keyGen.initialize(bits, random); keyGen.initialize(bits, random);
keypair = keyGen.generateKeyPair(); keypair = keyGen.generateKeyPair();
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
// Should not reach here because every Java implementation must have RSA key pair generator. // Should not reach here because every Java implementation must have RSA and EC key pair generator.
throw new Error(e); throw new Error(e);
} }
String[] paths; String[] paths;
try { try {
// Try the OpenJDK's proprietary implementation. // Try the OpenJDK's proprietary implementation.
paths = OpenJdkSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter); paths = OpenJdkSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter, algorithm);
} catch (Throwable t) { } catch (Throwable t) {
logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t); logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t);
try { try {
// Try Bouncy Castle if the current JVM didn't have sun.security.x509. // Try Bouncy Castle if the current JVM didn't have sun.security.x509.
paths = BouncyCastleSelfSignedCertGenerator.generate(fqdn, keypair, random, notBefore, notAfter); paths = BouncyCastleSelfSignedCertGenerator.generate(
fqdn, keypair, random, notBefore, notAfter, algorithm);
} catch (Throwable t2) { } catch (Throwable t2) {
logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t2); logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t2);
final CertificateException certificateException = new CertificateException( final CertificateException certificateException = new CertificateException(