io.vertx.config.vault.utils.Certificates.java Source code

Java tutorial

Introduction

Here is the source code for io.vertx.config.vault.utils.Certificates.java

Source

/*
 * Copyright (c) 2014 Red Hat, Inc. and others
 *
 * Red Hat 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:
 *
 * http://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.vertx.config.vault.utils;

import org.apache.commons.io.FileUtils;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.util.PrivateKeyFactory;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcContentSignerBuilder;
import org.bouncycastle.operator.bc.BcRSAContentSignerBuilder;

import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Date;

/**
 * @author <a href="http://escoffier.me">Clement Escoffier</a>
 */
public class Certificates {

    private static File SSL_DIRECTORY = new File("target/vault/config/ssl");
    private static final File CERT_PEMFILE = new File(SSL_DIRECTORY, "cert.pem");
    private static final File PRIVATE_KEY_PEMFILE = new File(SSL_DIRECTORY, "privatekey.pem");
    private static final File CLIENT_CERT_PEMFILE = new File(SSL_DIRECTORY, "client-cert.pem");
    private static final File CLIENT_PRIVATE_KEY_PEMFILE = new File(SSL_DIRECTORY, "client-privatekey.pem");
    private static final File CLIENT_KEYSTORE = new File(SSL_DIRECTORY, "keystore.jks");
    private static final File CLIENT_TRUSTSTORE = new File(SSL_DIRECTORY, "truststore.jks");

    private static X509Certificate vaultCertificate;

    /**
     * Called by the constructor method prior to configuring and launching the Vault instance.  Uses Bouncy Castle
     * (https://www.bouncycastle.org) to programmatically generate a private key and X509 certificate for use by
     * the Vault server instance in accepting SSL connections.
     */
    public static void createVaultCertAndKey() throws Exception {
        if (SSL_DIRECTORY.isDirectory() && CERT_PEMFILE.isFile()) {
            try (FileInputStream fis = new FileInputStream(CERT_PEMFILE)) {
                CertificateFactory fact = CertificateFactory.getInstance("X.509");
                vaultCertificate = (X509Certificate) fact.generateCertificate(fis);
            }
            return;
        }

        SSL_DIRECTORY.mkdirs();

        // Generate a certificate and private key for Vault, and write them to disk in PEM format.  Also store the
        // original X509Certificate object in a member variable, so it can later be used by "createClientCertAndKey()".
        final KeyPair keyPair = generateKeyPair();
        vaultCertificate = generateCert(keyPair,
                "C=AU, O=The Legion of the Bouncy Castle, OU=Vault Server Certificate, CN=localhost");
        writeCertToPem(vaultCertificate, CERT_PEMFILE);
        writePrivateKeyToPem(keyPair.getPrivate(), PRIVATE_KEY_PEMFILE);
    }

    /**
     * Constructs a Java truststore in JKS format, containing the Vault server certificate generated by
     * {@link #createVaultCertAndKey()}, so that Vault clients configured with this JKS will trust that
     * certificate.
     */
    public static void createClientCertAndKey() throws Exception {
        if (SSL_DIRECTORY.isDirectory() && CLIENT_CERT_PEMFILE.isFile()) {
            return;
        }

        // Store the Vault's server certificate as a trusted cert in the truststore
        final KeyStore trustStore = KeyStore.getInstance("jks");
        trustStore.load(null);
        trustStore.setCertificateEntry("cert", vaultCertificate);
        try (final FileOutputStream keystoreOutputStream = new FileOutputStream(CLIENT_TRUSTSTORE)) {
            trustStore.store(keystoreOutputStream, "password".toCharArray());
        }

        // Generate a client certificate, and store it in a Java keystore
        final KeyPair keyPair = generateKeyPair();
        final X509Certificate clientCertificate = generateCert(keyPair,
                "C=AU, O=The Legion of the Bouncy Castle, OU=Client Certificate, CN=localhost");
        final KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(null);
        keyStore.setKeyEntry("privatekey", keyPair.getPrivate(), "password".toCharArray(),
                new java.security.cert.Certificate[] { clientCertificate });
        keyStore.setCertificateEntry("cert", clientCertificate);
        try (final FileOutputStream keystoreOutputStream = new FileOutputStream(CLIENT_KEYSTORE)) {
            keyStore.store(keystoreOutputStream, "password".toCharArray());
        }

        // Also write the client certificate to a PEM file, so it can be registered with Vault
        writeCertToPem(clientCertificate, CLIENT_CERT_PEMFILE);
        writePrivateKeyToPem(keyPair.getPrivate(), CLIENT_PRIVATE_KEY_PEMFILE);
    }

