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

craterdog.security.CertificateManager Maven / Gradle / Ivy

/************************************************************************
 * Copyright (c) Crater Dog Technologies(TM).  All Rights Reserved.     *
 ************************************************************************
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.        *
 *                                                                      *
 * This code is free software; you can redistribute it and/or modify it *
 * under the terms of The MIT License (MIT), as published by the Open   *
 * Source Initiative. (See http://opensource.org/licenses/MIT)          *
 ************************************************************************/
package craterdog.security;

import craterdog.utils.Base64Utils;
import craterdog.utils.RandomUtils;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;


/**
 * This class provides methods that hide the complexities of the Java security API in dealing
 * with certificate management.
 *
 * @author Derk Norton
 */
public abstract class CertificateManager {

    static final XLogger logger = XLoggerFactory.getXLogger(CertificateManager.class);

    static private final String KEY_STORE_FORMAT = "PKCS12";

    /**
     * This method returns the asymmetric key type string.
     *
     * @return The asymmetric key type string.
     */
    public abstract String getAsymmetricKeyType();


    /**
     * This method returns the asymmetric key size.
     *
     * @return The asymmetric key size.
     */
    public abstract int getAsymmetricalKeySize();


    /**
     * This method returns the hash algorithm.
     *
     * @return The hash algorithm.
     */
    public abstract String getHashAlgorithm();


    /**
     * This method returns the asymmetric signature algorithm.
     *
     * @return The asymmetric signature algorithm.
     */
    public abstract String getAsymmetricSignatureAlgorithm();


    /**
     * This method generates a new public/private key pair.
     *
     * @return The new key pair.
     */
    public abstract KeyPair generateKeyPair();


    /**
     * This method saves a PKCS12 format key store out to an output stream.
     *
     * @param output The output stream to be written to.
     * @param keyStore The PKCS12 format key store.
     * @param password The password that should be used to encrypt the file.
     * @throws java.io.IOException Unable to save the key store to the specified output stream.
     */
    public final void saveKeyStore(OutputStream output, KeyStore keyStore, char[] password) throws IOException {
        logger.entry();
        try {
            keyStore.store(output, password);
        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to save a keystore.", e);
            throw logger.throwing(exception);
        }
        logger.exit();
    }


