# SSLCertFactory.java

    /*
    ** Module   : SSLCertFactory.java
    ** Abstract : defines APIs to generate SSL certificates and their private keys.
    **
    ** Copyright (c) 2014-2017, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 CA  20140207 First version.
    ** 002 CA  20160106 Enforce a RSA key length of at least 2048 bits.
    ** 003 CA  20170630 Added setRootCA().
    ** 004 SBI 20171030 Added loadRootCA(certPemFile, privateKeyPemFile, certChainPemFile) to load
    *                   external public and private key pair and its trusted certificate chain.
    ** 005 SBI 20171111 Added getFullChainFromRoot and setCertificateChain.
    ** 006 SBI 20171112 Added CertificateSuite, loadCertificateSuite(..).
    **     SBI 20171116 Added getFullChain() to CertificateSuite.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.security;

    import java.math.*;
    import java.security.*;
    import java.security.cert.*;
    import java.security.cert.Certificate;
    import java.util.*;

    /**
     * Define APIs to generate SSL certificates and their private keys.
     * <p>
     * Before starting to generate certificates, a root CA needs to be generated.  This will be used
     * for signing all the issued certificates.
     */
    public abstract class SSLCertFactory
    {
       /** The minimum length (in bits) of a RSA key. */
       public static final int MIN_RSA_KEY_STRENGTH = 2048;

       /** The public key exponent. */
       protected BigInteger exponent;

       /** The private key size: {@link #MIN_RSA_KEY_STRENGTH} bits or better.*/
       protected int keyStrength;

       /**
        * Decrypt a private key which was previously encrypted with the given password.
        *
        * @param    encrypted
        *           The bytes representing the encrypted private key.
        * @param    password
        *           The encryption password.
        *
        * @return   The decrypted {@link PrivateKey private key}.
        *
        * @throws   SSLCertGenException
        *           If the private key could not be decrypted.
        */
       public abstract PrivateKey decryptPrivateKey(byte[] encrypted, String password)
       throws SSLCertGenException;

       /**
        * Encrypt the given key using the provided password.
        *
        * @param    key
        *           The key to be encrypted.
        * @param    password
        *           The encryption password.
        *
        * @return   A byte array with the encrypted key.
        *
        * @throws   SSLCertGenException
        *           If the key could not be encrypted.
        */
       public abstract byte[] encryptPrivateKey(Key key, String password)
       throws SSLCertGenException;

       /**
        * Generate a certificate and sign it with the already generated root CA.
        * <p>
        * The encrypted private key will be saved in the specified {@code certKeyStore}; the encrypt
        * password will be returned by this API.
        * <p>
        * The public certificate will be saved in the specified {@code certStore}.
        *
        * @param    alias
        *           The certificate alias, used to store the private key and certificate.
        * @param    validity
        *           The certificate validity, in years.
        * @param    commonName
        *           The certificate's common name (CN).
        * @param    fieldMap
        *           A map with additional subject attributes.
        * @param    certStore
        *           The store where to save the certificate.
        * @param    certKeyStore
        *           The store where to save the private key.
        *
        * @return   The password used to encrypt the private key in the store.
        *
        * @throws   SSLCertGenException
        *           If the root CA is not yet generated or the certificate could not be generated.
        */
       public abstract String generateCertificate(String              alias,
                                                  int                 validity,
                                                  String              commonName,
                                                  Map<String, String> fieldMap,
                                                  KeyStore            certStore,
                                                  KeyStore            certKeyStore)
       throws SSLCertGenException;

       /**
        * Set the details for the root CA certificate.
        *
        * @param    cert
        *           The certificate.
        * @param    pk
        *           The private key.
        */
       public abstract void setRootCA(X509Certificate cert, PrivateKey pk)
       throws SSLCertGenException;

       /**
        * Generate a root CA certificate, which will be used to sign all the issues certificates.
        * <p>
        * The encrypted private key will be saved in the specified {@code certKeyStore}; the encrypt
        * password will be returned by this API.
        * <p>
        * The public root CA certificate will be saved in the specified {@code certStore}.
        *
        * @param    alias
        *           The certificate alias, used to store the private key and certificate.
        * @param    validity
        *           The certificate validity, in years.
        * @param    commonName
        *           The certificate's common name (CN).
        * @param    fieldMap
        *           A map with additional subject attributes.
        * @param    certStore
        *           The store where to save the certificate.
        * @param    certKeyStore
        *           The store where to save the private key.
        *
        * @return   The password used to encrypt the private key in the store.
        *
        * @throws   SSLCertGenException
        *           If the root CA is not yet generated or the certificate could not be generated.
        */
       public abstract String generateRootCA(String              alias,
                                             int                 validity,
                                             String              commonName,
                                             Map<String, String> fieldMap,
                                             KeyStore            certStore,
                                             KeyStore            certKeyStore)
       throws SSLCertGenException;

       /**
        * Generate a self-signed certificate. This may be used as a root CA.
        * <p>
        * The encrypted private key will be saved in the specified {@code certKeyStore}; the encrypt
        * password will be returned by this API.
        * <p>
        * The public certificate will be saved in the specified {@code certStore}.
        *
        * @param    alias
        *           The certificate alias, used to store the private key and certificate.
        * @param    certificateAuthority
        *           Flag indicating if the generated self-signed certificate will be used as the root
        *           CA.
        * @param    validity
        *           The certificate validity, in years.
        * @param    commonName
        *           The certificate's common name (CN).
        * @param    fieldMap
        *           A map with additional subject attributes.
        * @param    certStore
        *           The store where to save the certificate.
        * @param    certKeyStore
        *           The store where to save the private key.
        *
        * @return   The password used to encrypt the private key in the store.
        *
        * @throws   SSLCertGenException
        *           If the root CA is not yet generated or the certificate could not be generated.
        */
       public abstract String generateSelfSignedCertificate(String              alias,
                                                            boolean             certificateAuthority,
                                                            int                 validity,
                                                            String              commonName,
                                                            Map<String, String> fieldMap,
                                                            KeyStore            certStore,
                                                            KeyStore            certKeyStore)
       throws SSLCertGenException;

       /**
        * Loads externally generated certificate suite of public and private certificates with
        * trusted certificate chain if this PEM file is provided.
        *
        * @param    certPemFile
        *           The PEM file that contains the public certificate signed by well-known authorities
        *           or self-signed.
        * @param    privateKeyPemFile
        *           The PEM file that contains the private certificate signed by well-known authorities
        *           or self-signed.
        * @param    certChainPemFile
        *           The PEM file that contains the intermediate certificates up to the well-known
        *           authority CA certificate.
        *
        * @return   The certificate suite
        *
        * @throws   SSLCertGenException
        *           If IO or parse exceptions are thrown
        */
       public abstract CertificateSuite loadCertificateSuite(String certPemFile,
                                                             String privateKeyPemFile,
                                                             String certChainPemFile)
       throws SSLCertGenException;

       /**
        * Save the root CA into the provided certificate and private key stores to be accessed by the
        * given alias.
        *
        * @param    alias
        *           The certificate alias
        * @param    certStore
        *           The certificate store
        * @param    certKeyStore
        *           The certificate private key store
        *
        * @return   The protected private key password.
        *
        * @throws   SSLCertGenException
        *
        */
       public abstract String saveRootCA(String                  alias,
                                         KeyStore                certStore,
                                         KeyStore                certKeyStore)
       throws SSLCertGenException;

       /**
        * Get the map with the mandatory subject attributes.  The key is the string representation of
        * this attribute, while the value will be the associated description.
        *
        * @return   See above.
        */
       public abstract Map<String, String> getMandatorySubjectFields();

       /**
        * Initialize this SSL certificate factory.
        *
        * @param    keyStrength
        *           The private key size: {@link SSLCertFactory#MIN_RSA_KEY_STRENGTH} bits or better.
        *           If {@code null}, defaults to {@link SSLCertFactory#MIN_RSA_KEY_STRENGTH}.
        * @param    exponent
        *           The public key exponent.  If {@code null}, defaults to 65537.
        *           WARNING: a wrong value may result in vulnerable SSL private keys and also 3rd party
        *           software might not accept them. Use with care.
        *
        * @throws   SSLCertGenException
        *           If the factory could not be instantiated.
        */
       public void init(Integer keyStrength, BigInteger exponent)
       throws SSLCertGenException
       {
          this.keyStrength = (keyStrength == null) ? MIN_RSA_KEY_STRENGTH : keyStrength;
          if (this.keyStrength < MIN_RSA_KEY_STRENGTH)
          {
             final String msg = "The RSA private key size %d is smaller than %d bits.";
             throw new SSLCertGenException(String.format(msg, this.keyStrength, MIN_RSA_KEY_STRENGTH));
          }

          this.exponent = (exponent == null) ? new BigInteger("65537") : exponent;

          if (this.exponent.testBit(0) == false)
          {
             final String msg = "The RSA public exponent %s is even.";
             throw new SSLCertGenException(String.format(msg, this.exponent.toString()));
          }
       }

       /**
        * Build a full chain from the root certificate up to the well-known authority.
        *
        * @return   Certificate chain
        */
       public abstract Certificate[] getFullChainFromRoot();

       /**
        * Sets the trusted certificate chain.
        *
        * @param    chain
        *           The root certificate chain up to the well-known authority
        */
       public abstract void setCertificateChain(Certificate[] chain);

       /**
        * Holds public and private certificates with trusted certificate chain up to a well-known CA.
        */
       public static class CertificateSuite
       {
          /** Private certificate */
          public PrivateKey privateKey;

          /** Public certificate */
          public X509Certificate publicKey;

          /** Certificate chain */
          public X509Certificate[] chain;

          /**
           * Returns its full chain.
           *
           * @return   The full chain
           */
          public X509Certificate[] getFullChain()
          {
             X509Certificate[] fullChain = new X509Certificate[chain.length + 1];

             fullChain[0] = publicKey;

             if (chain != null && chain.length > 0)
             {
                System.arraycopy(chain, 0, fullChain, 1, chain.length);
             }

             return fullChain;
          }
       }
    }