org.graylog2.shared.security.tls.SelfSignedCertificate Maven / Gradle / Ivy
/**
* This file is part of Graylog.
*
* Graylog is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Graylog. If not, see .
*/
package org.graylog2.shared.security.tls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateAlgorithmId;
import sun.security.x509.CertificateIssuerName;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.CertificateSubjectName;
import sun.security.x509.CertificateValidity;
import sun.security.x509.CertificateVersion;
import sun.security.x509.CertificateX509Key;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.Objects.requireNonNull;
/**
* Generates a temporary self-signed certificate for testing purposes.
*
* NOTE:
* Never use the certificate and private key generated by this class in production.
* It is purely for testing purposes, and thus it is very insecure.
* It even uses an insecure pseudo-random generator for faster generation internally.
*
*/
public final class SelfSignedCertificate {
private static final Logger LOG = LoggerFactory.getLogger(SelfSignedCertificate.class);
/**
* Current time minus 1 year, just in case software clock goes back due to time synchronization
*/
static final Date NOT_BEFORE = new Date(System.currentTimeMillis() - 86400000L * 365);
/**
* The maximum possible value in X.509 specification: 9999-12-31 23:59:59
*/
static final Date NOT_AFTER = new Date(253402300799000L);
private final Certificate certificate;
private final PrivateKey privateKey;
private final KeyStore keyStore;
private SelfSignedCertificate(PrivateKey privateKey, Certificate certificate, KeyStore keyStore) throws CertificateException {
this.privateKey = requireNonNull(privateKey);
this.certificate = requireNonNull(certificate);
this.keyStore = requireNonNull(keyStore);
}
/**
* Returns the generated X.509 certificate.
*/
public Certificate certificate() {
return certificate;
}
/**
* Returns the generated RSA private key.
*/
public PrivateKey privateKey() {
return privateKey;
}
/**
* Returns the key store with the generated RSA private key and the X.509 certificate.
*/
public KeyStore keyStore() {
return keyStore;
}
/**
* Creates a new instance with FQDN "example.com".
*/
public static SelfSignedCertificate create() throws GeneralSecurityException, IOException {
return create("example.com", "password");
}
/**
* Creates a new instance with a key length of 1024 bits.
*
* Bypass entropy collection by using insecure random generator.
* We just want to generate it without any delay because it's for testing purposes only.
*
* @param fqdn a fully qualified domain name
* @param password the password for the generated key store
*/
public static SelfSignedCertificate create(String fqdn, String password) throws GeneralSecurityException, IOException {
return create(fqdn, ThreadLocalInsecureRandom.current(), 1024, password);
}
/**
* 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 password the password for the generated key store
*/
public static SelfSignedCertificate create(String fqdn, SecureRandom random, int bits, String password) throws GeneralSecurityException, IOException {
if (isNullOrEmpty(fqdn)) {
throw new IllegalArgumentException("FQDN must not be empty");
}
if (isNullOrEmpty(password)) {
throw new IllegalArgumentException("Key store password must not be empty");
}
final KeyPair keypair = generateKeyPair(random, bits);
final PrivateKey privateKey = keypair.getPrivate();
final X509Certificate certificate;
try {
certificate = generateCertificate(fqdn, keypair, random);
} catch (Throwable t) {
LOG.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t);
throw new CertificateException("No provider succeeded to generate a self-signed certificate. See debug log for the root cause.");
}
final KeyStore keyStore = generateKeyStore(fqdn, privateKey, certificate, password);
return new SelfSignedCertificate(privateKey, certificate, keyStore);
}
private static KeyPair generateKeyPair(SecureRandom random, int bits) {
final KeyPair keypair;
try {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(bits, random);
keypair = keyGen.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
// Should not reach here because every Java implementation must have RSA key pair generator.
throw new Error(e);
}
return keypair;
}
private static X509Certificate generateCertificate(String fqdn, KeyPair keypair, SecureRandom random) throws Exception {
final PrivateKey key = keypair.getPrivate();
// Prepare the information required for generating an X.509 certificate.
final X509CertInfo info = new X509CertInfo();
final X500Name owner = new X500Name("CN=" + fqdn);
info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3));
info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new BigInteger(64, random)));
try {
info.set(X509CertInfo.SUBJECT, new CertificateSubjectName(owner));
} catch (CertificateException ignore) {
info.set(X509CertInfo.SUBJECT, owner);
}
try {
info.set(X509CertInfo.ISSUER, new CertificateIssuerName(owner));
} catch (CertificateException ignore) {
info.set(X509CertInfo.ISSUER, owner);
}
info.set(X509CertInfo.VALIDITY, new CertificateValidity(NOT_BEFORE, NOT_AFTER));
info.set(X509CertInfo.KEY, new CertificateX509Key(keypair.getPublic()));
info.set(X509CertInfo.ALGORITHM_ID,
new CertificateAlgorithmId(new AlgorithmId(AlgorithmId.sha1WithRSAEncryption_oid)));
// Sign the cert to identify the algorithm that's used.
X509CertImpl cert = new X509CertImpl(info);
cert.sign(key, "SHA1withRSA");
// Update the algorithm and sign again.
info.set(CertificateAlgorithmId.NAME + '.' + CertificateAlgorithmId.ALGORITHM, cert.get(X509CertImpl.SIG_ALG));
cert = new X509CertImpl(info);
cert.sign(key, "SHA1withRSA");
cert.verify(keypair.getPublic());
return cert;
}
private static KeyStore generateKeyStore(String fqdn,
PrivateKey privateKey,
Certificate certificate,
String password) throws GeneralSecurityException, IOException {
final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
// Initialize key store
keyStore.load(null, password.toCharArray());
keyStore.setKeyEntry(fqdn, privateKey, password.toCharArray(), new Certificate[]{certificate});
return keyStore;
}
}