/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.ctecinf.nfe;

import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
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 javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
 *
 * @author Cássio Conceição
 * @since 06/11/2018
 * @version 201811
 * @see http://ctecinf.com.br/
 */
public class CACerts {

    /**
     * Gera CA certs
     *
     * @throws Exception
     */
    public static void generateCACerts() throws Exception {

        File cacerts = new File(Constants.CA_CERTS);

        if (!cacerts.exists()) {

            File file = new File(Constants.CA_CERTS);

            if (file.isFile() == false) {

                File dir = new File(System.getProperty("java.home") + File.separator + "lib" + File.separator + "security");

                file = new File(dir, Constants.CA_CERTS);

                if (file.isFile() == false) {
                    file = new File(dir, Constants.CA_CERTS);
                }
            }

            file.setReadable(true, false);
            file.setWritable(true, false);
            file.setExecutable(true, false);

            KeyStore ks;

            try (InputStream in = new FileInputStream(file)) {
                ks = KeyStore.getInstance(KeyStore.getDefaultType());
                ks.load(in, Constants.CA_CERTS_PASS.toCharArray());
            }

            JTextArea edit = new JTextArea();
            edit.setBackground(Color.BLACK);
            edit.setForeground(Color.GREEN);
            edit.setEditable(false);
            edit.setColumns(50);
            edit.setRows(12);
            edit.setText("Gerando CACerts...");

            JDialog dialog = new JDialog();
            dialog.setTitle("CACerts");
            dialog.add(new JScrollPane(edit));
            dialog.setAlwaysOnTop(true);
            dialog.setResizable(false);
            dialog.pack();
            dialog.setLocationRelativeTo(null);
            dialog.setVisible(true);

            for (String url : Constants.CA_CERTS_URL) {
                CACerts.generate(edit, url, 443, ks);
            }

            dialog.dispose();

            File cafile = new File(Constants.CA_CERTS);

            try (OutputStream out = new FileOutputStream(cafile)) {
                ks.store(out, Constants.CA_CERTS_PASS.toCharArray());
            }

            cafile.setReadable(true, false);
            cafile.setWritable(true, false);
            cafile.setExecutable(true, false);

            JOptionPane.showMessageDialog(null, "O Sistema precisa ser reeniciado para atualizar certificados.\nClique 'OK' e retire o certificado digital do leitor.\nAntes de iniciar insira novamente o certificado digital no leitor.");
            System.exit(0);
        } else {
            JOptionPane.showMessageDialog(null, "Arquivo já gerado.");
        }
    }

    private static void generate(JTextArea debug, String host, int port, KeyStore ks) throws Exception {

        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();

        debug.setText("INFO: Opening connection to " + host + ":" + port + "...\n" + debug.getText());

        SSLSocket socket = (SSLSocket) factory.createSocket(host, port);
        socket.setSoTimeout(10000);

        try {

            debug.setText("INFO: Starting SSL handshake...\n" + debug.getText());

            socket.startHandshake();
            socket.close();

            debug.setText("INFO: No errors, certificate is already trusted\n" + debug.getText());

        } catch (IOException ex) {
            System.err.println(ex);
        }

        X509Certificate[] chain = tm.chain;

        if (chain == null) {
            debug.setText("INFO: Could not obtain server certificate chain\n" + debug.getText());
        }

        debug.setText("INFO: Server sent " + (chain == null ? "0" : chain.length) + " certificate(s):\n" + debug.getText());

        MessageDigest sha1 = MessageDigest.getInstance("SHA1");
        MessageDigest md5 = MessageDigest.getInstance("MD5");

        if (chain != null) {

            for (int i = 0; i < chain.length; i++) {

                X509Certificate cert509 = chain[i];
                sha1.update(cert509.getEncoded());
                md5.update(cert509.getEncoded());

                String alias = host + "-" + (i);
                ks.setCertificateEntry(alias, cert509);

                debug.setText("INFO: Added certificate to keystore 'cacerts' using alias '" + alias + "'\n" + debug.getText());
            }
        }
    }

    private static class SavingTrustManager implements X509TrustManager {

        private final X509TrustManager tm;
        private X509Certificate[] chain;

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

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

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

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

}
