/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.infrastructure.docker.client;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.eclipse.che.commons.lang.NameGenerator;

/** @author andrew00x */
public class DockerCertificates {
  public static final String DEFAULT_CA_CERT_NAME = "ca.pem";
  public static final String DEFAULT_CLIENT_CERT_NAME = "cert.pem";
  public static final String DEFAULT_CLIENT_KEY_NAME = "key.pem";

  private static final char[] KEY_STORE_PASSWORD = NameGenerator.generate(null, 12).toCharArray();

  public static DockerCertificates loadFromDirectory(String dockerCertDir) {
    return loadFromDirectory(Paths.get(dockerCertDir));
  }

  public static DockerCertificates loadFromDirectory(File dockerCertDir) {
    return loadFromDirectory(dockerCertDir.toPath());
  }

  public static DockerCertificates loadFromDirectory(Path dockerCertDirPath) {
    try {
      final Path caCertPath = dockerCertDirPath.resolve(DEFAULT_CA_CERT_NAME);
      final Path clientKeyPath = dockerCertDirPath.resolve(DEFAULT_CLIENT_KEY_NAME);
      final Path clientCertPath = dockerCertDirPath.resolve(DEFAULT_CLIENT_CERT_NAME);
      final CertificateFactory cf = CertificateFactory.getInstance("X.509");
      final Certificate caCert = getCertificate(caCertPath, cf);
      final Certificate clientCert = getCertificate(clientCertPath, cf);
      final PrivateKey clientKey = getPrivateKey(clientKeyPath);
      final KeyStore keyStore = createKeyStore(clientCert, clientKey);
      final KeyStore trustStore = createTrustStore(caCert);
      final KeyManager[] keyManagers = loadKeyManagers(keyStore, KEY_STORE_PASSWORD);
      final TrustManager[] trustManagers = loadTrustManagers(trustStore);
      return new DockerCertificates(createSSLContext(keyManagers, trustManagers));
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  private static Certificate getCertificate(Path caCertPath, CertificateFactory cf)
      throws IOException, CertificateException {
    try (InputStream inputStream = Files.newInputStream(caCertPath)) {
      return cf.generateCertificate(inputStream);
    }
  }

  private static PrivateKey getPrivateKey(Path clientKeyPath)
      throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
    final PEMKeyPair clientKeyPair;
    try (Reader reader = Files.newBufferedReader(clientKeyPath, Charset.defaultCharset())) {
      clientKeyPair = (PEMKeyPair) new PEMParser(reader).readObject();
    }
    final PKCS8EncodedKeySpec spec =
        new PKCS8EncodedKeySpec(clientKeyPair.getPrivateKeyInfo().getEncoded());
    final KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePrivate(spec);
  }

  private static KeyStore createKeyStore(Certificate clientCert, PrivateKey clientKey)
      throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
    final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null, null);
    keyStore.setCertificateEntry("client", clientCert);
    keyStore.setKeyEntry("key", clientKey, KEY_STORE_PASSWORD, new Certificate[] {clientCert});
    return keyStore;
  }

  private static KeyStore createTrustStore(Certificate caCert)
      throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
    final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
    trustStore.load(null, null);
    trustStore.setEntry("ca", new KeyStore.TrustedCertificateEntry(caCert), null);
    return trustStore;
  }

  private static KeyManager[] loadKeyManagers(KeyStore keystore, char[] keyPassword)
      throws NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException {
    final KeyManagerFactory kmf =
        KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    kmf.init(keystore, keyPassword);
    return kmf.getKeyManagers();
  }

  private static TrustManager[] loadTrustManagers(KeyStore trustStore)
      throws NoSuchAlgorithmException, KeyStoreException {
    final TrustManagerFactory tmf =
        TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    tmf.init(trustStore);
    return tmf.getTrustManagers();
  }

  private static SSLContext createSSLContext(KeyManager[] keyManagers, TrustManager[] trustManagers)
      throws NoSuchAlgorithmException, KeyManagementException {
    final SSLContext sslcontext = SSLContext.getInstance("TLS");
    sslcontext.init(keyManagers, trustManagers, null);
    return sslcontext;
  }

  private final SSLContext sslContext;

  private DockerCertificates(SSLContext sslContext) {
    this.sslContext = sslContext;
  }

  public SSLContext getSslContext() {
    return sslContext;
  }
}
