KeyStoreManager.java

/*
 * Copyright (c) 2021 Payara Foundation and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.grizzly2.httpserver.test.tools;

import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Random;

import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.x500.X500Name;
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.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

/**
 * Creates a new keystore in memory.
 * This keystore contains just a private key and a self-signed certificate valid for two days.
 *
 * @author David Matejcek
 */
public class KeyStoreManager {

    private static final String KEYSTORE_PASSWORD = "";
    private final KeyStore keyStore;
    private final byte[] keyStoreBytes;

    /**
     * @param hostname - hostname, used for CN value of the self-signed certificate
     */
    public KeyStoreManager(final String hostname) {
        try {
            this.keyStore = KeyStore.getInstance("PKCS12");
            this.keyStore.load(null);
            this.keyStoreBytes = generatePrivateKeyAndCertificate(hostname, this.keyStore);
        } catch (Exception e) {
            throw new IllegalStateException("Could not initialize the keystore.", e);
        }
    }


    /**
     * @return {@link KeyStore}
     */
    public KeyStore getKeyStore() {
        return this.keyStore;
    }


    /**
     * @return the key store serialized to a byte array
     */
    public byte[] getKeyStoreBytes() {
        return this.keyStoreBytes;
    }


    /**
     * @return the key store password
     */
    public String getKeyStorePassword() {
        return KEYSTORE_PASSWORD;
    }


    private static byte[] generatePrivateKeyAndCertificate(final String hostname, final KeyStore keyStore) {
        try {
            final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(2048, SecureRandom.getInstance("SHA1PRNG"));
            final KeyPair keyPair = generator.generateKeyPair();
            final PrivateKey privateKey = keyPair.getPrivate();
            final PublicKey publicKey = keyPair.getPublic();

            final BigInteger serial = new BigInteger(256, new Random(System.currentTimeMillis()));
            final Instant validFrom = Instant.now().minusSeconds(60L);
            final Instant validTo = validFrom.plus(2, ChronoUnit.DAYS);

            final ASN1Sequence pubSeq = ASN1Sequence.getInstance(publicKey.getEncoded());
            final SubjectPublicKeyInfo info = SubjectPublicKeyInfo.getInstance(pubSeq.getEncoded());
            final X500Name name = new X500Name(
                "CN=" + hostname + ", OU=Jersey Container, O=Eclipse Foundation, L=Brussels, ST=Belgium, C=BE");
            final X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
                name, serial, Date.from(validFrom), Date.from(validTo), name, info);
            final JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder("SHA512withRSA");
            final ContentSigner signer = signerBuilder.build(privateKey);

            final X509CertificateHolder cHolder = builder.build(signer);
            final X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(cHolder);

            keyStore.setKeyEntry(hostname, privateKey, KEYSTORE_PASSWORD.toCharArray(),
                new Certificate[] {certificate});
            return toBytes(keyStore);
        } catch (final Exception e) {
            throw new IllegalStateException("Could not initialize the keystore", e);
        }
    }


    private static byte[] toBytes(final KeyStore keyStore) throws Exception {
        try (ByteArrayOutputStream os = new ByteArrayOutputStream(1024))  {
            keyStore.store(os, new char[0]);
            return os.toByteArray();
        }
    }
}