WaarpSecureKeyStore.java

/*
 * This file is part of Waarp Project (named also Waarp or GG).
 *
 *  Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
 *  tags. See the COPYRIGHT.txt in the distribution for a full listing of
 * individual contributors.
 *
 *  All Waarp Project is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Waarp 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 * Waarp . If not, see <http://www.gnu.org/licenses/>.
 */
package org.waarp.common.crypto.ssl;

import org.joda.time.DateTime;
import org.waarp.common.exception.CryptoException;
import org.waarp.common.file.FileUtils;
import org.waarp.common.logging.WaarpLogger;
import org.waarp.common.logging.WaarpLoggerFactory;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.Enumeration;

import static org.waarp.common.digest.WaarpBC.*;

/**
 * SecureKeyStore for SLL
 */
public class WaarpSecureKeyStore {
  private static final String CANNOT_SAVE_TO_FILE_KEY_STORE_INSTANCE =
      "Cannot save to file KeyStore Instance";

  private static final String CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE =
      "Cannot create KeyManagerFactory Instance";

  private static final String CANNOT_CREATE_KEY_STORE_INSTANCE =
      "Cannot create KeyStore Instance";

  /**
   * Internal Logger
   */
  private static final WaarpLogger logger =
      WaarpLoggerFactory.getLogger(WaarpSecureKeyStore.class);
  private static final String CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE =
      "Cannot create TrustManagerFactory Instance";
  private static final String CANNOT_CREATE_KEY_TRUST_STORE_INSTANCE =
      "Cannot create keyTrustStore Instance";
  private static final String CANNOT_SAVE_TO_FILE_KEY_TRUST_STORE_INSTANCE =
      "Cannot save to file keyTrustStore Instance";

  static {
    initializedTlsContext();
  }

  private String keyStoreFilename;
  private KeyStore keyStore;
  private KeyManagerFactory keyManagerFactory;
  private String keyStorePasswd;
  private String keyPassword;
  private WaarpSecureTrustManagerFactory secureTrustManagerFactory;
  private KeyStore keyTrustStore;
  private String trustStorePasswd;

  /**
   * Initialize empty KeyStore. No TrustStore is internally created.
   *
   * @param keyStorePasswd
   * @param keyPassword
   *
   * @throws CryptoException
   */
  public WaarpSecureKeyStore(final String keyStorePasswd,
                             final String keyPassword) throws CryptoException {
    this.keyStorePasswd = keyStorePasswd;
    this.keyPassword = keyPassword;
    try {
      keyStore = KeyStore.getInstance("JKS");
    } catch (final KeyStoreException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    }
    try {
      // Empty keyStore created so null for the InputStream
      keyStore.load(null, getKeyStorePassword());
    } catch (final NoSuchAlgorithmException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } catch (final CertificateException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } catch (final FileNotFoundException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } catch (final IOException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    }
    initKeyManagerFactory();
  }

  /**
   * Initialize the SecureKeyStore with no TrustStore from file
   *
   * @param keyStoreFilename
   * @param keyStorePasswd
   * @param keyPassword
   *
   * @throws CryptoException
   */
  public WaarpSecureKeyStore(final String keyStoreFilename,
                             final String keyStorePasswd,
                             final String keyPassword) throws CryptoException {
    initKeyStore(keyStoreFilename, keyStorePasswd, keyPassword);
  }

  /**
   * Initialize the SecureKeyStore and TrustStore from files
   *
   * @param keyStoreFilename
   * @param keyStorePasswd
   * @param keyPassword
   * @param trustStoreFilename if Null, no TrustKeyStore will be
   *     created
   * @param trustStorePasswd
   * @param needClientAuthent True if the TrustStore is also used for
   *     Client
   *     Authentication
   *
   * @throws CryptoException
   */
  public WaarpSecureKeyStore(final String keyStoreFilename,
                             final String keyStorePasswd,
                             final String keyPassword,
                             final String trustStoreFilename,
                             final String trustStorePasswd,
                             final boolean needClientAuthent)
      throws CryptoException {
    // Create the KeyStore
    initKeyStore(keyStoreFilename, keyStorePasswd, keyPassword);
    // Now create the TrustKeyStore
    if (trustStoreFilename != null) {
      initTrustStore(trustStoreFilename, trustStorePasswd, needClientAuthent);
    } else {
      initEmptyTrustStore();
    }
  }

