com.wandrell.util.ksgen.BouncyCastleKeyStoreFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.wandrell.util.ksgen.BouncyCastleKeyStoreFactory.java

Source

/**
 * The MIT License (MIT)
 * <p>
 * Copyright (c) 2016 the original author or authors.
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * <p>
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package com.wandrell.util.ksgen;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Date;
import java.util.Random;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Bouncy Castle based factory for generating key stores.
 *
 * @author Bernardo Martnez Garrido
 */
public final class BouncyCastleKeyStoreFactory extends AbstractKeyStoreFactory {

    /**
     * The logger used for logging the key store creation.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(BouncyCastleKeyStoreFactory.class);

    /**
     * Random values generator.
     * <p>
     * To be used whenever a new random value is required.
     */
    private final Random random = new Random();

    /**
     * The algorithm to be used for the secret key.
     */
    private final String secretKeyAlgorithm = "DES";

    /**
     * The algorith to use for the signature.
     */
    private final String signatureAlgorithm = "SHA256WithRSAEncryption";

    /**
     * Default constructor.
     */
    public BouncyCastleKeyStoreFactory() {
        super();
    }

    /**
     * Returns a {@code SubjectKeyIdentifier} for the received {@code Key}.
     *
     * @param key
     *            the key for generating the identifier
     * @return a {@code SubjectKeyIdentifier} for the received {@code Key}
     * @throws IOException
     *             if any problem occurs while reading the key
     */
    private final SubjectKeyIdentifier createSubjectKeyIdentifier(final Key key) throws IOException {
        final ASN1Sequence seq; // Sequence for the key info
        ASN1InputStream stream = null; // Stream for reading the key

        try {
            stream = new ASN1InputStream(new ByteArrayInputStream(key.getEncoded()));
            seq = (ASN1Sequence) stream.readObject();
        } finally {
            IOUtils.closeQuietly(stream);
        }

        return new BcX509ExtensionUtils().createSubjectKeyIdentifier(new SubjectPublicKeyInfo(seq));
    }

    /**
     * Returns a {@code Certificate} with the received data.
     *
     * @param keypair
     *            key pair for the certificate
     * @param issuer
     *            issuer for the certificate
     * @return a {@code Certificate} with the received data
     * @throws IOException
     *             if there is an I/O or format problem with the certificate
     *             data
     * @throws OperatorCreationException
     *             if there was a problem creation a bouncy castle operator
     * @throws CertificateException
     *             if any of the certificates in the keystore could not be
     *             loaded
     * @throws InvalidKeyException
     *             if there was a problem with the key
     * @throws NoSuchAlgorithmException
     *             if an algorithm required to create the key store could not be
     *             found
     * @throws NoSuchProviderException
     *             if a required provider is missing
     * @throws SignatureException
     *             if any problem occurs while signing the certificate
     */
    private final Certificate getCertificate(final KeyPair keypair, final String issuer)
            throws IOException, OperatorCreationException, CertificateException, InvalidKeyException,
            NoSuchAlgorithmException, NoSuchProviderException, SignatureException {
        final X509v3CertificateBuilder builder; // Certificate builder
        final X509Certificate certificate; // Certificate

        // Generates the certificate builder
        builder = getCertificateBuilder(keypair.getPublic(), issuer);

        // Generates the signed certificate
        certificate = getSignedCertificate(builder, keypair.getPrivate());

        // Verifies the certificate
        certificate.checkValidity(getCurrentDate());
        certificate.verify(keypair.getPublic());

        LOGGER.debug("Created certificate of type {} with encoded value {}", certificate.getType(),
                Arrays.asList(certificate.getEncoded()));
        LOGGER.debug("Created certificate with public key:{}", certificate.getPublicKey());

        return certificate;
    }

