package aQute.jpm.main;

/*
 * Copyright 2006 Sun Microsystems, Inc.  All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   - Neither the name of Sun Microsystems nor the names of its
 *     contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/**
 * http://blogs.sun.com/andreas/resource/InstallCert.java
 * Use:
 * java InstallCert hostname
 * Example:
 *% java InstallCert ecc.fedora.redhat.com
 */

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import aQute.lib.io.IO;
import aQute.service.reporter.Reporter;

/**
 * Class used to add the server's certificate to the KeyStore with your trusted
 * certificates.
 */
public class InstallCert {
	private final static Logger logger = LoggerFactory.getLogger(InstallCert.class);

	public static void installCert(Reporter reporter, String host, int port, String passphrase, File file,
			boolean install) throws Exception {

		KeyStore ks = getKeystore(passphrase, file);

		SSLContext context = SSLContext.getInstance("TLS");
		TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
		tmf.init(ks);
		X509TrustManager defaultTrustManager = (X509TrustManager) tmf.getTrustManagers()[0];
		SavingTrustManager tm = new SavingTrustManager(defaultTrustManager);
		context.init(null, new TrustManager[] {
				tm
		}, null);
		SSLSocketFactory factory = context.getSocketFactory();

		logger.debug("Opening connection to {}:{}", host, port);
		SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
		socket.setSoTimeout(10000);
		try {
			logger.debug("Starting SSL handshake...");
			socket.startHandshake();
			socket.close();
			logger.debug("No errors, certificate is already trusted");
		} catch (SSLException e) {
			logger.debug("expected exception");
		}

		X509Certificate[] chain = tm.chain;
		if (chain == null) {
			logger.debug("Could not obtain server certificate chain");
			return;
		}

		logger.debug("Server sent {} certificate(s):", chain.length);

		System.out.println("Chain");
		String trusted = null;

		for (int i = 0; i < chain.length; i++) {
			X509Certificate cert = chain[i];
			String alias = ks.getCertificateAlias(cert);
			if (alias != null && trusted == null)
				trusted = alias;

			System.out.printf("%-40s %s%n", alias, cert.getSubjectDN());
		}

		if (trusted != null) {
			System.out
					.println("This server is trusted, certificate in the chain was found as alias \"" + trusted + "\"");
			return;
		}

		if (!install)
			return;

		X509Certificate cert = chain[0];
		String alias = host;
		ks.setCertificateEntry(alias, cert);

		saveKeystore(passphrase, file, ks);

		logger.debug("Added certificate to keystore '{}' using alias '{}'", file, alias);
	}

	private static void saveKeystore(String passphrase, File file, KeyStore ks) throws FileNotFoundException,
			IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
		file = getCacertFile(file);
		try (OutputStream out = IO.outputStream(file)) {
			ks.store(out, passphrase.toCharArray());
		}
	}

	private static KeyStore getKeystore(String passphrase, File file) throws FileNotFoundException, KeyStoreException,
			IOException, NoSuchAlgorithmException, CertificateException {
		file = getCacertFile(file);
		KeyStore ks;
		try (InputStream in = IO.stream(file)) {
			ks = KeyStore.getInstance(KeyStore.getDefaultType());
			ks.load(in, passphrase.toCharArray());
		}

		System.out.println("In key store");
		for (Enumeration<String> e = ks.aliases(); e.hasMoreElements();) {
			String alias = e.nextElement();
			Certificate certificate = ks.getCertificate(alias);
			if (certificate instanceof X509Certificate) {
				X509Certificate x509 = (X509Certificate) certificate;
				System.out.printf("%-40s %s%n", alias, x509.getSubjectDN());
			}
		}
		System.out.println("---------------");
		return ks;
	}

	private static File getCacertFile(File file) {
		if (file == null) {
			File java = new File(System.getProperty("java.home"));
			file = IO.getFile(java, "lib/security/jssecacerts");
			if (!file.isFile())
				file = IO.getFile(java, "lib/security/cacerts");
			if (!file.isFile())
				file = IO.getFile(java, "jre/lib/security/cacerts");
			if (!file.isFile())
				throw new IllegalArgumentException(
						"Cannot find certifcate file in $JAVA_HOME/lib/security/(jsse)?cacerts");
			System.out.printf("using cacerts %s%n", file);
		}
		return file;
	}

	public static void deleteCert(String alias, String password, File cacerts) throws FileNotFoundException,
			KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
		KeyStore ks = getKeystore(password, cacerts);

		Certificate certificate = ks.getCertificate(alias);
		if (certificate == null) {
			System.out.println("No such alias " + alias);
			return;
		}

		ks.deleteEntry(alias);

		saveKeystore(password, cacerts, ks);
	}

	private static class SavingTrustManager implements X509TrustManager {

		private final X509TrustManager	tm;
		X509Certificate[]				chain;

		SavingTrustManager(X509TrustManager tm) {
			this.tm = tm;
		}

		public X509Certificate[] getAcceptedIssuers() {
			throw new UnsupportedOperationException();
		}

		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			throw new UnsupportedOperationException();
		}

		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
			this.chain = chain;
			tm.checkServerTrusted(chain, authType);
		}
	}

}
