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;
      }
   }
}