/**
 * Copyright (c) The openTCS Authors.
 *
 * This program is free software and subject to the MIT license. (For details,
 * see the licensing information (LICENSE.txt) you should have received with
 * this copy of the software.)
 */
package org.opentcs.access.rmi.factories;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.Serializable;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import static java.util.Objects.requireNonNull;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import org.opentcs.access.SslParameterSet;

/**
 * Provides methods for creating client-side and server-side {@link SSLContext} instances.
 *
 * @author Martin Grzenia (Fraunhofer IML)
 */
class SecureSslContextFactory
    implements Serializable {

  /**
   * The name of the algorithm to use for the {@link KeyManagerFactory} and
   * {@link TrustManagerFactory}.
   */
  private static final String KEY_TRUST_MANAGEMENT_ALGORITHM = "SunX509";
  /**
   * The protocol to use with the ssl context.
   */
  private static final String SSL_CONTEXT_PROTOCOL = "TLSv1.2";
  /**
   * The ssl parameters to be used for creating the ssl context.
   */
  private final SslParameterSet sslParameterSet;

  /**
   * Creates a new instance.
   *
   * @param sslParameterSet The ssl parameters to be used for creating the ssl context.
   */
  public SecureSslContextFactory(SslParameterSet sslParameterSet) {
    this.sslParameterSet = requireNonNull(sslParameterSet, "sslParameterSet");
  }

  /**
   * Creates an instance of {@link SSLContext} for the client.
   *
   * @return The ssl context.
   * @throws IllegalStateException If the creation of the ssl context fails.
   */
  public SSLContext createClientContext()
      throws IllegalStateException {
    SSLContext context = null;

    try {
      KeyStore ts = KeyStore.getInstance(sslParameterSet.getKeystoreType());
      ts.load(new FileInputStream(sslParameterSet.getTruststoreFile()),
              sslParameterSet.getTruststorePassword().toCharArray());
      TrustManagerFactory tmf = TrustManagerFactory.getInstance(KEY_TRUST_MANAGEMENT_ALGORITHM);
      tmf.init(ts);

      context = SSLContext.getInstance(SSL_CONTEXT_PROTOCOL);
      context.init(null, tmf.getTrustManagers(), null);
    }
    catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException
               | KeyManagementException ex) {
      throw new IllegalStateException("Error creating the client's ssl context", ex);
    }

    return context;
  }

  /**
   * Creates an instance of {@link SSLContext} for the server.
   *
   * @return The ssl context.
   * @throws IllegalStateException If the creation of the ssl context fails.
   */
  public SSLContext createServerContext()
      throws IllegalStateException {
    SSLContext context = null;

    try {
      KeyStore ks = KeyStore.getInstance(sslParameterSet.getKeystoreType());
      ks.load(new FileInputStream(sslParameterSet.getKeystoreFile()),
              sslParameterSet.getKeystorePassword().toCharArray());
      KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_TRUST_MANAGEMENT_ALGORITHM);
      kmf.init(ks, sslParameterSet.getKeystorePassword().toCharArray());

      context = SSLContext.getInstance(SSL_CONTEXT_PROTOCOL);
      context.init(kmf.getKeyManagers(), null, null);

    }
    catch (NoSuchAlgorithmException | KeyStoreException | CertificateException | IOException
               | KeyManagementException | UnrecoverableKeyException ex) {
      throw new IllegalStateException("Error creating the server's ssl context", ex);
    }

    return context;
  }
}