    /**
     * See https://www.cryptoworkshop.com/guide/, chapter 3
     *
     * @return A 4096-bit RSA keypair
     * @throws NoSuchAlgorithmException
     */
    private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
        final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", new BouncyCastleProvider());
        keyPairGenerator.initialize(4096);
        return keyPairGenerator.genKeyPair();
    }

    /**
     * See http://www.programcreek.com/java-api-examples/index.php?api=org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder
     *
     * @param keyPair The RSA keypair with which to generate the certificate
     * @param issuer  The issuer (and subject) to use for the certificate
     * @return An X509 certificate
     * @throws IOException
     * @throws OperatorCreationException
     * @throws CertificateException
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     * @throws SignatureException
     */
    private static X509Certificate generateCert(final KeyPair keyPair, final String issuer)
            throws IOException, OperatorCreationException, CertificateException, NoSuchProviderException,
            NoSuchAlgorithmException, InvalidKeyException, SignatureException {
        final String subject = issuer;
        final X509v3CertificateBuilder certificateBuilder = new X509v3CertificateBuilder(new X500Name(issuer),
                BigInteger.ONE, new Date(System.currentTimeMillis() - 1000L * 60 * 60 * 24 * 30),
                new Date(System.currentTimeMillis() + (1000L * 60 * 60 * 24 * 30)), new X500Name(subject),
                SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()));

        final GeneralNames subjectAltNames = new GeneralNames(new GeneralName(GeneralName.iPAddress, "127.0.0.1"));
        certificateBuilder.addExtension(org.bouncycastle.asn1.x509.Extension.subjectAlternativeName, false,
                subjectAltNames);

        final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
                .find("SHA1WithRSAEncryption");
        final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
        final BcContentSignerBuilder signerBuilder = new BcRSAContentSignerBuilder(sigAlgId, digAlgId);
        final AsymmetricKeyParameter keyp = PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
        final ContentSigner signer = signerBuilder.build(keyp);
        final X509CertificateHolder x509CertificateHolder = certificateBuilder.build(signer);

        final X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(x509CertificateHolder);
        certificate.checkValidity(new Date());
        certificate.verify(keyPair.getPublic());
        return certificate;
    }

    /**
     * See https://stackoverflow.com/questions/3313020/write-x509-certificate-into-pem-formatted-string-in-java
     *
     * @param certificate An X509 certificate
     * @param file        the file
     * @throws CertificateEncodingException
     * @throws FileNotFoundException
     */
    private static void writeCertToPem(final X509Certificate certificate, final File file)
            throws CertificateEncodingException, IOException {
        final Base64.Encoder encoder = Base64.getEncoder();

        final String certHeader = "-----BEGIN CERTIFICATE-----\n";
        final String certFooter = "\n-----END CERTIFICATE-----";
        final byte[] certBytes = certificate.getEncoded();
        final String certContents = new String(encoder.encode(certBytes));
        final String certPem = certHeader + certContents + certFooter;
        FileUtils.write(file, certPem);
    }

    /**
     * See https://stackoverflow.com/questions/3313020/write-x509-certificate-into-pem-formatted-string-in-java
     *
     * @param key  An RSA private key
     * @param file a file to which the private key will be written in PEM format
     * @throws FileNotFoundException
     */
    private static void writePrivateKeyToPem(final PrivateKey key, File file) throws IOException {
        final Base64.Encoder encoder = Base64.getEncoder();

        final String keyHeader = "-----BEGIN PRIVATE KEY-----\n";
        final String keyFooter = "\n-----END PRIVATE KEY-----";
        final byte[] keyBytes = key.getEncoded();
        final String keyContents = new String(encoder.encode(keyBytes));
        final String keyPem = keyHeader + keyContents + keyFooter;
        FileUtils.write(file, keyPem);
    }
}