package org.bouncycastle.test.est.examples;


import java.io.File;
import java.security.Provider;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Set;

import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.est.CACertsResponse;
import org.bouncycastle.est.ESTService;
import org.bouncycastle.est.jcajce.JcaJceUtils;
import org.bouncycastle.est.jcajce.JsseDefaultHostnameAuthorizer;
import org.bouncycastle.est.jcajce.JsseESTServiceBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
 * CaCertsExample gives examples of fetching CA certs.
 */
public class CaCertsExample
{

    public CaCertsExample(String[] args)
        throws Exception
    {

        if (args.length == 0)
        {
            printArguments();
            System.exit(1);
        }

        Security.addProvider(new BouncyCastleProvider());

        File trustAnchorFile = null;
        String serverRootUrl = null;
        boolean printTLSCerts = false;
        String tlsVersion = "TLS";
        String tlsProvider = "SunJSSE";
        String tlsProviderClass = null;
        boolean noNameVerifier = false;
        String label = null;
        int timeout = 0;
        String suffixList = null;
        try
        {
            for (int t = 0; t < args.length; t++)
            {
                String arg = args[t];
                if (arg.equals("-t"))
                {
                    trustAnchorFile = ExampleUtils.nextArgAsFile("Trust Anchor File", args, t);
                    t += 1;
                }
                else if (arg.equals("-u"))
                {
                    serverRootUrl = ExampleUtils.nextArgAsString("Server Hostname", args, t);
                    t += 1;
                }
                else if (arg.equals("--printTLS"))
                {
                    printTLSCerts = true;
                }
                else if (arg.equals("--tls"))
                {
                    tlsVersion = ExampleUtils.nextArgAsString("TLS version", args, t);
                    t += 1;
                }
                else if (arg.equals("--tlsProvider"))
                {
                    tlsProvider = ExampleUtils.nextArgAsString("TLS Provider", args, t);
                    t += 1;
                    tlsProviderClass = ExampleUtils.nextArgAsString("TLS Provider Class", args, t);
                    t += 1;
                }
                else if (arg.equals("--to"))
                {
                    timeout = ExampleUtils.nextArgAsInteger("Timeout", args, t);
                    t += 1;
                }
                else if (arg.equals("--no-name-verifier"))
                {
                    noNameVerifier = true;
                }
                else if (arg.equals("--label"))
                {
                    label = ExampleUtils.nextArgAsString("CA Label", args, t);
                    t += 1;
                }
                else if (arg.equals("--sl"))
                {
                    suffixList = ExampleUtils.nextArgAsString("Suffix List", args, t);
                    t += 1;
                }
                else
                {
                    throw new IllegalArgumentException("Unknown argument " + arg);
                }

            }
        }
        catch (IllegalArgumentException ilex)
        {
            System.err.println(ilex.getMessage());

            printArguments();

            System.exit(1);
        }

        if (serverRootUrl == null)
        {
            System.err.println("Server url (-u) must be defined.");
            System.exit(-1);
        }

        if (suffixList == null) 
        {
            System.err.println("Known Suffix List (--sl)  must be defined.");
            System.exit(-1);
        }

        //
        // Read the trust anchor.
        //
        Set<TrustAnchor> trustAnchors = null;
        if (trustAnchorFile != null)
        {
            trustAnchors = ExampleUtils.toTrustAnchor(ExampleUtils.readPemCertificates(trustAnchorFile));
        }

        if (tlsProviderClass != null)
        {
            Security.addProvider((Provider)Class.forName(tlsProviderClass).newInstance());
        }

        //SSLSocketFactoryCreatorBuilder sfcb = null;

        //
        // Make est client builder
        //
        X509TrustManager[] trustManagers = null;
        JsseESTServiceBuilder builder = null;
        if (trustAnchors != null && !trustAnchors.isEmpty())
        {
            trustManagers = JcaJceUtils.getCertPathTrustManager(trustAnchors, null);
        }
        else
        {
            // In this case we do not have trust anchors so create a builder for a client talking to an untrusted server.

            trustManagers = new X509TrustManager[]{JcaJceUtils.getTrustAllTrustManager()};
        }


        builder = new JsseESTServiceBuilder(serverRootUrl, trustManagers);

        if (noNameVerifier)
        {
            builder.withHostNameAuthorizer(null);
        }
        else
        {
            builder.withHostNameAuthorizer(new JsseDefaultHostnameAuthorizer(SuffixList.loadSuffixes(suffixList)));
        }


        builder.withTimeout(timeout);
        builder.withLabel(label);
        builder.withTLSVersion(tlsVersion);
        builder.withProvider(tlsProvider);
        //
        // Make a client.
        //

        ESTService estService = builder.build();

        CACertsResponse caCertsResponse = estService.getCACerts();
        //
        // We must check the response is trusted. If it is not trusted we have fetched the CAs
        // without verifying the source using a trust anchor. At this point an out of band 'ie user' must
        // accept the CA certs returned.
        // This is congruent with <https://tools.ietf.org/html/rfc7030#section-4.1.1> Bootstrapping.
        //

        Certificate[] certs = ((SSLSession)caCertsResponse.getSession()).getPeerCertificates();

        if (!caCertsResponse.isTrusted())
        {

            System.out.println();

            for (Certificate cert : certs)
            {

                //
                // Limited the amount of information for the sake of the example.
                // The default too string prints everything and is hard to follow.
                // It is at this point developers should present users with enough information to make an informed
                // decision.
                //

                X509Certificate x509 = (X509Certificate)cert;
                System.out.println("Subject: " + x509.getSubjectDN());
                System.out.println("Issuer: " + x509.getIssuerDN());
                System.out.println("Serial Number: " + x509.getSerialNumber());
                System.out.println("Not Before: " + x509.getNotBefore());
                System.out.println("Not After: " + x509.getNotAfter());
                System.out.println("Signature Algorithm: " + x509.getSigAlgName());

                System.out.println();
                System.out.println(cert.toString());


            }

            System.out.println("As part of the TLS handshake, the server tendered to us these certificates.");
            if (!ExampleUtils.userSaysYes("Do you accept these certificates (y,n) ?"))
            {
                System.exit(0);
            }

            System.out.println();
            System.out.println("The untrusted server tendered to us these certificates as CA certs");
            for (X509CertificateHolder holder : ESTService.storeToArray(caCertsResponse.getCertificateStore()))
            {

                //
                // Limited the amount of information for the sake of the example.
                // The default too string prints everything and is hard to follow.
                // It is at this point developers should present users with enough information to make an informed
                // decision.
                //

                System.out.println("Subject: " + holder.getSubject());
                System.out.println("Issuer: " + holder.getIssuer());
                System.out.println("Serial Number: " + holder.getSerialNumber());
                System.out.println("Not Before: " + holder.getNotBefore());
                System.out.println("Not After: " + holder.getNotAfter());
                System.out.println("Signature Algorithm: " + holder.getSignatureAlgorithm());
                System.out.println();
                System.out.println(ExampleUtils.toJavaX509Certificate(holder));

            }

            if (!ExampleUtils.userSaysYes("Do you accept these certificates (y,n) ?"))
            {
                System.exit(0);
            }
        }


        System.out.println("Fetched CA Certs:\n\n");

        for (X509CertificateHolder holder : ESTService.storeToArray(caCertsResponse.getCertificateStore()))
        {
            System.out.println(ExampleUtils.toPem(holder));
        }

        if (printTLSCerts)
        {
            System.out.println("\n TLS Certificates");
            System.out.println();
            for (Certificate cert : certs)
            {
                System.out.println(ExampleUtils.toPem(new X509CertificateHolder(cert.getEncoded())));
            }
        }

    }

    public static void main(String[] args)
        throws Exception
    {
        try
        {
            new CaCertsExample(args);

        }
        catch (Exception ex)
        {
            System.out.println("\n\n-----------------");
            System.out.println(ex.getMessage());
            System.out.println("-----------------\n\n");
            throw ex;
        }
    }

    public void printArguments()
    {
        System.out.println("-t <file>                         Trust anchor file. (PEM)");
        System.out.println("-u <url>                          Server Hostname.");
        System.out.println("--printTLS <url>                  Print TLS certificates as PEM format");
        System.out.println("--tls <version>                   Use this TLS version when creating socket factory, Eg TLSv1.2");
        System.out.println("--tlsProvider <provider> <class>  The JSSE Provider.");
        System.out.println("--to <milliseconds>               Timeout in milliseconds.");
        System.out.println("--no-name-verifier                No hostname verifier.");
        System.out.println("--label <ca label>                CA Label.");
        System.out.println("--sl <file>                       List of known suffixes.");
    }

}
