// $Id: GermanHealthSetup2019.cs $

// You should only need to do these procedures once. Updated for 2018-19 changes.
//   Last Updated:
//   $Date: 2019-06-26 08:11:00 $

/********************************************************************************
 * Copyright (c) 2014-19 DI Management Services Pty Limited. All rights reserved.
 * Provided to illustrate the use of functions in the CryptoSys PKI Toolkit.
 * Not necessarily a good example of secure programming techniques.
 * Provided "as is" with no warranties. Use at your own risk.
 * The code in this module is licensed under the terms of the MIT license.  
 * For a copy, see <http://opensource.org/licenses/MIT>
 ********************************************************************************
 */

/* Changes from 2014 version:
 * --------------------------
 * New RSA keys are generated with 4096 bits, up from 2048.
 * Uses stronger encryption (AES-128/SHA-256) when encrypted private key is created.
 * Uses RSA-PSS-SHA256 to sign PKSC#10 certificate request.
 * Updated deprecated options and methods, e.g. Cms.Options.
 */


using System;
using System.Text;
using System.IO;
using CryptoSysPKI;

namespace GermanHealthSetup
{
    class GermanHealthSetup
    {
        private const string YOUR_PKID = "999009999";

        static void Main(string[] args)
        {
            int ver = General.Version();
            Console.WriteLine("PKI Version={0}", ver);
            if (ver < 120200)
            {
                Console.WriteLine("Require CryptoSys PKI version 12.2.0 or greater");
                return;
            }
            //Console.WriteLine("Number of command line parameters = {0}", args.Length);
            //foreach (string s in args)
            //{
            //    System.Console.WriteLine(s);
            //}

            // Expecting at least one argument
            if (args.Length < 1) usage();
            Console.WriteLine("Doing " + args[0] + "...");
 
            // Act on first command-line argument
            switch (args[0])
            {
                case "makekeys":
                    makekeys();
                    break;
                case "makecertreq":
                    makecertreq();
                    break;
                case "makedigest":
                    makedigest();
                    break;
                case "makecert":
                    makecert();
                    break;
                case "makecertchain":
                    makecertchain();
                    break;
                case "splitp7c":
                    splitp7c();
                    break;
                case "findourcert":
                    findourcert();
                    break;
                case "validatep7c":
                    validatep7c();
                    break;

                default:
                    usage();
                    break;
            }
        }

        static void usage()
        {
            Console.WriteLine("ERROR: Expecting command: 'makekeys', 'makecertreq',...");
            System.Environment.Exit(1);
        }

        static void display_error(int code)
        {
            Console.WriteLine("ERROR: " + General.ErrorLookup(code) + ": " + General.LastError());
        }

        /// <summary>
        /// Create a new pair of 4096-bit RSA private/public keys saved as binary BER-encoded files
        /// </summary>
        /// <returns></returns>
        static bool makekeys()
        {
            // Do this just once.

            // NOTE: each time you run this you destroy the earlier keys
            // and need to run all subsequent procedures again
            
            // Set filenames to be created
            string pubKeyFile = YOUR_PKID + "_pub.p1";
            string priKeyFile = YOUR_PKID + "_pri.p8";
            // Set your password - use something stronger!
            // DO NOT HARDCODE PRODUCTION PASSWORDS!
            string password = "password";

            // Create a new pair of RSA keys
            int n = Rsa.MakeKeys(pubKeyFile, priKeyFile, 4096, Rsa.PublicExponent.Exp_EQ_65537, 5000, password, CipherAlgorithm.Aes128, HashAlgorithm.Sha256, Rsa.Format.Binary, true);
            if (n == 0)
                Console.WriteLine("Created files: '" + pubKeyFile + "' and '" + priKeyFile + "'");
            else
                display_error(n);
            return (0 == n);
        }

        /// <summary>
        /// Create a Certificate Request file (CRS, PKCS#10, .p10 file)
        /// </summary>
        /// <returns></returns>
        static bool makecertreq()
        {
            // Name of file to be created
            string reqFile = YOUR_PKID + ".p10";
            // Existing key file and password 
            // DO NOT HARDCODE PRODUCTION PASSWORDS!
            string priKeyFile = YOUR_PKID + "_pri.p8";
            string password = "password";
            // Distinguished name, in correct order
            string distName = "C=DE;O=ISTG Beispiel Vertrauen Mitte;OU=Unsere Firma;OU=IK999009991;CN=Erika Mustermann";

            // CHANGED [2019-06-26] - use RSA-PSS instead of RSA-SHA256
            // Create certificate request, signing with Rsa_Pss_Sha256
            int n = X509.CertRequest(reqFile, priKeyFile, distName, "", password, SigAlgorithm.Rsa_Pss_Sha256, X509.CsrOptions.FormatBinary);
            if (n == 0)
                Console.WriteLine("Created file '" + reqFile + "'");
            else
                display_error(n);
            return (0 == n);
        }

