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 GitHub
parent 8f7ca2b4ef
commit 9427255ffb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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();
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter)
throws Exception {
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
String algorithm) throws Exception {
PrivateKey key = keypair.getPrivate();
// Prepare the information required for generating an X.509 certificate.
@ -51,7 +51,8 @@ final class BouncyCastleSelfSignedCertGenerator {
X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
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);
X509Certificate cert = new JcaX509CertificateConverter().setProvider(PROVIDER).getCertificate(certHolder);
cert.verify(keypair.getPublic());

View File

@ -44,8 +44,8 @@ import static io.netty.handler.ssl.util.SelfSignedCertificate.*;
final class OpenJdkSelfSignedCertGenerator {
@SuppressJava6Requirement(reason = "Usage guarded by dependency check")
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter)
throws Exception {
static String[] generate(String fqdn, KeyPair keypair, SecureRandom random, Date notBefore, Date notAfter,
String algorithm) throws Exception {
PrivateKey key = keypair.getPrivate();
// Prepare the information required for generating an X.509 certificate.
@ -70,12 +70,12 @@ final class OpenJdkSelfSignedCertGenerator {
// Sign the cert to identify the algorithm that's used.
X509CertImpl cert = new X509CertImpl(info);
cert.sign(key, "SHA256withRSA");
cert.sign(key, algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
// Update the algorithm and sign again.
info.set(CertificateAlgorithmId.NAME + '.' + CertificateAlgorithmId.ALGORITHM, cert.get(X509CertImpl.SIG_ALG));
cert = new X509CertImpl(info);
cert.sign(key, "SHA256withRSA");
cert.sign(key, algorithm.equalsIgnoreCase("EC") ? "SHA256withECDSA" : "SHA256withRSA");
cert.verify(keypair.getPublic());
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 even uses an insecure pseudo-random generator for faster generation internally.
* </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#deleteOnExit()}.
* </p><p>
@ -69,7 +69,7 @@ public final class SelfSignedCertificate {
"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
* for those that need more stringent security requirements.
*/
@ -83,84 +83,166 @@ public final class SelfSignedCertificate {
/**
* Creates a new instance.
* <p> Algorithm: RSA </p>
*/
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.
* <p> Algorithm: RSA </p>
*
* @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 {
this("example.com", notBefore, notAfter);
public SelfSignedCertificate(Date notBefore, Date 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);
this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, "RSA", DEFAULT_KEY_LENGTH_BITS);
}
/**
* 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, String algorithm, int bits) throws CertificateException {
this(fqdn, DEFAULT_NOT_BEFORE, DEFAULT_NOT_AFTER, algorithm, bits);
}
/**
* Creates a new instance.
* <p> Algorithm: RSA </p>
*
* @param fqdn a fully qualified domain name
* @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, Date notBefore, Date notAfter) 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(), DEFAULT_KEY_LENGTH_BITS, notBefore, notAfter);
this(fqdn, ThreadLocalInsecureRandom.current(), DEFAULT_KEY_LENGTH_BITS, notBefore, notAfter, "RSA");
}
/**
* Creates a new instance.
*
* @param fqdn a fully qualified domain name
* @param random the {@link java.security.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);
}
/**
* Creates a new instance.
*
* @param fqdn a fully qualified domain name
* @param random the {@link java.security.SecureRandom} to use
* @param bits the number of bits of the generated private key
* @param fqdn a fully qualified domain name
* @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
* @param algorithm Key pair algorithm
* @param bits the number of bits of the generated private key
*/
public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter, String algorithm, int bits)
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.
*
* @param fqdn a fully qualified domain name
* @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 notBefore Certificate is not valid before this time
* @param notAfter Certificate is not valid after this time
*/
public SelfSignedCertificate(String fqdn, SecureRandom random, int bits, Date notBefore, Date notAfter)
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;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm);
keyGen.initialize(bits, random);
keypair = keyGen.generateKeyPair();
} 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);
}
String[] paths;
try {
// 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) {
logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t);
try {
// 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) {
logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t2);
final CertificateException certificateException = new CertificateException(
@ -239,8 +321,8 @@ public final class SelfSignedCertificate {
encodedBuf = Base64.encode(wrappedBuf, true);
try {
keyText = "-----BEGIN PRIVATE KEY-----\n" +
encodedBuf.toString(CharsetUtil.US_ASCII) +
"\n-----END PRIVATE KEY-----\n";
encodedBuf.toString(CharsetUtil.US_ASCII) +
"\n-----END PRIVATE KEY-----\n";
} finally {
encodedBuf.release();
}
@ -270,8 +352,8 @@ public final class SelfSignedCertificate {
try {
// Encode the certificate into a CRT file.
certText = "-----BEGIN CERTIFICATE-----\n" +
encodedBuf.toString(CharsetUtil.US_ASCII) +
"\n-----END CERTIFICATE-----\n";
encodedBuf.toString(CharsetUtil.US_ASCII) +
"\n-----END CERTIFICATE-----\n";
} finally {
encodedBuf.release();
}