using System;
using System.Text;
using System.Diagnostics;
using CryptoSysPKI;

/* Demonstrates a one-off processing of a PFX file.
 * 1. Show we can read in private key just like we can from a PKCS#8 key file.
 * 2. Extract our X.509 certificate.
 * 3. Extract all certificates in the chain.
 * 4. Show details of all these certificates to use in XML-DSIG.
 * 5. Compose a <xades:Cert> element for a XADES file
 */


/******************************* LICENSE ***********************************
 * Copyright (C) 2019 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
 * The code in this module is licensed under the terms of the MIT license.  
 * For a copy, see <http://opensource.org/licenses/MIT>
****************************************************************************
*/

namespace ExtractFromPFX
{
    class ExtractFromPFX
    {
        static void ProcessPFX()
        {
            string pfxfile, pwd;
            string certfile, p7file;
            StringBuilder sbPriKey, sbPubKey;
            int r, nCerts, i;
            string fname, s, query;
            string template, xmlelement, hashalg;


            // Our PFX file (could also be .p12 = same thing)
            pfxfile = "enid.pfx";
            pwd = "password";   // Danger, Will Robinson!

            // 1. First show we can read in our private key directly from the PFX file 
            // (just like from a PKCS#8 key file)
            // We can use this "internal" private key string to, e.g., sign data.
            sbPriKey = Rsa.ReadPrivateKey(pfxfile, pwd);
            Console.WriteLine("Rsa.ReadPrivateKey() returns an internal string of length {0}", sbPriKey.Length);
            Debug.Assert(sbPriKey.Length > 0, "Failed to read private key from PFX");
            Console.WriteLine("Pri key bits = {0}", Rsa.KeyBits(sbPriKey.ToString()));

            // 2. Extract our X.509 certificate
            certfile = "mycert.cer";
            r = X509.GetCertFromPFX(certfile, pfxfile, pwd);
            Console.WriteLine("X509.GetCertFromPFX() returns {0} (expected +ve)", r);

            // 2a. Read in our public key from this certificate
            sbPubKey = Rsa.ReadPublicKey(certfile);
            Debug.Assert(sbPubKey.Length > 0, "Failed to read public key from certificate");
            Console.WriteLine("Pub key bits = {0}", Rsa.KeyBits(sbPubKey.ToString()));

            // 2b. Show these two keys are matched
            Console.WriteLine("Pri key hashcode = {0:X8}", Rsa.KeyHashCode(sbPriKey));
            Console.WriteLine("Pub key hashcode = {0:X8}", Rsa.KeyHashCode(sbPubKey));

            // 2c. alternatively...
            r = Rsa.KeyMatch(sbPriKey.ToString(), sbPubKey.ToString());
            Console.WriteLine("Rsa.KeyMatch() returns {0} (expected 0)", r);
            Debug.Assert(0 == r, "Keys do not match");

            // 3. Extract all certificates in the chain as a single P7 file
            // (strictly should be .p7c but .p7b works better in Windows)
            p7file = "certs.p7b";
            r = X509.GetP7ChainFromPFX(p7file, pfxfile, pwd);
            Console.WriteLine("X509.GetP7ChainFromPFX() returns {0} (expected +ve)", r);
            Debug.Assert(r > 0, "Failed to extract P7 file from PFX");

            // 3a. Validate the chain
            r = X509.ValidatePath(p7file);
            Console.WriteLine("X509.ValidatePath() returns {0} (expected 0)", r);
            Debug.Assert(r == 0, "Path validation failed");

            // 4. Extract certs individually from the P7 file
            //    and show details of these certificates.

            // How many certs are there?
            nCerts = (int)X509.GetCertFromP7Chain("", p7file, 0);
            Console.WriteLine("nCerts = {0}", nCerts);
            Debug.Assert(nCerts > 0, "No certs found in P7 file");

            // Enumerate through the certs
            for (i = 1; i <= nCerts; i++) {
                fname = "cert" + i + ".cer";
                Console.WriteLine("FILE: {0}", fname);
                r = X509.GetCertFromP7Chain(fname, p7file, i);
                Debug.Assert(r > 0);

                // 4a. Extract details from certificate in XML-DSIG-required form (LDAP, decimal)
                query = "subjectName";
                s = X509.QueryCert(fname, query, X509.OutputOpts.Ldap);
                Console.WriteLine("{0}: {1}", query, s);
                query = "issuerName";
                s = X509.QueryCert(fname, query, X509.OutputOpts.Ldap);
                Console.WriteLine("{0}: {1}", query, s);
                query = "serialNumber";
                s = X509.QueryCert(fname, query, X509.OutputOpts.Decimal);
                Console.WriteLine("{0}: {1}", query, s);

                // And compute its SHA-1 digest value ("thumbprint")
                s = X509.CertThumb(fname, HashAlgorithm.Sha1);
                Console.WriteLine("DigestValue (hex) = {0}", s);
                // We want the base64-encoded form for XML DigestValue elements
                Console.WriteLine("DigestValue (b64) = {0}", Cnv.ToBase64(Cnv.FromHex(s)));
            }

            // 5. Compose a <xades:Cert> element for a XADES file
            Console.WriteLine("FILE: {0}", certfile);
            template = @"<xades:Cert>
  <xades:CertDigest>
    <ds:DigestMethod Algorithm=""@!HASH-ALG!@"" />
    <ds:DigestValue>@!DIGEST-VALUE!@</ds:DigestValue>
  </xades:CertDigest>
    <xades:IssuerSerial>
      <ds:X509IssuerName>@!ISSUER-NAME!@</ds:X509IssuerName>
      <ds:X509SerialNumber>@!SERIAL-NUMBER!@</ds:X509SerialNumber>
  </xades:IssuerSerial>
</xades:Cert>";
            // Get element content in required form (LDAP, decimal) from cert then substitute in template.
            xmlelement = template;
            // 5a. Use SHA-1
            hashalg = "http://www.w3.org/2000/09/xmldsig#sha1";
            xmlelement = xmlelement.Replace("@!HASH-ALG!@", hashalg);
            s = X509.CertThumb(certfile, HashAlgorithm.Sha1);
            s = Cnv.ToBase64(Cnv.FromHex(s));
            xmlelement = xmlelement.Replace("@!DIGEST-VALUE!@", s);
            s = X509.QueryCert(certfile, "issuerName", X509.OutputOpts.Ldap);
            xmlelement = xmlelement.Replace("@!ISSUER-NAME!@", s);
            s = X509.QueryCert(certfile, "serialNumber", X509.OutputOpts.Decimal);
            xmlelement = xmlelement.Replace("@!SERIAL-NUMBER!@", s);
            Console.WriteLine(xmlelement);

            // 5b. Repeat using SHA-256
            xmlelement = template;
            hashalg = "http://www.w3.org/2001/04/xmlenc#sha256";    // CHANGED
            xmlelement = xmlelement.Replace("@!HASH-ALG!@", hashalg);
            s = X509.CertThumb(certfile, HashAlgorithm.Sha256);     // CHANGED
            s = Cnv.ToBase64(Cnv.FromHex(s));
            xmlelement = xmlelement.Replace("@!DIGEST-VALUE!@", s);
            s = X509.QueryCert(certfile, "issuerName", X509.OutputOpts.Ldap);
            xmlelement = xmlelement.Replace("@!ISSUER-NAME!@", s);
            s = X509.QueryCert(certfile, "serialNumber", X509.OutputOpts.Decimal);
            xmlelement = xmlelement.Replace("@!SERIAL-NUMBER!@", s);
            Console.WriteLine(xmlelement);
        }

        static void Main(string[] args)
        {
            ProcessPFX();
        }
    }
}