// $Id: GermanHealthExamples.cs $

// Examples using CryptoSys PKI to create and read signed-and-enveloped PKCS7 (CMS) objects suitable for
// the Security interface for data exchange in the health service version 3.0 (as far as we know).
//   Last Updated:
//   $Date: 2014-09-08 17:37:00 $

/*******************************************************************************
' Copyright (c) 2014 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.
' *****************************************************************************/

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

namespace GermanHealthExamples
{
    class GermanHealthExamples
    {
        static void Main(string[] args)
        {
            int ver = General.Version();
            Console.WriteLine("PKI Version={0}", ver);
            if (ver < 31000)
            {
                Console.WriteLine("Require CryptoSys PKI version 3.10.0 or greater");
                return;
            }
            // 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 "maketest":
                    maketest();
                    break;
                case "readtest":
                    readtest();
                    break;

                default:
                    usage();
                    break;
            }
        }

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

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

        /// <summary>
        /// Make a test signed-and-enveloped-data object
        /// </summary>
        /// <returns></returns>
        static bool maketest()
        {
            // The message we want to send
            string msg = "Hallo Walt";
            // Final enveloped-data file to send to recipient
            string outputFile = "To_999009051a.p7m";
            // Our private key data
            string priKeyFile = "999009991a_pri.p8";
            StringBuilder sbPassword = new StringBuilder("password");
            // Signer's certificate plus (optionally) the certs in the chain that signed it. 
            // Separated by a semi-colon ";".
            // NOTE: the first cert in the list MUST be the signers
            // -- it's up to you to work out which is which (see Check_CertList_With_PrivateKey())
            string signersCertList = "999009991a.cer" + ";" + "Int_Cert.cer" + ";" + "CA_Cert.cer";
            // The certificate of the recipient -- this must be provided 
            // (otherwise we wouldn't know whom to send it to)
            string recipCertFile = "999009051a.cer";

            // Display input
            Console.WriteLine("Message to be signed and enveloped='" + msg + "'");

            bool isok = Make_Signed_And_EnvelopedData(outputFile, msg, priKeyFile, sbPassword.ToString(), 
                signersCertList, recipCertFile, true);

            if (isok)
                Console.WriteLine("OK, created output file '" + outputFile + "'");
            else
                Console.WriteLine("FAILED!");

            Wipe.String(sbPassword);

            return true;
        }

        /// <summary>
        /// Read a test signed-and-enveloped-data file
        /// </summary>
        /// <remarks>
        /// NOTE: we can only do this because we have the private key for the dummy user with id IK999009051,
        /// which, of course, you would not normally have.
        /// To test yourself, send a test message to yourself signed by yourself.
        /// </remarks>
        /// <returns></returns>
        static bool readtest()
        {
            string inputFile = "To_999009051a.p7m";
            // Recipient's private key data
            string priKeyFile = "999009051a_pri.p8";
            StringBuilder sbPassword = new StringBuilder("password");

            // Display input
            Console.WriteLine("Reading input file '" + inputFile + "'");

            string msg = Read_Signed_and_Enveloped_Data(inputFile, priKeyFile, sbPassword.ToString(), true);

            Console.WriteLine("Result='" + msg + "'");

            // Clean up password
            Wipe.String(sbPassword);

            return true;
        }

        /*********************/
        /* GENERIC FUNCTIONS */
        /*********************/