        /// <summary>
        /// Compute the SHA-1 message digest value of the public key
        /// </summary>
        /// <returns></returns>
        static bool makedigest()
        {
            // Compute the SHA-1 digest of the public key file created earlier
            string pubKeyFile = YOUR_PKID + "_pub.p1";
            string digestHex = Hash.HexFromFile(pubKeyFile, HashAlgorithm.Sha1);

            if (digestHex.Length == 0) display_error(General.ErrorCode());
            Console.WriteLine("SHA1(PublicKey)=" + digestHex);
            return (digestHex.Length > 0);
        }

        /// <summary>
        /// Create an X.509 certificate from the p10 file sent by the user signed by the intermediate CA
        /// </summary>
        /// <remarks>You cannot do this. It is done by the CA using its own private key. 
        /// We do it here to get a dummy certificate in the correct format to use</remarks>
        /// <returns></returns>
        static bool makecert()
        {
            // Cert file to be created
            string certFile = YOUR_PKID + ".cer";
            // Input
            string reqFile = YOUR_PKID + ".p10";
            string issuerCert = "Int_Cert.cer";
            int certNum = 0x9999;
            // This is the CA's keyfile password, which you don't have
            string issuerKey = "Int_pri.p8";    
            string password = "password";

            // Create new cert from certificate request file signed using sha256rsa
            int n = X509.MakeCert(certFile, issuerCert, reqFile, issuerKey, certNum, 7, "", "", 0, password, X509.Options.SigAlg_Sha256WithRSAEncryption);
            if (n == 0)
                Console.WriteLine("Created file '" + certFile + "'");
            else
                display_error(n);
            return (0 == n);
        }

        /// <summary>
        /// Make a p7c "certs-only" signed-data chain file for user
        /// </summary>
        /// <remarks>This is done by the CA, not by you. 
        /// We do it here to create a dummy file in the correct format you would receive from the CA.
        /// </remarks>
        /// <returns></returns>
        static bool makecertchain()
        {
            string outFile = YOUR_PKID + ".p7c";
            string certList = YOUR_PKID + ".cer" + ";" + "Int_Cert.cer" + ";" + "CA_Cert.cer";
            Console.WriteLine("certList=" + certList);

            // Create a certs-only .p7c chain
            int n = Cms.MakeSigData(outFile, "", certList, "", Cms.SigAlg.Default, Cms.SigDataOptions.CertsOnly);
            if (n == 0)
                Console.WriteLine("Created file '" + outFile + "'");
            else
                display_error(n);
            return (0 == n);
        }

        /// <summary>
        /// Split the p7c cert list file into separate X.509 certificates
        /// </summary>
        /// <returns></returns>
        static bool splitp7c()
        {
            string listFile = YOUR_PKID + ".p7c";
            string certFile;

            // How many certificates?
            int nCerts = X509.GetCertCountInP7Chain(listFile);
            Console.WriteLine("X509.GetCertCountInP7Chain() returns " + nCerts);
            // Enumerate through them all
            if (nCerts > 0)
            {
                for (int iCert = 1; iCert <= nCerts; iCert++)
                {   // NB 1..n not 0..n-1
                    certFile = "TheCert" + iCert + ".cer";
                    int n = X509.GetCertFromP7Chain(certFile, listFile, iCert);
                    if (n < 0) display_error(n);
                    Console.WriteLine("Cert(" + iCert + ")->" + certFile);
                }
            }
            // But we don't know which one is ours...so see the next procedure
            // (it's most likely that the first one is yours, but we'll check anyway)
            return (nCerts > 0);
        }

        /// <summary>
        /// Find and extract our own certificate in the .p7c file
        /// </summary>
        /// <returns></returns>
        static bool findourcert()
        {
            // Your new certificate file to be copied
            string newCert = YOUR_PKID + ".cer";
            // Input file
            string listFile = YOUR_PKID + ".p7c";
            // Your own private key and password
            // Did we mention? DO NOT HARDCODE PRODUCTION PASSWORDS!
            string priKeyFile = YOUR_PKID + "_pri.p8";
            string password = "password";

            int nCerts, iCert;
            string certFile;
            int n, nLen;
            StringBuilder sbPriKey, sbPubKey;
            string matchingCert;

            // 1. Extract X.509 certificates from P7 cert list...
            // How many certificates?
            nCerts = X509.GetCertCountInP7Chain(listFile);
            Console.WriteLine("X509.GetCertCountInP7Chain() returns " + nCerts);
            // Enumerate through them all
            if (nCerts > 0)
            {
                for (iCert = 1; iCert <= nCerts; iCert++)
                {   // NB 1..n not 0..n-1
                    certFile = "TheCert" + iCert + ".cer";
                    n = X509.GetCertFromP7Chain(certFile, listFile, iCert);
                    if (n < 0) display_error(n);
                    Console.WriteLine("Cert(" + iCert + ")->" + certFile);
                }
            }
            else
            {
                Console.WriteLine("ERROR: no certificate extracted");
                return false;
            }

            // 2. Read in private key from encrypted file...
            sbPriKey = Rsa.ReadPrivateKey(priKeyFile, password);
            if (sbPriKey.Length == 0)
            {
                display_error(General.ErrorCode());
                return false;
            }
            // Display some details about our private key
            Console.WriteLine("Private key is " + Rsa.KeyBits(sbPriKey) + " bits");
            Console.WriteLine("Private key HashCode = 0x{0:X8}", Rsa.KeyHashCode(sbPriKey));

            // 3. Test each certificate in the list against our private key...
            nLen = Rsa.KeyBytes(sbPriKey);
            matchingCert = "";
            for (iCert = 1; iCert <= nCerts; iCert++)
            {
                certFile = "TheCert" + iCert + ".cer";
                Console.WriteLine("For certificate " + certFile + "...");
                // Read in the public key from the certificate
                sbPubKey = Rsa.ReadPublicKey(certFile);
                if (sbPubKey.Length == 0)
                {
                    Console.WriteLine("ERROR: cannot read public key from cert '" + certFile + "'");
                    return false;
                }
                Console.WriteLine("  This public key is " + Rsa.KeyBits(sbPubKey) + " bits long");
                Console.WriteLine("  Public key HashCode = 0x{0:X8}", Rsa.KeyHashCode(sbPubKey));
                // Does this match our own private key?
                n = Rsa.KeyMatch(sbPriKey, sbPubKey);
                if (0 == n)
                {
                    Console.WriteLine("  FOUND MATCH: private key in '" + priKeyFile +
                        "' matches public key in '" + certFile + "'");
                    // We are done, so we could break the loop here, but we don't
                    // to demonstrate what happens with the other certs that don't match
                    matchingCert = certFile;
                }
                else
                {
                    Console.WriteLine("  Private and public keys do not match.");
                }
            }
            Console.WriteLine("Compared all public keys.");

            // 4. Copy our cert if we found it
            if (matchingCert.Length > 0)
            {
                Console.WriteLine("Found a match for '" + matchingCert + "'");
                // Copy the file we want, overwriting any existing file
                File.Copy(matchingCert, newCert, true);
                Console.WriteLine("Copied matching cert to '" + newCert + "'");
            }
            else
            {
                Console.WriteLine("Did not find a match for the private key.");
            }
            // Clean up
            Wipe.String(sbPriKey);

            return (matchingCert.Length > 0);
        }

        /// <summary>
        /// Validate the certs in the p7c chain
        /// </summary>
        /// <returns></returns>
        static bool validatep7c()
        {
            // Input p7c chain file
            string p7cFile = YOUR_PKID + ".p7c";

            // The trusted self-signed CA cert and its known SHA-1 thumbprint
            // -- YOU WILL NEED TO CHANGE THESE
            string trustedCert = "CA_Cert.cer";
            string trustedThumb = "3867c2c9072362fa2565365b1e82b639a42f8220";

            string thumb;
            int n;

            // 1. Does the trusted cert match its known thumbprint?
            thumb = X509.CertThumb(trustedCert, HashAlgorithm.Sha1);
            if (String.Compare(thumb, trustedThumb, true) == 0)
            {
                Console.WriteLine("OK, trusted certifcate has expected thumbprint.");
            }
            else
            {
                Console.WriteLine("ERROR: thumbprint of trusted cert does not match");
                return false;
            }

            // 2. Use ValidatePath to check that the certificate path is valid
            //    with the trusted cert as its ultimate parent
            n = X509.ValidatePath(p7cFile, trustedCert, false);

            if (n == 0)
                Console.WriteLine("OK, certification path is valid");
            else if (n == 1)
                Console.WriteLine("ERROR: certification path is invalid");
            else
                display_error(n);
            return (0 == n);
        }

    }
}