All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.ssl.util.SelfSignedCertificate Maven / Gradle / Ivy

There is a newer version: 5.0.0.Alpha2
Show newest version
/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package io.netty.handler.ssl.util;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.base64.Base64;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;

/**
 * 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. *

* 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()}. *

* At first, this method tries to use OpenJDK's X.509 implementation (the {@code sun.security.x509} package). * If it fails, it tries to use Bouncy Castle as a fallback. *

*/ public final class SelfSignedCertificate { private static final InternalLogger logger = InternalLoggerFactory.getInstance(SelfSignedCertificate.class); /** Current time minus 1 year, just in case software clock goes back due to time synchronization */ private static final Date DEFAULT_NOT_BEFORE = new Date(SystemPropertyUtil.getLong( "io.netty.selfSignedCertificate.defaultNotBefore", System.currentTimeMillis() - 86400000L * 365)); /** The maximum possible value in X.509 specification: 9999-12-31 23:59:59 */ private static final Date DEFAULT_NOT_AFTER = new Date(SystemPropertyUtil.getLong( "io.netty.selfSignedCertificate.defaultNotAfter", 253402300799000L)); /** * 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. */ private static final int DEFAULT_KEY_LENGTH_BITS = SystemPropertyUtil.getInt("io.netty.handler.ssl.util.selfSignedKeyStrength", 2048); private final File certificate; private final File privateKey; private final X509Certificate cert; private final PrivateKey key; /** * Creates a new instance. *

Algorithm: RSA

*/ public SelfSignedCertificate() throws CertificateException { this(new Builder()); } /** * Creates a new instance. *

Algorithm: RSA

* * @param notBefore Certificate is not valid before this time * @param notAfter Certificate is not valid after this time */ public SelfSignedCertificate(Date notBefore, Date notAfter) throws CertificateException { this(new Builder().notBefore(notBefore).notAfter(notAfter)); } /** * 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(new Builder().notBefore(notBefore).notAfter(notAfter).algorithm(algorithm).bits(bits)); } /** * Creates a new instance. *

Algorithm: RSA

* * @param fqdn a fully qualified domain name */ public SelfSignedCertificate(String fqdn) throws CertificateException { this(new Builder().fqdn(fqdn)); } /** * Creates a new instance. * * @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(new Builder().fqdn(fqdn).algorithm(algorithm).bits(bits)); } /** * Creates a new instance. *

Algorithm: RSA

* * @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 */ public SelfSignedCertificate(String fqdn, Date notBefore, Date notAfter) throws CertificateException { this(new Builder().fqdn(fqdn).notBefore(notBefore).notAfter(notAfter)); } /** * Creates a new instance. * * @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 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 { this(new Builder().fqdn(fqdn).notBefore(notBefore).notAfter(notAfter).algorithm(algorithm).bits(bits)); } /** * Creates a new instance. *

Algorithm: RSA

* * @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(new Builder().fqdn(fqdn).random(random).bits(bits)); } /** * 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(new Builder().fqdn(fqdn).random(random).algorithm(algorithm).bits(bits)); } /** * Creates a new instance. *

Algorithm: RSA

* * @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 { this(new Builder().fqdn(fqdn).notBefore(notBefore).notAfter(notAfter).random(random).bits(bits)); } /** * 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 { this(new Builder().fqdn(fqdn).random(random).algorithm(algorithm).bits(bits) .notBefore(notBefore).notAfter(notAfter)); } private SelfSignedCertificate(Builder builder) throws CertificateException { if (!builder.generateBc()) { if (!builder.generateKeytool()) { if (!builder.generateSunMiscSecurity()) { // last exception is always from generateSunMiscSecurity, so we can cast here throw (CertificateException) builder.failure; } } } certificate = new File(builder.paths[0]); privateKey = new File(builder.paths[1]); key = builder.privateKey; try (FileInputStream certificateInput = new FileInputStream(certificate)) { cert = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(certificateInput); } catch (Exception e) { throw new CertificateEncodingException(e); } } public static Builder builder() { return new Builder(); } /** * Returns the generated X.509 certificate file in PEM format. */ public File certificate() { return certificate; } /** * Returns the generated EC/RSA private key file in PEM format. */ public File privateKey() { return privateKey; } /** * Returns the generated X.509 certificate. */ public X509Certificate cert() { return cert; } /** * Returns the generated EC/RSA private key. */ public PrivateKey key() { return key; } /** * Deletes the generated X.509 certificate file and EC/RSA private key file. */ public void delete() { safeDelete(certificate); safeDelete(privateKey); } static String[] newSelfSignedCertificate( String fqdn, PrivateKey key, X509Certificate cert) throws IOException, CertificateEncodingException { // Encode the private key into a file. ByteBuf wrappedBuf = Unpooled.wrappedBuffer(key.getEncoded()); ByteBuf encodedBuf; final String keyText; try { encodedBuf = Base64.encode(wrappedBuf, true); try { keyText = "-----BEGIN PRIVATE KEY-----\n" + encodedBuf.toString(CharsetUtil.US_ASCII) + "\n-----END PRIVATE KEY-----\n"; } finally { encodedBuf.release(); } } finally { wrappedBuf.release(); } // Change all asterisk to 'x' for file name safety. fqdn = fqdn.replaceAll("[^\\w.-]", "x"); File keyFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".key", null); keyFile.deleteOnExit(); OutputStream keyOut = new FileOutputStream(keyFile); try { keyOut.write(keyText.getBytes(CharsetUtil.US_ASCII)); keyOut.close(); keyOut = null; } finally { if (keyOut != null) { safeClose(keyFile, keyOut); safeDelete(keyFile); } } wrappedBuf = Unpooled.wrappedBuffer(cert.getEncoded()); final String certText; try { encodedBuf = Base64.encode(wrappedBuf, true); try { // Encode the certificate into a CRT file. certText = "-----BEGIN CERTIFICATE-----\n" + encodedBuf.toString(CharsetUtil.US_ASCII) + "\n-----END CERTIFICATE-----\n"; } finally { encodedBuf.release(); } } finally { wrappedBuf.release(); } File certFile = PlatformDependent.createTempFile("keyutil_" + fqdn + '_', ".crt", null); certFile.deleteOnExit(); OutputStream certOut = new FileOutputStream(certFile); try { certOut.write(certText.getBytes(CharsetUtil.US_ASCII)); certOut.close(); certOut = null; } finally { if (certOut != null) { safeClose(certFile, certOut); safeDelete(certFile); safeDelete(keyFile); } } return new String[] { certFile.getPath(), keyFile.getPath() }; } private static void safeDelete(File certFile) { if (!certFile.delete()) { if (logger.isWarnEnabled()) { logger.warn("Failed to delete a file: " + certFile); } } } private static void safeClose(File keyFile, OutputStream keyOut) { try { keyOut.close(); } catch (IOException e) { if (logger.isWarnEnabled()) { logger.warn("Failed to close a file: " + keyFile, e); } } } private static boolean isBouncyCastleAvailable() { try { // this class is in bcpkix, both fips and non-fips Class.forName("org.bouncycastle.cert.X509v3CertificateBuilder"); return true; } catch (ClassNotFoundException e) { return false; } } public static final class Builder { // user fields String fqdn = "localhost"; SecureRandom random; int bits = DEFAULT_KEY_LENGTH_BITS; Date notBefore = DEFAULT_NOT_BEFORE; Date notAfter = DEFAULT_NOT_AFTER; String algorithm = "RSA"; // fields that are populated on demand Throwable failure; KeyPair keypair; PrivateKey privateKey; String[] paths; private Builder() { } /** * Set the fully-qualified domain name of the certificate that should be generated. * * @param fqdn The FQDN * @return This builder */ public Builder fqdn(String fqdn) { this.fqdn = ObjectUtil.checkNotNullWithIAE(fqdn, "fqdn"); return this; } /** * Set the RNG to use for key generation. This setting is not supported by the keytool-based generator. * * @param random The CSPRNG * @return This builder */ public Builder random(SecureRandom random) { this.random = random; return this; } /** * Set the key size. * * @param bits The key size * @return This builder */ public Builder bits(int bits) { this.bits = bits; return this; } /** * Set the start of the certificate validity period. * * @param notBefore The start date * @return This builder */ public Builder notBefore(Date notBefore) { this.notBefore = ObjectUtil.checkNotNullWithIAE(notBefore, "notBefore"); return this; } /** * Set the end of the certificate validity period. * * @param notAfter The start date * @return This builder */ public Builder notAfter(Date notAfter) { this.notAfter = ObjectUtil.checkNotNullWithIAE(notAfter, "notAfter"); return this; } /** * Set the key algorithm. Only RSA and EC are supported. * * @param algorithm The key algorithm * @return This builder */ public Builder algorithm(String algorithm) { if ("EC".equalsIgnoreCase(algorithm)) { this.algorithm = "EC"; } else if ("RSA".equalsIgnoreCase(algorithm)) { this.algorithm = "RSA"; } else { throw new IllegalArgumentException("Algorithm not valid: " + algorithm); } return this; } private SecureRandom randomOrDefault() { // Bypass entropy collection by using insecure random generator. // We just want to generate it without any delay because it's for testing purposes only. return random == null ? ThreadLocalInsecureRandom.current() : random; } private void generateKeyPairLocally() { if (keypair != null) { return; } try { KeyPairGenerator keyGen = KeyPairGenerator.getInstance(algorithm); keyGen.initialize(bits, randomOrDefault()); keypair = keyGen.generateKeyPair(); } catch (NoSuchAlgorithmException e) { // Should not reach here because every Java implementation must have RSA and EC key pair generator. throw new IllegalStateException(e); } privateKey = keypair.getPrivate(); } private void addFailure(Throwable t) { if (failure != null) { t.addSuppressed(failure); } failure = t; } boolean generateBc() { if (!isBouncyCastleAvailable()) { // no need to even try. We can avoid generating the key pair with this check. logger.debug("Failed to generate a self-signed X.509 certificate because " + "BouncyCastle PKIX is not available in classpath"); return false; } generateKeyPairLocally(); try { // Try Bouncy Castle first as otherwise we will see an IllegalAccessError on more recent JDKs. paths = BouncyCastleSelfSignedCertGenerator.generate( fqdn, keypair, randomOrDefault(), notBefore, notAfter, algorithm); return true; } catch (Throwable t) { logger.debug("Failed to generate a self-signed X.509 certificate using Bouncy Castle:", t); addFailure(t); return false; } } boolean generateKeytool() { if (!KeytoolSelfSignedCertGenerator.isAvailable()) { logger.debug("Not attempting to generate certificate with keytool because keytool is missing"); return false; } if (random != null) { logger.debug("Not attempting to generate certificate with keytool because of explicitly set " + "SecureRandom"); return false; } try { KeytoolSelfSignedCertGenerator.generate(this); return true; } catch (Throwable t) { logger.debug("Failed to generate a self-signed X.509 certificate using keytool:", t); addFailure(t); return false; } } boolean generateSunMiscSecurity() { generateKeyPairLocally(); try { // Try the OpenJDK's proprietary implementation. paths = OpenJdkSelfSignedCertGenerator.generate( fqdn, keypair, randomOrDefault(), notBefore, notAfter, algorithm); return true; } catch (Throwable t2) { logger.debug("Failed to generate a self-signed X.509 certificate using sun.security.x509:", t2); final CertificateException certificateException = new CertificateException( "No provider succeeded to generate a self-signed certificate. " + "See debug log for the root cause.", t2); addFailure(certificateException); return false; } } /** * Build the certificate. This builder must not be used again after this method is called. * * @return The certificate * @throws CertificateException If generation fails */ public SelfSignedCertificate build() throws CertificateException { return new SelfSignedCertificate(this); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy