/* $Id: GermanHealthExamples2018.cs $
 *   Last Updated:
 *   $Date: 2018-05-05 18:05:00 $
 */

/*
 * 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 [2018].
 * Ref: "Security Schnittstelle (SECON) Anlage 16"
 * Security interface (SECON) Annex 16, Valid from: 01.01.2018 Status: 26.10.2017.
 */

/********************************************************************************
 * Copyright (c) 2014-18 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:
 * --------------------------
 * Content encryption algorithm changed from Triple-DES to AES-256.
 * Signature algorithm changed to RSA-PSS-SHA256 with mgf1SHA256.
 * Key encryption algorithm changed to RSA-OAEP with SHA-256 and mgf1SHA256.
 * Test RSA keys are 4096 bits.
 * Test certificates are signed using RSA-PSS-SHA256.
 * For reference, "RSA-PSS" == "RSASSA-PSS" and "RSA-OAEP" == "RSAES-OAEP".
 */

/* Required files in current working directory
 * -------------------------------------------
 * 999009051b.cer
 * 999009051b_pri.p8e
 * 999009991b.cer
 * 999009991b_pri.p8e
 * CA_4096_Cert.cer
 * Int_4096_Cert.cer
 */

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 < 110300)
            {
                Console.WriteLine("Require CryptoSys PKI version 11.3.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 argument: maketest|readtest");
            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_999009051b.p7m";
            // Our private key data
            string priKeyFile = "999009991b_pri.p8e";
            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 = "999009991b.cer" + ";" + "Int_4096_Cert.cer" + ";" + "CA_4096_Cert.cer";
            // The certificate of the recipient -- this must be provided 
            // (otherwise we wouldn't know whom to send it to)
            string recipCertFile = "999009051b.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_999009051b.p7m";
            // Recipient's private key data
            string priKeyFile = "999009051b_pri.p8e";
            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.ReadPrivateKey(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 RSA-PSS-SHA256 for the signature
            n = Cms.MakeSigDataFromString(sigFile, msg, signersCertList, sbPriKey.ToString(), Cms.SigAlg.Rsa_Pss_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
            // using RSA-OAEP with SHA-256 for the key-encryption algorithm
            //    -- this produces a binary enveloped-data file
            n = Cms.MakeEnvData(outputFile, sigFile, recipCertFile, CipherAlgorithm.Aes256, Cms.KeyEncrAlgorithm.Rsa_Oaep, HashAlgorithm.Sha256, 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.ReadPrivateKey(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");
            }

            // 1a. While we're here, check the form of the enveloped-data object we received
            Console.WriteLine("For enveloped-data file: '" + inputFile + "'");
            query = "keyEncryptionAlgorithm";
            s = Cms.QueryEnvData(inputFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "oaepParams";
            s = Cms.QueryEnvData(inputFile, query);
            Console.WriteLine("  " + query + "=" + s);

            // 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 = "signatureAlgorithm";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "digestAlgorithm";
            s = Cms.QuerySigData(sigFile, query);
            Console.WriteLine("  " + query + "=" + s);
            query = "pssParams";
            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;
        }
    }
}