        /// <summary>
        /// Creates a PKCS#7 signed-and-enveloped-data object
        /// </summary>
        /// <param name="outputFile">File to be created</param>
        /// <param name="msg">Message to be sent</param>
        /// <param name="priKeyFile">Sender's encrypted key file</param>
        /// <param name="password">Password for key file</param>
        /// <param name="signersCertList">List of signers' certificates, sender's first</param>
        /// <param name="recipCertFile">Filename of recipient's certificate</param>
        /// <param name="keepInterFile">True to keep intermediate file</param>
        /// <returns>True if successful; otherwise false</returns>
        static bool Make_Signed_And_EnvelopedData(string outputFile, string msg, string priKeyFile, string password,
            string signersCertList, string recipCertFile, bool keepInterFile)
        {
            int n;

            // Intermediate signed-data file we will create
            string sigFile = outputFile + ".int.tmp";

            // 1. Read in the secret private key to a StringBuilder
            Console.WriteLine("Reading private key from PRI file...");
            StringBuilder sbPriKey = Rsa.ReadEncPrivateKey(priKeyFile, password);
            // Check for success
            if (sbPriKey.Length == 0)
            {
                Console.WriteLine("ERROR: Cannot read private key");
                return false;
            }
            else
            {
                Console.WriteLine("...OK, read in private key: key length=" + Rsa.KeyBits(sbPriKey) + " bits");
            }
            // 2. Create a signed-data object with signed attributes and signing-time and all certificates.
            // using SHA-256 for the signature
            n = Cms.MakeSigDataFromString(sigFile, msg, signersCertList, sbPriKey.ToString(), HashAlgorithm.Sha256,
                Cms.SigDataOptions.IncludeAttributes | Cms.SigDataOptions.AddSignTime);
            Console.WriteLine("CMS_MakeSigDataFromString returns " + n + " (expecting 0)");

            // Clean up as we go
            Wipe.String(sbPriKey);
            // Check for success
            if (n != 0)
            {
                display_error(n);
                return false;
            }
            else
            {
                Console.WriteLine("OK, created signed-data file '" + sigFile + "'");
            }

            // 3. Encrypt the signed-data object directly using the recipient's certificate
            //    -- this produces a binary enveloped-data file
            //    -- Use Triple DES (Tdea) for now. In 2016 (or thereabouts) change to Aes256
            n = Cms.MakeEnvData(outputFile, sigFile, recipCertFile, CipherAlgorithm.Tdea, 0);

            // Clean up sensitive data
            if (!keepInterFile)
            {
                Wipe.File(sigFile);
            }
            // Check for success (NB expecting # of recipients, not zreo)
            if (n <= 0)
            {
                display_error(n);
                return false;
            }
            else
            {
                Console.WriteLine("OK, created enveloped-data file '" + outputFile + "'");
            }

            // Now send the output file to the recipient...
            return true;
        }

        /// <summary>
        /// Read a signed-and-enveloped-data object using recipient's private key
        /// </summary>
        /// <param name="inputFile">signed-and-enveloped-data object file</param>
        /// <param name="priKeyFile">recipient's encrypted private key file</param>
        /// <param name="password">password for private key</param>
        /// <returns>String containing extracted message or empty string on error</returns>
        static string Read_Signed_and_Enveloped_Data(string inputFile, string priKeyFile, 
            string password, bool keepInterFile)
        {
            string msg = "";
            int n;
            string s, query;

            // 1. Read in the recipient's secret private key to a StringBuilder
            Console.WriteLine("Reading private key from PRI file...");
            StringBuilder sbPriKey = Rsa.ReadEncPrivateKey(priKeyFile, password);
            // Check for success
            if (sbPriKey.Length == 0)
            {
                Console.WriteLine("ERROR: Cannot read private key");
                return "";
            }
            else
            {
                Console.WriteLine("...OK, read in private key: key length=" + Rsa.KeyBits(sbPriKey) + " bits");
            }

            // Intermediate file we will create
            string sigFile = inputFile + ".i2.tmp";

            // 2. Read the encrypted data from the enveloped-data file
            n = Cms.ReadEnvDataToFile(sigFile, inputFile, "", sbPriKey.ToString(), 0);
            Console.WriteLine("Cms.ReadEnvDataToFile returns " + n + " (expected 0)");

            // Check for success
            if (n != 0)
            {
                display_error(n);
                return "";
            }
            else
            {
                Console.WriteLine("Extracted signed-data file '" + sigFile + "'");
            }

            // 2a. While we're here, get some info from the signed-data file
            Console.WriteLine("For signed-data file: '" + sigFile + "'");
            s = Cms.QuerySigData(sigFile, "version");
            Console.WriteLine("  Version=" + s);
            query = "signingTime";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "messageDigest";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "digestAlgorithm";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "countOfSignerInfos";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "CountOfCertificates";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            // 2b. And verify the signed data
            n = Cms.VerifySigData(sigFile);
            Console.WriteLine("Cms.VerifySigData returns " + n + " (expected 0)");

            // 3. Extract the content
            msg = Cms.ReadSigDataToString(sigFile);

            // Clean up sensitive data
            if (!keepInterFile)
            {
                Wipe.File(sigFile);
            }

            // Check for success
            if (msg.Length == 0)
            {
                display_error(General.ErrorCode());
                return "";
            }

            return msg;
        }
    }
}