  /**
   * Initialize the SecureKeyStore with no TrustStore from file
   *
   * @param keystoreFilename
   * @param keystorePasswd
   * @param keyPasswordNew
   *
   * @throws CryptoException
   */
  public final void initKeyStore(final String keystoreFilename,
                                 final String keystorePasswd,
                                 final String keyPasswordNew)
      throws CryptoException {
    keyStoreFilename = keystoreFilename;
    keyStorePasswd = keystorePasswd;
    keyPassword = keyPasswordNew;
    // First keyStore itself
    try {
      keyStore = KeyStore.getInstance("JKS");
    } catch (final KeyStoreException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    }
    FileInputStream inputStream = null;
    try {
      final File temp = new File(keystoreFilename).getAbsoluteFile();
      inputStream = new FileInputStream(temp);
      keyStore.load(inputStream, getKeyStorePassword());
    } catch (final NoSuchAlgorithmException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } catch (final CertificateException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } catch (final FileNotFoundException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } catch (final IOException e) {
      logger.error(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_STORE_INSTANCE, e);
    } finally {
      FileUtils.close(inputStream);
    }
    checkExpiryDate(keyStore);
    initKeyManagerFactory();
  }

  /**
   * Init KeyManagerFactory
   *
   * @throws CryptoException
   */
  final void initKeyManagerFactory() throws CryptoException {
    try {
      keyManagerFactory = KeyManagerFactory.getInstance(
          KeyManagerFactory.getDefaultAlgorithm());
    } catch (final NoSuchAlgorithmException e) {
      logger.error(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
    }
    try {
      keyManagerFactory.init(keyStore, getCertificatePassword());
    } catch (final UnrecoverableKeyException e) {
      logger.error(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
    } catch (final KeyStoreException e) {
      logger.error(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
    } catch (final NoSuchAlgorithmException e) {
      logger.error(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_KEY_MANAGER_FACTORY_INSTANCE, e);
    }
  }

  /**
   * Delete a Key from the KeyStore based on its alias
   *
   * @param alias
   *
   * @return True if entry is deleted
   */
  public final boolean deleteKeyFromKeyStore(final String alias) {
    try {
      keyStore.deleteEntry(alias);
    } catch (final KeyStoreException e) {
      logger.error("Cannot delete Key from KeyStore Instance", e);
      return false;
    }
    return true;
  }

  /**
   * Add a Key and its certificates into the KeyStore based on its alias
   *
   * @param alias
   * @param key
   * @param chain
   *
   * @return True if entry is added
   */
  public final boolean setKeytoKeyStore(final String alias, final Key key,
                                        final Certificate[] chain) {
    try {
      keyStore.setKeyEntry(alias, key, getCertificatePassword(), chain);
    } catch (final KeyStoreException e) {
      logger.error("Cannot add Key and Certificates to KeyStore Instance", e);
      return false;
    }
    return true;
  }

  /**
   * Save a KeyStore to a file
   *
   * @param filename
   *
   * @return True if keyStore is saved to file
   */
  public final boolean saveKeyStore(final String filename) {
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(filename);
      try {
        keyStore.store(fos, getKeyStorePassword());
      } catch (final KeyStoreException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_STORE_INSTANCE, e);
        return false;
      } catch (final NoSuchAlgorithmException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_STORE_INSTANCE, e);
        return false;
      } catch (final CertificateException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_STORE_INSTANCE, e);
        return false;
      } catch (final IOException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_STORE_INSTANCE, e);
        return false;
      }
    } catch (final FileNotFoundException e) {
      logger.error(CANNOT_SAVE_TO_FILE_KEY_STORE_INSTANCE, e);
      return false;
    } finally {
      FileUtils.close(fos);
    }
    return true;
  }

  /**
   * Initialize the TrustStore from a filename and its password
   *
   * @param truststoreFilename
   * @param truststorePasswd
   * @param needClientAuthent True if the TrustStore is also to
   *     authenticate
   *     clients
   *
   * @throws CryptoException
   */
  public final void initTrustStore(final String truststoreFilename,
                                   final String truststorePasswd,
                                   final boolean needClientAuthent)
      throws CryptoException {
    trustStorePasswd = truststorePasswd;
    try {
      keyTrustStore = KeyStore.getInstance("JKS");
    } catch (final KeyStoreException e) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e);
    }
    FileInputStream inputStream = null;
    try {
      final File temp = new File(truststoreFilename).getAbsoluteFile();
      inputStream = new FileInputStream(temp);
      keyTrustStore.load(inputStream, getKeyTrustStorePassword());
    } catch (final NoSuchAlgorithmException e) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e);
    } catch (final CertificateException e) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e);
    } catch (final FileNotFoundException e) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e);
    } catch (final IOException e) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e);
    } finally {
      FileUtils.close(inputStream);
    }
    checkExpiryDate(keyTrustStore);
    final TrustManagerFactory trustManagerFactory;
    try {
      trustManagerFactory = TrustManagerFactory.getInstance(
          KeyManagerFactory.getDefaultAlgorithm());
    } catch (final NoSuchAlgorithmException e1) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e1);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e1);
    }
    try {
      trustManagerFactory.init(keyTrustStore);
    } catch (final KeyStoreException e1) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e1);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e1);
    }
    try {
      secureTrustManagerFactory =
          new WaarpSecureTrustManagerFactory(trustManagerFactory,
                                             needClientAuthent);
    } catch (final CryptoException e) {
      logger.error(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE, e);
      throw new CryptoException(CANNOT_CREATE_TRUST_MANAGER_FACTORY_INSTANCE,
                                e);
    }
  }

  /**
   * Initialize an empty TrustStore
   *
   * @return True if correctly initialized empty
   */
  public final boolean initEmptyTrustStore() {
    trustStorePasswd = "secret";//NOSONAR
    try {
      keyTrustStore = KeyStore.getInstance("JKS");
    } catch (final KeyStoreException e) {
      logger.error(CANNOT_CREATE_KEY_TRUST_STORE_INSTANCE, e);
      return false;
    }
    try {
      // Empty keyTrustStore created so null for the InputStream
      keyTrustStore.load(null, getKeyTrustStorePassword());
    } catch (final NoSuchAlgorithmException e) {
      logger.error(CANNOT_CREATE_KEY_TRUST_STORE_INSTANCE, e);
      return false;
    } catch (final CertificateException e) {
      logger.error(CANNOT_CREATE_KEY_TRUST_STORE_INSTANCE, e);
      return false;
    } catch (final FileNotFoundException e) {
      logger.error(CANNOT_CREATE_KEY_TRUST_STORE_INSTANCE, e);
      return false;
    } catch (final IOException e) {
      logger.error(CANNOT_CREATE_KEY_TRUST_STORE_INSTANCE, e);
      return false;
    }
    secureTrustManagerFactory = new WaarpSecureTrustManagerFactory();
    return true;
  }

  /**
   * Delete a Key from the TrustStore based on its alias
   *
   * @param alias
   *
   * @return True if entry is deleted
   */
  public final boolean deleteKeyFromTrustStore(final String alias) {
    try {
      keyStore.deleteEntry(alias);
    } catch (final KeyStoreException e) {
      logger.error("Cannot delete Key from keyTrustStore Instance", e);
      return false;
    }
    return true;
  }

  /**
   * Add a Certificate into the TrustStore based on its alias
   *
   * @param alias
   * @param cert
   *
   * @return True if entry is added
   */
  public final boolean setKeytoTrustStore(final String alias,
                                          final Certificate cert) {
    try {
      keyStore.setCertificateEntry(alias, cert);
    } catch (final KeyStoreException e) {
      logger.error("Cannot add Certificate to keyTrustStore Instance", e);
      return false;
    }
    return true;
  }

  /**
   * Save the TrustStore to a file
   *
   * @param filename
   *
   * @return True if keyTrustStore is saved to file
   */
  public final boolean saveTrustStore(final String filename) {
    FileOutputStream fos = null;
    try {
      fos = new FileOutputStream(filename);
      try {
        keyTrustStore.store(fos, getKeyTrustStorePassword());
      } catch (final KeyStoreException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_TRUST_STORE_INSTANCE, e);
        return false;
      } catch (final NoSuchAlgorithmException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_TRUST_STORE_INSTANCE, e);
        return false;
      } catch (final CertificateException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_TRUST_STORE_INSTANCE, e);
        return false;
      } catch (final IOException e) {
        logger.error(CANNOT_SAVE_TO_FILE_KEY_TRUST_STORE_INSTANCE, e);
        return false;
      }
    } catch (final FileNotFoundException e) {
      logger.error(CANNOT_SAVE_TO_FILE_KEY_TRUST_STORE_INSTANCE, e);
      return false;
    } finally {
      FileUtils.close(fos);
    }
    return true;
  }

  /**
   * Load a certificate from a filename
   *
   * @param filename
   *
   * @return the X509 Certificate from filename
   *
   * @throws CertificateException
   * @throws FileNotFoundException
   */
  public static Certificate loadX509Certificate(final String filename)
      throws CertificateException, FileNotFoundException {
    final CertificateFactory cf = CertificateFactory.getInstance("X.509");
    final FileInputStream in = new FileInputStream(filename);
    try {
      return cf.generateCertificate(in);
    } finally {
      FileUtils.close(in);
    }
  }

  /**
   * @return the certificate Password
   */
  public final char[] getCertificatePassword() {
    if (keyPassword != null) {
      return keyPassword.toCharArray();
    }
    return "nopwd".toCharArray();
  }

  /**
   * @return the KeyStore Password
   */
  public final char[] getKeyStorePassword() {
    if (keyStorePasswd != null) {
      return keyStorePasswd.toCharArray();
    }
    return "nopwd".toCharArray();
  }

  /**
   * @return the KeyTrustStore Password
   */
  public final char[] getKeyTrustStorePassword() {
    if (trustStorePasswd != null) {
      return trustStorePasswd.toCharArray();
    }
    return "nopwd".toCharArray();
  }

  /**
   * @return the KeyStore Filename
   */
  public final String getKeyStoreFilename() {
    return keyStoreFilename;
  }

  /**
   * @return the secureTrustManagerFactory
   */
  public final WaarpSecureTrustManagerFactory getSecureTrustManagerFactory() {
    return secureTrustManagerFactory;
  }

  /**
   * @return the keyManagerFactory
   */
  public final KeyManagerFactory getKeyManagerFactory() {
    return keyManagerFactory;
  }

  /**
   * @return the KeyStore
   */
  public final KeyStore getKeyStore() {
    return keyStore;
  }

  /**
   * @return the Trust KeyStore
   */
  public final KeyStore getKeyTrustStore() {
    return keyTrustStore;
  }

  /**
   * @param keystore
   *
   * @return True if all certificates are OK
   */
  public static boolean checkExpiryDate(final KeyStore keystore) {
    final Enumeration<String> aliases;
    try {
      aliases = keystore.aliases();
    } catch (final KeyStoreException e) {//NOSONAR
      logger.warn("Cannot get Aliases: {}", e.getMessage());
      return true;
    }
    Date expiryDate;
    boolean valid = true;
    for (; aliases.hasMoreElements(); ) {
      final String alias = aliases.nextElement();
      try {
        expiryDate =
            ((X509Certificate) keystore.getCertificate(alias)).getNotAfter();
        final DateTime dateTime = new DateTime(expiryDate);
        if (dateTime.isBeforeNow()) {
          logger.error("Certificate {} has an expiry date before today: {}",
                       alias, dateTime);
          valid = false;
        } else {
          logger.debug("Certificate {} has an expiry date over today: {}",
                       alias, dateTime);
        }
      } catch (final KeyStoreException e) {//NOSONAR
        logger.warn("Cannot get Expiry Date: {}", e.getMessage());
      }
    }
    return valid;
  }
}