    /**
     * Returns a certificate builder.
     *
     * @param publicKey
     *            public key for the certificate builder
     * @param issuer
     *            issuer for the certificate builder
     * @return a certificate builder
     * @throws IOException
     *             if any format error occurrs while creating the certificate
     */
    private final X509v3CertificateBuilder getCertificateBuilder(final PublicKey publicKey, final String issuer)
            throws IOException {
        final X500Name issuerName; // Issuer name
        final X500Name subjectName; // Subject name
        final BigInteger serial; // Serial number
        final X509v3CertificateBuilder builder; // Certificate builder
        final Date start; // Certificate start date
        final Date end; // Certificate end date
        final KeyUsage usage; // Key usage
        final ASN1EncodableVector purposes; // Certificate purposes

        issuerName = new X500Name(issuer);
        subjectName = issuerName;
        serial = BigInteger.valueOf(getRandom().nextInt());

        // Dates for the certificate
        start = getOneYearBackDate();
        end = getOneHundredYearsFutureDate();

        builder = new JcaX509v3CertificateBuilder(issuerName, serial, start, end, subjectName, publicKey);

        builder.addExtension(Extension.subjectKeyIdentifier, false, createSubjectKeyIdentifier(publicKey));
        builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));

        usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.keyEncipherment
                | KeyUsage.dataEncipherment | KeyUsage.cRLSign);
        builder.addExtension(Extension.keyUsage, false, usage);

        purposes = new ASN1EncodableVector();
        purposes.add(KeyPurposeId.id_kp_serverAuth);
        purposes.add(KeyPurposeId.id_kp_clientAuth);
        purposes.add(KeyPurposeId.anyExtendedKeyUsage);
        builder.addExtension(Extension.extendedKeyUsage, false, new DERSequence(purposes));

        return builder;

    }

    /**
     * Returns the current date.
     * 
     * @return the current date
     */
    private final Date getCurrentDate() {
        return new Date();
    }

    /**
     * Creates a key pair.
     *
     * @return the key pair
     * @throws NoSuchAlgorithmException
     *             if the required algorithm for the key pair does not exist
     */
    private final KeyPair getKeyPair() throws NoSuchAlgorithmException {
        final KeyPairGenerator keyPairGenerator; // Key pair generator
        final KeyPair keypair; // Key pair

        keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(1024, new SecureRandom());

        keypair = keyPairGenerator.generateKeyPair();

        LOGGER.debug("Created key pair with private key {} {} and public key {} {}",
                keypair.getPrivate().getAlgorithm(), Arrays.asList(keypair.getPrivate().getEncoded()),
                keypair.getPublic().getAlgorithm(), Arrays.asList(keypair.getPublic().getEncoded()));

        return keypair;
    }

    /**
     * Returns a date for this day one hundred years in the future.
     * 
     * @return a date one hundred years in the future
     */
    private final Date getOneHundredYearsFutureDate() {
        final Long msDay; // Milliseconds in a day
        final Integer yearDays; // Days in a year
        final Integer years; // Number of years

        msDay = 86400000L;
        yearDays = 365;
        years = 100;

        return new Date(System.currentTimeMillis() + msDay * yearDays * years);
    }

    /**
     * Returns a date for this day the previous year.
     * 
     * @return a date one year back
     */
    private final Date getOneYearBackDate() {
        final Long msDay; // Milliseconds in a day
        final Integer yearDays; // Days in a year

        msDay = 86400000L;
        yearDays = 365;

        return new Date(System.currentTimeMillis() - msDay * yearDays);
    }

    /**
     * Returns the password as a byte array.
     * 
     * @param password
     *            the password to transform into a byte array
     * @return the password as a byte array
     */
    private final byte[] getPasswordArray(final String password) {
        // TODO: This always returns the same value
        return new byte[] { 1, 2, 3, 4, 5 };
    }

    /**
     * Returns the random values generator.
     * 
     * @return the random values generator
     */
    private final Random getRandom() {
        return random;
    }

    /**
     * Returns the algorithm to be used for the secret key.
     * 
     * @return the algorithm to be used for the secret key
     */
    private final String getSecretKeyAlgorithm() {
        return secretKeyAlgorithm;
    }

    /**
     * Returns the algorithm to use for the signature.
     * 
     * @return
     */
    private final String getSignatureAlgorithm() {
        return signatureAlgorithm;
    }

    /**
     * Returns a signed certificate.
     *
     * @param builder
     *            builder to create the certificate
     * @param key
     *            private key for the certificate
     * @return a signed certificate
     * @throws OperatorCreationException
     *             if there was a problem creation a bouncy castle operator
     * @throws CertificateException
     *             if any of the certificates in the keystore could not be
     *             loaded
     */
    private final X509Certificate getSignedCertificate(final X509v3CertificateBuilder builder, final PrivateKey key)
            throws OperatorCreationException, CertificateException {
        final ContentSigner signer; // Content signer
        final String provider; // Provider
        final X509Certificate signed; // Signed certificate

        provider = BouncyCastleProvider.PROVIDER_NAME;
        signer = new JcaContentSignerBuilder(getSignatureAlgorithm()).setProvider(provider).build(key);

        signed = new JcaX509CertificateConverter().setProvider(provider).getCertificate(builder.build(signer));

        LOGGER.debug("Signed certificate with {} private key {}, using algorithm {}", key.getAlgorithm(),
                Arrays.asList(key.getEncoded()), key.getFormat());

        return signed;
    }

    @Override
    protected final void addCertificate(final KeyStore kstore, final String password, final String alias,
            final String issuer) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException,
            OperatorCreationException, CertificateException, IOException, KeyStoreException, SignatureException {
        final KeyPair keypair; // Key pair for the certificate
        final Certificate certificate; // Generated certificate
        final Certificate[] chain; // Certificate chain

        // Creates a key pair
        keypair = getKeyPair();

        // Creates a certificate
        certificate = getCertificate(keypair, issuer);

        // Creates the certificates chain
        chain = new Certificate[] { certificate };

        // Sets the key data into the key store
        kstore.setKeyEntry(alias, keypair.getPrivate(), password.toCharArray(), chain);

        LOGGER.debug("Added certificate with alias {} and password {} for issuer {}", alias, password, issuer);
    }

    @Override
    protected final void addSecretKey(final KeyStore kstore, final String alias, final String password)
            throws KeyStoreException {
        final SecretKeyEntry secretKeyEntry; // Secret key entry
        final PasswordProtection keyPassword; // Secret key password protection
        final SecretKey secretKey; // Secret key password
        final byte[] key; // Secret key as array

        key = getPasswordArray(password);
        secretKey = new SecretKeySpec(key, getSecretKeyAlgorithm());

        LOGGER.debug("Created secret key {} with format {}", Arrays.asList(secretKey.getEncoded()),
                secretKey.getFormat());

        secretKeyEntry = new SecretKeyEntry(secretKey);
        keyPassword = new PasswordProtection(password.toCharArray());
        kstore.setEntry(alias, secretKeyEntry, keyPassword);

        LOGGER.debug("Added secret key with alias {} and password {}", alias, password);
    }

    @Override
    protected final KeyStore getKeystore(final String password)
            throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
        return getKeystore(password, KeyStore.getDefaultType());
    }

    @Override
    protected final KeyStore getKeystore(final String password, final String type)
            throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
        final KeyStore kstore; // The returned key store
        final char[] pass; // The key store password

        kstore = KeyStore.getInstance(type);

        pass = password.toCharArray();
        kstore.load(null, pass);

        LOGGER.debug("Created {} key store with password {}", type, password);

        return kstore;
    }

}