    /**
     * This method retrieves a PKCS12 format key store from an input stream.
     *
     * @param input The input stream from which to read the key store.
     * @param password The password that was used to encrypt the file.
     * @return The PKCS12 format key store.
     * @throws java.io.IOException Unable to retrieve the key store from the specified input stream.
     */
    public final KeyStore retrieveKeyStore(InputStream input, char[] password) throws IOException {
        logger.entry();
        try {
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_FORMAT);
            keyStore.load(input, password);
            logger.exit();
            return keyStore;
        } catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to retrieve a keystore.", e);
            throw logger.throwing(exception);
        }
    }


    /**
     * This method retrieves a public certificate from a key store.
     *
     * @param keyStore The key store containing the certificate.
     * @param certificateName The name (alias) of the certificate.
     * @return The X509 format public certificate.
     */
    public final X509Certificate retrieveCertificate(KeyStore keyStore, String certificateName) {
        try {
            logger.entry();
            X509Certificate certificate = (X509Certificate) keyStore.getCertificate(certificateName);
            logger.exit();
            return certificate;
        } catch (KeyStoreException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to retrieve a certificate.", e);
            throw logger.throwing(exception);
        }
    }


    /**
     * This method retrieves a private key from a key store.
     *
     * @param keyStore The key store containing the private key.
     * @param keyName The name (alias) of the private key.
     * @param password The password used to encrypt the private key.
     * @return The decrypted private key.
     */
    public final PrivateKey retrievePrivateKey(KeyStore keyStore, String keyName, char[] password) {
        try {
            logger.entry();
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyName, password);
            logger.exit();
            return privateKey;
        } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to retrieve a private key.", e);
            throw logger.throwing(exception);
        }
    }


    public final String[] splitPrivateKey(PrivateKey key) {
        String[] result = new String[2];
        byte[] keyBytes = key.getEncoded();
        int numberOfBytes = keyBytes.length;
        byte[] randomBytes = RandomUtils.generateRandomBytes(numberOfBytes);
        byte[] xorBytes = xorByteArrays(keyBytes, randomBytes);
        result[0] = Base64Utils.encode(randomBytes);
        result[1] = Base64Utils.encode(xorBytes);
        return result;
    }


    public final PrivateKey mergePrivateKey(String[] encodedByteArrays) {
        try {
            byte[] randomBytes = Base64Utils.decode(encodedByteArrays[0]);
            byte[] xorBytes = Base64Utils.decode(encodedByteArrays[1]);
            byte[] keyBytes = xorByteArrays(randomBytes, xorBytes);
            KeyFactory factory = KeyFactory.getInstance(getAsymmetricKeyType());
            PrivateKey privateKey = factory.generatePrivate(new PKCS8EncodedKeySpec(keyBytes));
            return privateKey;
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            RuntimeException exception = new RuntimeException("Attempted to merge invalid key shards");
            throw logger.throwing(exception);
        }
    }


    private byte[] xorByteArrays(byte[] firstArray, byte[] secondArray) {
        int numberOfBytes = firstArray.length;
        byte[] xorBytes = new byte[numberOfBytes];
        for (int i = 0; i < numberOfBytes; i++) {
            xorBytes[i] =  (byte) (0xFF & (firstArray[i] ^ secondArray[i]));
        }
        return xorBytes;
    }


    /**
     * This method creates a new self-signed X509 certificate for a new certificate authority (CA).
     *
     * @param privateKey The private key for the new certificate.
     * @param publicKey The public key that the new certificate is encoding.
     * @param subject The distinguished name for the certificate (e.g. CN=Derk Norton, O=Crater Dog Technologies).
     * @param serialNumber The unique serial number for the new certificate.
     * @param lifetime The number of milliseconds before the certificate should expire.
     * @return The new signed certificate.
     */
    public abstract X509Certificate createCertificateAuthority(PrivateKey privateKey, PublicKey publicKey,
            String subject, BigInteger serialNumber, long lifetime);


    /**
     * This method creates a new X509 certificate that is signed by a certificate authority (CA).
     *
     * @param caPrivateKey The private key for the certificate authority.
     * @param caCertificate The public certificate for the certificate authority.
     * @param publicKey The public key that the new certificate is encoding.
     * @param subject The distinguished name for the certificate (e.g. CN=Derk Norton, O=Crater Dog Technologies).
     * @param serialNumber The unique serial number for the new certificate.
     * @param lifetime The number of milliseconds before the certificate should expire.
     * @return The new signed certificate.
     */
    public abstract X509Certificate createCertificate(PrivateKey caPrivateKey, X509Certificate caCertificate,
            PublicKey publicKey, String subject, BigInteger serialNumber, long lifetime);


    /**
     * This method creates a new PKCS12 format key store containing a named private key and public certificate.
     *
     * @param keyName The name of the private key and public certificate.
     * @param password The password used to encrypt the private key.
     * @param privateKey The private key.
     * @param certificate The X509 format public certificate.
     * @return The new PKCS12 format key store.
     */
    public final KeyStore createPkcs12KeyStore(String keyName, char[] password, PrivateKey privateKey, X509Certificate certificate) {
        logger.entry();
        List certificates = new ArrayList<>();
        certificates.add(certificate);
        KeyStore keyStore = createPkcs12KeyStore(keyName, password, privateKey, certificates);
        logger.exit();
        return keyStore;
    }


    /**
     * This method creates a new PKCS12 format key store containing a named private key and public certificate chain.
     *
     * @param keyName The name of the private key and public certificate.
     * @param password The password used to encrypt the private key.
     * @param privateKey The private key.
     * @param certificates The chain of X509 format public certificates.
     * @return The new PKCS12 format key store.
     */
    public final KeyStore createPkcs12KeyStore(String keyName, char[] password, PrivateKey privateKey, List certificates) {
        try {
            logger.entry();
            X509Certificate[] chain = new X509Certificate[certificates.size()];
            chain = certificates.toArray(chain);
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_FORMAT);
            keyStore.load(null, null);
            keyStore.setKeyEntry(keyName, privateKey, password, chain);
            logger.exit();
            return keyStore;
        } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to create a new keystore.", e);
            throw logger.throwing(exception);
        }
    }


    /**
     * This method encodes a public key into a PEM string.
     *
     * @param key The public key.
     * @return The corresponding PEM string.
     */
    public abstract String encodePublicKey(PublicKey key);


    /**
     * This method decodes public key from a PEM string.
     *
     * @param pem The PEM string for the public key.
     * @return The corresponding key.
     */
    public abstract PublicKey decodePublicKey(String pem);


    /**
     * This method encodes a private key into a PEM string.
     *
     * @param key The private key.
     * @param password The password to be used to encrypt the private key.
     * @return The corresponding PEM string.
     */
    public abstract String encodePrivateKey(PrivateKey key, char[] password);


    /**
     * This method decodes private key from a PEM string.
     *
     * @param pem The PEM string for the private key.
     * @param password The password to be used to decrypt the private key.
     * @return The corresponding key.
     */
    public abstract PrivateKey decodePrivateKey(String pem, char[] password);


    /**
     * This method encodes an X509 format certificate into a PEM string.
     *
     * @param certificate The X509 format certificate.
     * @return The corresponding PEM string.
     */
    public abstract String encodeCertificate(X509Certificate certificate);


    /**
     * This method decodes an X509 format certificate from a PEM string.
     *
     * @param pem The PEM string for the certificate.
     * @return The corresponding X509 format certificate.
     */
    public abstract X509Certificate decodeCertificate(String pem);


    /**
     * This method encodes a PKCS12 format key store into a base 64 string format.
     *
     * @param keyStore The PKCS12 format key store to be encoded.
     * @param password The password to be used to encrypt the byte stream.
     * @return The base 64 encoded string.
     */
    public final String encodeKeyStore(KeyStore keyStore, char[] password) {
        logger.entry();
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            keyStore.store(out, password);
            out.flush();
            byte[] bytes = out.toByteArray();
            String encodedKeyStore = Base64Utils.encode(bytes);
            logger.exit();
            return encodedKeyStore;
        } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to encode a keystore.", e);
            throw logger.throwing(exception);
        }
    }


    /**
     * This method decodes a PKCS12 format key store from its encrypted byte stream.
     *
     * @param base64String The base 64 encoded, password encrypted PKCS12 byte stream.
     * @param password The password that was used to encrypt the byte stream.
     * @return The PKCS12 format key store.
     */
    public final KeyStore decodeKeyStore(String base64String, char[] password) {
        logger.entry();
        byte[] bytes = Base64Utils.decode(base64String);
        try (ByteArrayInputStream in = new ByteArrayInputStream(bytes)) {
            KeyStore keyStore = KeyStore.getInstance(KEY_STORE_FORMAT);
            keyStore.load(in, password);
            logger.exit();
            return keyStore;
        } catch (IOException | KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
            RuntimeException exception = new RuntimeException("An unexpected exception occurred while attempting to decode a keystore.", e);
            throw logger.throwing(exception);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy