using System;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Text;

using CryptoSysPKI;

/*  $Id: TestPKIcsharpV12.cs $ 
 *   Last updated:
 *   $Date: 2020-03-6 09:13 $
 *   $Version: 12.3.0 $
 */

/* Some tests demonstrating new features in CryptoSys PKI v12.1 
 * in particular RSA-PSS and ECC signatures in X.509 certificates.
 * 
 * Test files are in `pkiDotNetTestFiles.zip`. These must be in the CWD.
 */

/******************************* LICENSE ***********************************
 * Copyright (C) 2018-20 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 TestPKIcsharpV12
{
    class TestPKIcsharpV12
    {
        private const int MIN_PKI_VERSION = 120100;
        // Test files required to exist in the current working directory:
        private static string[] arrFileNames = new string[] 
		{ 
            "AliceRSAPSS.p8",   /* Unencrypted version */
            "AliceRSAPSS.p8e",  /* Encrypted, password="password" */
            "AliceRSAPssSignByCarl.cer",
            "BobRSAPSS.p8e",
            "BobRSAPssSignByCarl.cer",
            "CarlRSAPssSelf.cer",
            "CA_ECC_P256.p8e",
            "CA_ECC_P256.pub",
            "CA_RSA_2048.p8e",
            "CA_RSA_2048.pub",
            "excontent.txt",
            "rsa-oaep-1.p8",
            "rsa-oaep-1.pub",
            "User_ECC_P256.p8e",
            "User_ECC_P256.pub",
            "User_RSA_2048.p8e",
            "User_RSA_2048.pub",
            "User_RSA_2048.cer",
            "User2_ECC_P256.p8e",
            "User2_ECC_P256.pub",
            "User2_RSA_2048.p8e",
            "User2_RSA_2048.pub",
        };
        // Name of file with zipped test files
        private static string zippedTestFiles = "pkiDotNetTestFiles.zip";

        static void Main(string[] args)
        {
            Console.WriteLine("TESTING CRYPTOSYS PKI FOR CHANGES IN V12.0 AND V12.1");

            // Make sure minimum required version of CryptoSys PKI is installed...
            Console.WriteLine("PKI Version is " + General.Version());
            if (General.Version() < MIN_PKI_VERSION) {
                Console.WriteLine("FATAL ERROR: Require PKI version " + MIN_PKI_VERSION + " or later.");
                return;
            }

            // Deal with command-line options
            bool doSome = false;
            bool askDelete = false;
            for (int iarg = 0; iarg < args.Length; iarg++) {
                if (args[iarg] == "some")
                    doSome = true;
               if (args[iarg] == "askdelete")
                    askDelete = true;
            }

            // Check required files exist and setup temp directory
            string origdir = SetupTestFilesAndDirectory(arrFileNames);
            if (null == origdir) return;


            //*************
            // DO THE TESTS
            //*************
            if (doSome) // Use "some" in the command line
			{   // Do some tests - comment these out as required
                /* //test_MakeKeys();  // Only do this once! */
                //test_ReadKeys();
                //test_MakeCerts_ECDSA();
                //test_MakeCerts_PSS();
                //test_SIG_VerifyData_PSS();
                //test_RSA_EncryptDecrypt();
                //test_SIG_SignData_PSS();
                //test_SIG_SignData_ECDSA();
                //test_RSA_ToXMLStringEx();
                //test_CMS_MakeSigData_PSS();
                //test_MakeSignedEnveloped_PSS_OAEP();
                //test_PFX_MakeFile_PSS();
                /* NEW IN [v12.1] */
                test_CIPHER_EncryptAEAD();
                //test_X509_ReadCertFromP7Chain();
                //test_X509_ReadCertFromPFX();
            } 
            else {   // Do all the test modules (default)
                DoAllTests();
            }


            // FINALLY, DISPLAY QUICK INFO ABOUT THE CORE DLL
            Console.WriteLine("\nDETAILS OF CORE DLL...");
            Console.WriteLine("DLL Version={0} [{1}] Lic={2} Compiled=[{3}] ",
                General.Version(), General.Platform(), General.LicenceType(), General.CompileTime());
            Console.WriteLine("[{0}]", General.ModuleName());


            //********************************************
            Console.WriteLine("\nALL TESTS COMPLETED.");

            // Put CWD back to original and offer to remove the test dir
            RestoreDirectory(origdir, askDelete);
        }
        static void DoAllTests()
        {
            /* //test_MakeKeys();  // Only do this once! */
            test_ReadKeys();
            test_MakeCerts_ECDSA();
            test_MakeCerts_PSS();
            test_SIG_VerifyData_PSS();
            test_RSA_EncryptDecrypt();
            test_SIG_SignData_PSS();
            test_SIG_SignData_ECDSA();
            test_RSA_ToXMLStringEx();
            test_CMS_MakeSigData_PSS();
            test_MakeSignedEnveloped_PSS_OAEP();
            test_PFX_MakeFile_PSS();
            // New in [v12.1]
            test_CIPHER_EncryptAEAD();
            test_X509_ReadCertFromP7Chain();
            test_X509_ReadCertFromPFX();
        }


        //********************
        // THE TEST MODULES...
        //********************


        static void test_MakeKeys()
        {
            /* Demonstrates:
             * Rsa.MakeKeys()
             * Ecc.MakeKeys()
             */
            int r;
            Console.WriteLine("Generating two 2048-bit RSA keys...");
            r = Rsa.MakeKeys("CA_RSA_2048.pub", "CA_RSA_2048.p8e", 2048, Rsa.PublicExponent.Exp_EQ_65537, 80, "password", Rsa.PbeOptions.Pbe_Pbkdf2_aes256_CBC, false);
            r = Rsa.MakeKeys("User_RSA_2048.pub", "User_RSA_2048.p8e", 2048, Rsa.PublicExponent.Exp_EQ_65537, 80, "password", Rsa.PbeOptions.Pbe_Pbkdf2_aes256_CBC, false);

            Console.WriteLine("Generating two ECC keys using P-256...");
            r = Ecc.MakeKeys("CA_ECC_P256.pub", "CA_ECC_P256.p8e", Ecc.CurveName.P_256, "password", Ecc.PbeScheme.Pbe_Pbkdf2_aes256_CBC, "", 0);
            r = Ecc.MakeKeys("User_ECC_P256.pub", "User_ECC_P256.p8e", Ecc.CurveName.P_256, "password", Ecc.PbeScheme.Pbe_Pbkdf2_aes256_CBC, "", 0);
        }

        static void test_ReadKeys()
        {
            Console.WriteLine("\nShow we can read the key files we made...");

            /* Demonstrates:
             * Rsa.ReadPrivateKey()
             * Rsa.ReadPublicKey()
             * Rsa.KeyBits()
             * Rsa.KeyHashCode()
             * Rsa.KeyMatch()
             * Ecc.ReadPrivateKey()
             * Ecc.ReadPublicKey()
             * Ecc.QueryKey()
             * Ecc.KeyHashCode()
             */

            StringBuilder sbPriKey;
            string pubkey;
            int r;

            // RSA keys:
            sbPriKey = Rsa.ReadPrivateKey("CA_RSA_2048.p8e", "password");
            Debug.Assert(sbPriKey.Length > 0, "Failed to read CA_RSA_2048 private key");
            Console.WriteLine("CA_RSA pri key length={0}", Rsa.KeyBits(sbPriKey.ToString()));
            Console.WriteLine("CA_RSA pri key hash code={0,8:X}", Rsa.KeyHashCode(sbPriKey.ToString()));
            pubkey = Rsa.ReadPublicKey("CA_RSA_2048.pub").ToString();
            Debug.Assert(pubkey.Length > 0, "Failed to read CA_RSA_2048 public key");
            Console.WriteLine("CA_RSA pub key length={0}", Rsa.KeyBits(pubkey));
            Console.WriteLine("CA_RSA pub key hash code={0,8:X}", Rsa.KeyHashCode(pubkey));
            // Check they match
            r = Rsa.KeyMatch(sbPriKey.ToString(), pubkey);
            Console.WriteLine("Rsa.KeyMatch() returns {0} (expected 0)", r);
            Debug.Assert(0 == r);

            // ECC keys:
            sbPriKey = Ecc.ReadPrivateKey("CA_ECC_P256.p8e", "password");
            Debug.Assert(sbPriKey.Length > 0, "Failed to read CA_ECC_P256 private key");
            Console.WriteLine("CA_ECC pri key length={0}", Ecc.QueryKey(sbPriKey.ToString(), "keyBits"));
            Console.WriteLine("CA_ECC pri key hash code={0,8:X}", Ecc.KeyHashCode(sbPriKey.ToString()));
            pubkey = Ecc.ReadPublicKey("CA_ECC_P256.pub").ToString();
            Debug.Assert(pubkey.Length > 0, "Failed to read CA_ECC_P256 public key");
            Console.WriteLine("CA_ECC pub key length={0}", Ecc.QueryKey(pubkey, "keyBits"));
            Console.WriteLine("CA_ECC pub key hash code={0,8:X}", Ecc.KeyHashCode(pubkey));
        }

        static void test_MakeCerts_ECDSA()
        {
            Console.WriteLine("\nCreate a new CA certificate and end-user certificates using ECDSA...");

            /* Demonstrates:
             * X509.MakeCertSelf() using SigAlgorithm.Ecdsa_Sha256
             * X509.MakeCert() using SigAlgorithm.Ecdsa_Sha256 and X509.CertOptions.Ecdsa_Deterministic
             * X509.CertRequest() using SigAlgorithm.Ecdsa_Sha256
             * X509.VerifyCert() for an X.509 certificate
             * X509.VerifyCert() for a PKCS#10 CSR
             * X509.VerifyCert() for an X.509 CRL
             * X509.MakeCRL() using SigAlgorithm.Ecdsa_Sha256
             * X509.CheckCertInCRL()
             * X509.TextDumpToString() using X509.OutputOpts.Ldap
             * Asn1.TextDumpToString()
             * Ecc.ReadPublicKey()
             */

            int r;
            X509.KeyUsageOptions kuFlags;
            string dn, extns;
            string s, query, pubkey;

            // Create a self-signed CA certificate
            string ca_cert = "CA_ECC_P256.cer";
            kuFlags = X509.KeyUsageOptions.KeyCertSign | X509.KeyUsageOptions.CrlSign | X509.KeyUsageOptions.NonRepudiation;
            dn = "C=AU;OU=Elliptical;O=Cert Services;CN=El Jefe";
            extns = "serialNumber=#xECC0CA;notBefore=2018-01-01;notAfter=2023-12-31";
            r = X509.MakeCertSelf(ca_cert, "CA_ECC_P256.p8e", 0, 0, dn, extns, kuFlags, "password", SigAlgorithm.Ecdsa_Sha256, X509.CertOptions.UTF8String);
            Console.WriteLine("X509.MakeCertSelf returns {0} (expecting 0)", r);
            Debug.Assert(0 == r);

            // Display cert details (with distinguished name in LDAP form (NB reverse order to dn above) and serial number in Decimal
            Console.WriteLine("FILE: {0}", ca_cert);
            s = X509.TextDumpToString(ca_cert, X509.OutputOpts.Decimal | X509.OutputOpts.Ldap);
            Console.WriteLine(s);

            // Use the CA certificate to create an end-user certificate
            string user_cert = "User_ECC_P256.cer";
            kuFlags = X509.KeyUsageOptions.DataEncipherment | X509.KeyUsageOptions.DigitalSignature | X509.KeyUsageOptions.KeyEncipherment;
            dn = "C=AU;OU=Elliptical;O=User Org;CN=The User";
            extns = "serialNumber=#xECD5A0;notBefore=2018-01-02;notAfter=2023-12-30";
            r = X509.MakeCert(user_cert, ca_cert, "User_ECC_P256.pub", "CA_ECC_P256.p8e", 0, 0, dn, extns, kuFlags, "password", 
                SigAlgorithm.Ecdsa_Sha256, X509.CertOptions.UTF8String | X509.CertOptions.Ecdsa_Deterministic);
            Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r);
            Debug.Assert(0 == r);

            // Query cert for information
            Console.WriteLine("FILE: {0}", user_cert);
            query = "signatureAlgorithm";
            Console.WriteLine("X509.QueryCert('{0}')={1}", query, X509.QueryCert(user_cert, query));
            query = "serialNumber";
            Console.WriteLine("X509.QueryCert('{0}')={1}", query, X509.QueryCert(user_cert, query));
            // Extract public key from certificate
            pubkey = Ecc.ReadPublicKey(user_cert).ToString();
            Debug.Assert(pubkey.Length > 0, "Failed to read public key from certificate");
            Console.WriteLine("ECC public key length={0}", Ecc.QueryKey(pubkey, "keyBits"));

            // Validate the end user cert was issued by CA
            r = X509.VerifyCert(user_cert, ca_cert);
            Console.WriteLine("X509.VerifyCert returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            Console.WriteLine("\nCreate a PKCS10 certificate signing request (CSR) using ECC P-256 key then use to issue new X.509 certificate...");

            string user_csr = "User2_ECC_P256.p10";
            dn = "C=AU;OU=Elliptic;O=User Org;CN=User 2";
            extns = "keyUsage=digitalSignature,dataEncipherment,dataEncipherment;";
            // Make a CSR - must match ECC key with ECC signature algorithm
            r = X509.CertRequest(user_csr, "User2_ECC_P256.p8e", dn, extns, "password", SigAlgorithm.Ecdsa_Sha256, 0);
            Console.WriteLine("X509.CertRequest returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);
            // We can verify that the signature is good
            r = X509.VerifyCert(user_csr, "");
            Console.WriteLine("X509.VerifyCert('{1}') returns {0} (expecting 0)", r, user_csr);
            Debug.Assert(0 == r);

            // CA receives the CSR and issues a new end-user certificate
            string user2_cert = "User2_ECC_P256.cer";
            extns = "notBefore=2018-01-02;";
            r = X509.MakeCert(user2_cert, ca_cert, user_csr, "CA_ECC_P256.p8e", 0xECD5A2, 4, "", extns, 0, "password", SigAlgorithm.Ecdsa_Sha256, 0);
            Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            // Dump the ASN.1 structure of the user certificate
            Console.WriteLine("FILE: {0}", user2_cert);
            s = Asn1.TextDumpToString(user2_cert, 0);
            Console.WriteLine(s);

            Console.WriteLine("\nThe CA creates a Certificate Revocation List (CRL) revoking User2's certificate as of 1 April 2018");
            string crlFile = "ECC_P256.crl";
            r = X509.MakeCRL(crlFile, ca_cert, "CA_ECC_P256.p8e", "password", "#xECD5A2,2018-04-01", "", SigAlgorithm.Ecdsa_Sha256, 0);
            Console.WriteLine("X509.MakeCRL returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            // Verify that the signature in the CRL file is good
            r = X509.VerifyCert(crlFile, ca_cert);
            Console.WriteLine("X509.VerifyCert('{1}') returns {0} (expecting 0)", r, crlFile);
            Debug.Assert(0 == r);

            // See if certificate has been revoked as of today
            r = X509.CheckCertInCRL(user2_cert, crlFile, null, "");
            Console.WriteLine("X509.CheckCertInCRL('{1}',now) returns {0} (expecting {2}=X509.Revoked)", r, crlFile, X509.Revoked);
            Debug.Assert(X509.Revoked == r);
            // See if certificate has been revoked as of 2018-02-01
            string datestr = "2018-02-01";
            r = X509.CheckCertInCRL(user2_cert, crlFile, null, datestr);
            Console.WriteLine("X509.CheckCertInCRL('{1}',{2}) returns {0} (expecting 0=NOT REVOKED)", r, crlFile, datestr);
            Debug.Assert(0 == r);


        }

        static void test_MakeCerts_PSS()
        {
            Console.WriteLine("\nCreate a new CA certificate and end-user certificate using RSA-PSS...");

            /* Demonstrates:
             * X509.MakeCertSelf() using SigAlgorithm.Rsa_Pss_Sha256
             * X509.MakeCert() using SigAlgorithm.Rsa_Pss_Sha256
             * X509.QueryCert()
             * X509.ValidatePath()
             * X509.TextDumpToString() using X509.OutputOpts.Ldap
             * Rsa.ReadPublicKey()
             * Rsa.KeyBits()
             */

            int r;
            X509.KeyUsageOptions kuFlags;
            string dn, extns;
            string s, query, pubkey;

            // Create a self-signed CA certificate
            string ca_cert = "CA_RSA_2048.cer";
            kuFlags = X509.KeyUsageOptions.KeyCertSign | X509.KeyUsageOptions.CrlSign | X509.KeyUsageOptions.NonRepudiation;
            dn = "C=AU;OU=PSS;O=Cert Services;CN=El Jefe";
            extns = "serialNumber=#d1234567;notBefore=2018-01-01;notAfter=2023-12-31";
            r = X509.MakeCertSelf(ca_cert, "CA_RSA_2048.p8e", 0, 0, dn, extns, kuFlags, "password", SigAlgorithm.Rsa_Pss_Sha256, X509.CertOptions.UTF8String);
            Console.WriteLine("X509.MakeCertSelf returns {0} (expecting 0)", r);
            Debug.Assert(0 == r);

            // Display cert details (with distinguished name in LDAP form (NB reverse order to dn above) and serial number in Decimal
            Console.WriteLine("FILE: {0}", ca_cert);
            s = X509.TextDumpToString(ca_cert, X509.OutputOpts.Decimal | X509.OutputOpts.Ldap);
            Console.WriteLine(s);

            // Use the CA certificate to create an end-user certificate
            string user_cert = "User_RSA_2048.cer";
            kuFlags = X509.KeyUsageOptions.DataEncipherment | X509.KeyUsageOptions.DigitalSignature | X509.KeyUsageOptions.KeyEncipherment;
            dn = "C=AU;OU=PSS;O=User Org;CN=The User";
            extns = "serialNumber=#d2345678;notBefore=2018-01-02;notAfter=2023-12-30";
            r = X509.MakeCert(user_cert, ca_cert, "User_RSA_2048.pub", "CA_RSA_2048.p8e", 0, 0, dn, extns, kuFlags, "password", SigAlgorithm.Rsa_Pss_Sha256, X509.CertOptions.UTF8String);
            Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r);
            Debug.Assert(0 == r);

            // Query cert for information
            Console.WriteLine("FILE: {0}", user_cert);
            query = "signatureAlgorithm";
            Console.WriteLine("X509.QueryCert('{0}')={1}", query, X509.QueryCert(user_cert, query));
            query = "serialNumber";
            Console.WriteLine("X509.QueryCert('{0}')={1}", query, X509.QueryCert(user_cert, query, X509.OutputOpts.Decimal));
            // Extract public key from certificate
            pubkey = Rsa.ReadPublicKey(user_cert).ToString();
            Debug.Assert(pubkey.Length > 0, "Failed to read public key from certificate");
            Console.WriteLine("Rsa public key length={0}", Rsa.KeyBits(pubkey));

            // Validate the certificate path
            string certpath = ca_cert + ";" + user_cert;
            r = X509.ValidatePath(certpath);
            Console.WriteLine("X509.ValidatePath('{0}') returns {1} (expecting 0)", certpath, r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

        }

        static void test_SIG_VerifyData_PSS()
        {
            Console.WriteLine("\nVERIFY A SIGNATURE VALUE:");

            /* Demonstrates:
             * Sig.VerifyData() using SigAlgorithm.Rsa_Pss_Sha1 and SigAlgorithm.Rsa_Pss_Sha512
             * Sig.VerifyDigest()
             */

            int r;
            string rsakeyvalue, msghex, S;
            string rsaprikey;
            byte[] msg, digest;

            Console.WriteLine("1. NIST test CAVS 11.1 FIPS186-3 - SigVer RSA PKCS#1 RSASSA-PSS...");
            // Example No 1 from NIST test
            // # CAVS 11.1
            // # "FIPS186-3 - SigVer RSA PKCS#1 RSASSA-PSS" information for "rsa2_check"
            // CryptoSys-PKI-specific way to read in an RSA key using hex-encoded components
	        rsakeyvalue = "<RSAKeyValue>" +
		        "<Modulus EncodingType=\"hexBinary\">ec996bc93e81094436fd5fc2eef511782eb40fe60cc6f27f24bc8728d686537f1caa82cfcfa5c323604b6918d7cd0318d98395c855c7c7ada6fc447f192283cdc81e7291e232336019d4dac12356b93a349883cd2c0a7d2eae9715f1cc6dd657cea5cb2c46ce6468794b326b33f1bff61a00fa72931345ca6768365e1eb906dd</Modulus>" +
		        "<Exponent EncodingType=\"hexBinary\">90c6d3</Exponent></RSAKeyValue>";
            msghex = "a4daf4621676917e28493a585d9baffca3755e77e1f18e3ccfb3dec60ab8ee7e684f5cde8864f2d7ae041d70ce1ea1b1e7878cbf93416848dbfdb5214fde972e5780cb83c439dfc8aa9fa3e2724adbd02bdb36d2213c84d1b12a23fb5bf1baae19772a97ef7cc21bc420b3f570a6c321167745f9b46a489ff8420f9a5679c1c4";
	        S = "319c62984acd52423e59a17d27d4eca7722703b054a71a1ee5f7a218b6f4a274632eaf8ef2a577a7e8a7f654b8deb1ec9b1e529cf93459cc8af4c6df6fffabc3edded0c421604ea2aae35836b05fd9de7abd78540d45fd6d0ea714733a3427b00d9d6404db8ede4a27932b47d88243eefcbffe1e55841823def30c57de7562cf";

            msg = Cnv.FromHex(msghex);
            r = Sig.VerifyData(S, msg, rsakeyvalue, SigAlgorithm.Rsa_Pss_Sha1);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            // Example from NIST with SHA512 and sLen = 0
            Console.WriteLine("2. NIST test with SHA512 and sLen = 0...");
	        rsakeyvalue = "<RSAKeyValue>" +
		        "<Modulus EncodingType=\"hexBinary\">a3f2235ad2053b4c83fa38f8284ed805421621fe98845fb01b689f5b82b32511b6d16173e7b40a66a3a999c189beb9e06822150ac8be677186370c823b5277d909de07564e281cca2f13873d9d07b7bd85a2b9ac66f4ce4f5e38b8e9eebec04c8caf311e375d69e80851d559b8e90e85ba6b96476790f727c25aa8163062ec8543fcc7759be62c7768ecc37f340bb06102762bf0441ca1aa2c7a81bf37dc8b27439d3abba93812c9bb44fe4d6a94baae709379f5ce5d0c8f81d00086b9caa3026819588f491b525807899cdab33d8e992150d2b105d3aab615217c6a3d740831c7dc76faabd9c9b9817ead0b494566de1433fff5ba4604c6b8446f6fc35e746aff84ff8bd7500410d10e82bf4c9036489de47dee9a327a5c4510d8561321b91d55559a4cba85e0c361767084b25217e8a63c4e151a1e88689feecffd16fa0a65ae41d2babca99cf1b959c3c076c0f75974146f2cc494126fbecad4217b9aaa00f169fa512527ff5a0b50da46d6be870ecef2af7a1e6c4556f6f7a0a00b9f47cb</Modulus>" +
		        "<Exponent EncodingType=\"hexBinary\">000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3f57f</Exponent></RSAKeyValue>";
	        msghex = "be2f3e1dc8a3711570401bd535185426944d094e8481a12a438de07d54760c88c99d4fdbbe355d6a26fa56e3ca20ee3f8e8acb98f63d2f3aea14d6fcb6b522d155c3759aef56de3ea0a8f9fd7b111001cf358636a87c765c99c2975bb95063d6ec0b780264ec3eb967b0caca52d10294deb402d3a224bfb9d9ffea41662f18c0";
	        S = "787cdd6e1d4fdf9a0d9f965eb85725232a9efcc12abfa1ef25a81e0983111d9000d494fc7d3201eb3bba327302727f7086147a755b4827030c7276536f425593ab2e9127a149e754de7ad77f8c2043267db49f8a35031d83f13d140d5df4d424b47454041a23b92ff6818e749d65d01fc50bebf69152f3f5fcb4873b1036219e22b1e74f8368c8c501ce65f2c929d90a8ec899630e802547a7ca6ef18ab3cb3eb4a691ee68aebeaf1b9c055ad12218039cf480cd8d294332c5e16ebbe6af11f8f4bf49f9b4ed2f511126ae780a3b784be8f4426abd17f8600074483f2af3b71a8964c6e0fa00049a1d940d34cc08839e0c59253d99e90d17871d489674695663626166d36ff91d8c2299a2f051eae2d60e8ed0bc3fac1e490b470c12f3d697f6fbfd880de2e90e9fcbd485fa3393198372fb01e4cec5c15917ecdd42e57c43ecf55a8c0ecbdcef1bce4e36d96d46b112570b53f82f3d2064b08ac78613670a28ea69d79c717eb1c294090dbd561fa6e504d09d265724e37a2dc6f445f6f528c9";

            msg = Cnv.FromHex(msghex);
            r = Sig.VerifyData(S, msg, rsakeyvalue, SigAlgorithm.Rsa_Pss_Sha512);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            Console.WriteLine("3. Test using independently-generated signature...");
            rsaprikey = "-----BEGIN RSA PRIVATE KEY-----" +
                "    MIICXgIBAAKBgQDH/zRrIL8qvdyGlYFRnddU7NjXY3u6P2YCrS/Gy4vkYhmUtfF0" +
                "    slIvY6RTF+kDEYCbZagsSjQYAFuqLZwAF5gNBPzDMfhZFmjY42Da9MGIg/0ePpye" +
                "    zJWXO7FN0DxQ1Jw8iHzvon4bVT++VswAQe2158w/Q5cwOF2h89E/KMuwqQIDAQAB" +
                "    AoGBAIVP2vwRzvvJpQbc/1+NDC0i14PzX1UNz4y3LqKfqXcp4Q1cnj+AYgIOtElj" +
                "    JcIS15w+DfS/3aumCXQNhPAWyhWtEiLNifOGvIK31G7Xb1QPnrfRueEnE5PEG5XZ" +
                "    zAC9cnxNoyun/6NcC4oo0qETj4obTNNStI30uo+bEtJ6g9ZVAkEA9Fa/acw+DpN1" +
                "    IxRo4ZIfrikqjCwZ+RO1aXKmndXv3s8dzWQvRgr72m31kZol1UmnpoMdPnps8Qgi" +
                "    dnNU6B2gMwJBANGKs5B/IDB5LwIJZteCrFsltcC3cdzxmcRTKfCq6m4wx2oDWTXF" +
                "    ZaygFtA+CXrrGxz0B0Jte3eGUpvsyasXn7MCQQDyyL+qAKgpE5xxHvaYLPoNtBny" +
                "    7l9gf5TjEmk8rDeMzYBvdf0DPCbFBD3eT60IIgfUDLQiQMO/PLYBvNfBTK7BAkAN" +
                "    ZmBLSkXls6o06CMCfyHEhmnUFCcc6PpbWrIg6N0rBMWL2wD2dlQlMOukj4MNsEFA" +
                "    nb5lGhk+MIHR5NeUsGMPAkEAuYLHs1uMpunnWbWf7XzsRPfs4mZE1VfZefqle2v6" +
                "    L9mZsntuuiwZ/9tegrNgIMrWovHy33K9xcu9g9KIeojrag==" +
                "    -----END RSA PRIVATE KEY-----";
            S = "445488de220010752634cf01df16172c22260abcb9dc26eccbe046a38e01e2fcb098266e39e337d99bb6ce33c7c4c8334e5f19d81ef341a1e3baf14e7d0a0e1eb67ac08bd5b0b2860b214a22e5254562f743bcf66d19c9dd05e4030f5aeb07e04d016973bf639c339f600ff7be39c27b3841728726e6c22b18fe69265a701d6b";
            msg = new byte[] { 0x61, 0x62, 0x63 };  // "abc"
            // Extract RSA public key from private
            string pubkeystr = Rsa.PublicKeyFromPrivate(Rsa.ReadPrivateKey(rsaprikey, "")).ToString();
            // Verify signature using public key
            r = Sig.VerifyData(S, msg, pubkeystr, SigAlgorithm.Rsa_Pss_Sha1);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            // Same again but verify against message digest
            Console.WriteLine("4. Same again but verify against message digest...");
            digest = Hash.BytesFromBytes(msg, HashAlgorithm.Sha1);
            Console.WriteLine("SHA1('{0}')={1}", System.Text.Encoding.Default.GetString(msg), Cnv.ToHex(digest));
            r = Sig.VerifyDigest(S, digest, pubkeystr, SigAlgorithm.Rsa_Pss_Sha1);
            Console.WriteLine("Sig.VerifyDigest() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

        }

        static void test_SIG_SignData_PSS()
        {
            Console.WriteLine("\nSIGN DATA USING RSA-PSS:");

            /* Demonstrates:
             * Sig.SignData() using SigAlgorithm.Rsa_Pss_Sha256
             * Sig.SignData() using Sig.SigOptions.PssSaltLenZero and Sig.SigOptions.Mgf1Sha1
             * Sig.VerifyData()
             * Sig.SignFile() using SigAlgorithm.Rsa_Pss_Sha512 and Sig.SigOptions.Mgf1Sha1
             * Sig.VerifyFile()
             */

            byte[] msg;
            string sigval, oksig;
            int r;

            string certFile = "AliceRSAPssSignByCarl.cer";
            string priKeyFile = "AliceRSAPSS.p8e"; // pkcs8-encrypted 
            string mypassword="password";

            // Input data = three-char ASCII string "abc"
            msg = System.Text.Encoding.Default.GetBytes("abc");
            Console.WriteLine("MSG: {0}", Cnv.ToHex(msg));

            Console.WriteLine("Sign using RSA-PSS-SHA256 (different each time)");
            // This will be different each time
            sigval = Sig.SignData(msg, priKeyFile, mypassword, SigAlgorithm.Rsa_Pss_Sha256);
            Console.WriteLine("SIG: {0}", sigval);
            if (sigval.Length == 0) disp_error();
            Debug.Assert(sigval.Length > 0);

            Console.WriteLine("Verify using X.509 certificate");
            // We must specify the signature algorithm used to sign it
            r = Sig.VerifyData(sigval, msg, certFile, SigAlgorithm.Rsa_Pss_Sha256);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            Console.WriteLine("Sign using RSA-PSS-SHA256 with zero-length salt (so signature is deterministic) and MGF1-with-SHA1 (just to be awkward)");
            // (This should be the same each time)
            sigval = Sig.SignData(msg, priKeyFile, mypassword, SigAlgorithm.Rsa_Pss_Sha256, Sig.SigOptions.PssSaltLenZero | Sig.SigOptions.Mgf1Sha1, 0);
            Console.WriteLine("SIG: {0}", sigval);
            Debug.Assert(sigval.Length > 0);
            // Known result
            oksig = "e0eb1ocnqg9raQBcjPYhjI5+l23P8aNWUHSPgaW5mUlfh2py1IjD1c/TDGQoIpQisbaBq0yDB4DSSpW0p9RqeQK33bELI3KM6A83q/5+J76WY/ZCPDSGJRTtovV4jiRSvoqPqcL/bJcQv1j9pqH2tTQmdSgArCYAntnLpBLYR2bbm3Q/1hnrs1T8CttYje+qSPvFAmShxSS8ryIm5POj6p2aXXtdDqo47B46nYeHAVArPUT1CKEXMelZWItlTWEjzqnofLO+nquYIpb7gNBExSfkwxTbBHa88UPu35eEe0AfLaxqEudi7YCAZ6/8cBC4MUXlx8Th6PQ5kPKN+i+ibw==";
            Debug.Assert(sigval == oksig);

            Console.WriteLine("Verify using X.509 certificate");
            // We must specify the signature algorithm used to sign it and must specify MGF1-with-SHA1, since we used that option
            // (however, salt length can be detected automatically from signature value)
            r = Sig.VerifyData(sigval, msg, certFile, SigAlgorithm.Rsa_Pss_Sha256, Sig.VerifyOpts.Mgf1Sha1);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            Console.WriteLine("\nSign a file using RSA-PSS-SHA512 with salt length set to the maximum possible; output encoded in hex");
            string dataFile = "excontent.txt";
            Console.WriteLine("FILE: {0}", dataFile);
            // (This will be different each time)
            // Set the salt length to be the maximum possible and output signature in hex encoding
            sigval = Sig.SignFile(dataFile, priKeyFile, mypassword, SigAlgorithm.Rsa_Pss_Sha512, Sig.SigOptions.PssSaltLenMax, Sig.Encoding.Base16);
            Console.WriteLine("SIG: {0}", sigval);
            if (sigval.Length == 0) disp_error();
            Debug.Assert(sigval.Length > 0);

            Console.WriteLine("Verify using X.509 certificate");
            // We must specify the signature algorithm used to sign it (but salt length is found automatically)
            r = Sig.VerifyFile(sigval, dataFile, certFile, SigAlgorithm.Rsa_Pss_Sha512);
            Console.WriteLine("Sig.VerifyFile() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

        }

        static void test_SIG_SignData_ECDSA()
        {
            Console.WriteLine("\nSIGN DATA USING ECDSA:");

            /* Demonstrates:
             * Sig.SignData() using SigAlgorithm.Ecdsa_Sha256
             * Sig.SignData() using Sig.SigOptions.UseDeterministic and Sig.SigOptions.Asn1DERStructure
             * Sig.VerifyData()
             */

            byte[] msg;
            string sigval, oksig;
            int r;

            string certFile = "User_ECC_P256.cer";
            string priKeyFile = "User_ECC_P256.p8e"; // pkcs8-encrypted 
            string mypassword = "password";

            // Input data = three-char ASCII string "abc"
            msg = System.Text.Encoding.Default.GetBytes("abc");
            Console.WriteLine("MSG: {0}", Cnv.ToHex(msg));

            Console.WriteLine("Sign using PKI_SIG_ECDSA_SHA256 (different each time)");
            // This will be different each time
            sigval = Sig.SignData(msg, priKeyFile, mypassword, SigAlgorithm.Ecdsa_Sha256);
            Console.WriteLine("SIG: {0}", sigval);
            Debug.Assert(sigval.Length > 0);

            Console.WriteLine("Verify using X.509 certificate");
            // We must specify the signature algorithm used to sign it
            r = Sig.VerifyData(sigval, msg, certFile, SigAlgorithm.Ecdsa_Sha256);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);

            Console.WriteLine("Sign using PKI_SIG_ECDSA_SHA256 with deterministic digital signature generation procedure of [RFC6979] ");
            Console.WriteLine("--output as BitCoin DER-encoded ASN.1 structure encoded in hex");
            // This should be the same each time
            sigval = Sig.SignData(msg, priKeyFile, mypassword, SigAlgorithm.Ecdsa_Sha256, Sig.SigOptions.UseDeterministic | Sig.SigOptions.Asn1DERStructure, Sig.Encoding.Base16);
            Console.WriteLine("SIG: {0}", sigval);
            Debug.Assert(sigval.Length > 0);

            // Known result
            oksig = "304402202f088efe451adba26ccafe507c3db0083f14e6c7fc822970dbf73a8e30de5bc702203e8c02b4e4310aff7d1e990dc50fa633c396bebd8e1b3f7daa599e9cd8d89a74";
            Console.WriteLine("OK : {0}", oksig);

            Console.WriteLine("Verify using X.509 certificate");
            // We must specify the signature algorithm used to sign it (everything else is detected automatically)
            r = Sig.VerifyData(sigval, msg, certFile, SigAlgorithm.Ecdsa_Sha256);
            Console.WriteLine("Sig.VerifyData() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r);
        }
       
        static void test_RSA_EncryptDecrypt()
        {
            Console.WriteLine("\nENCRYPT/DECRYPT SHORT MESSAGE USING RSA:");

            /* Demonstrates:
             * Rsa.Encrypt() using Rsa.EME.OAEP with default SHA-1
             * Rsa.Encrypt() using Rsa.EME.OAEP and Rsa.HashAlg.Sha256
             * Rsa.Encrypt() using default PKCS#1v1_5
             * Rsa.Decrypt()
             */

            string encHex, msgHex, correctHex;
            byte[] encmsg, msg, outmsg;

            // RSA key file 1024-bit from pkcs-1v2-1-vec
            string priKeyFile = "rsa-oaep-1.p8";    // PKCS#8 unencrypted PrivateKeyInfo
            string pubKeyFile = "rsa-oaep-1.pub";   // PKCS#1 RSAPublicKey

            // RSAES-OAEP Encryption Example 1.1 from `oaep-vect.txt` in `pkcs-1v2-1-vec.zip`
            Console.WriteLine("1. Decrypt RSAES-OAEP Encryption Example 1.1 from `oaep-vect.txt` in `pkcs-1v2-1-vec.zip`");
            // Cipher value from RSA-OAEP test vector
            encHex = "354fe67b4a126d5d35fe36c777791a3f7ba13def484e2d3908aff722fad468fb21696de95d0be911c2d3174f8afcc201035f7b6d8e69402de5451618c21a535fa9d7bfc5b8dd9fc243f8cf927db31322d6e881eaa91a996170e657a05a266426d98c88003f8477c1227094a0d9fa1e8c4024309ce1ecccb5210035d47ac72e8a";
            msg = Rsa.Decrypt(Cnv.FromHex(encHex), priKeyFile, "", Rsa.EME.OAEP, 0, 0);
            msgHex = Cnv.ToHex(msg);
            Console.WriteLine("msg={0}", msgHex);

            // Known result from test vector
            correctHex = "6628194e12073db03ba94cda9ef9532397d50dba79b987004afefe34";
            Console.WriteLine("OK ={0}", correctHex);
            Debug.Assert(String.Equals(correctHex, msgHex, StringComparison.OrdinalIgnoreCase));

            Console.WriteLine("2a. Encrypt using RSA-OAEP with SHA-256 (different each time)");
            Console.WriteLine("INPUT : {0}", Cnv.ToHex(msg));
            encmsg = Rsa.Encrypt(msg, pubKeyFile, Rsa.EME.OAEP, Rsa.HashAlg.Sha256, 0);
            Console.WriteLine("OUTPUT: {0}", Cnv.ToHex(encmsg));
            Console.WriteLine("2b. Decrypt");
            // Note we must we must specify the hash function used in RSA-OAEP
            outmsg = Rsa.Decrypt(encmsg, priKeyFile, "", Rsa.EME.OAEP, Rsa.HashAlg.Sha256, 0);
            Console.WriteLine("OUTPUT: {0}", Cnv.ToHex(outmsg));
            Debug.Assert(String.Equals(correctHex, Cnv.ToHex(outmsg), StringComparison.OrdinalIgnoreCase));

            Console.WriteLine("3a. Encrypt using default PKCS#1v1_5");
            msg = Cnv.FromHex("616263");    // "abc"
            Console.WriteLine("INPUT : {0}", Cnv.ToHex(msg));
            encmsg = Rsa.Encrypt(msg, pubKeyFile);
            Console.WriteLine("OUTPUT: {0}", Cnv.ToHex(encmsg));
            Console.WriteLine("3b. Decrypt");
            outmsg = Rsa.Decrypt(encmsg, priKeyFile, "");
            Console.WriteLine("OUTPUT: {0}", Cnv.ToHex(outmsg));
            Debug.Assert(String.Equals(Cnv.ToHex(msg), Cnv.ToHex(outmsg), StringComparison.OrdinalIgnoreCase));

        }
        static void test_RSA_ToXMLStringEx()
        {
            Console.WriteLine("\nCreate an XML string representation of an RSA internal key string with 'ds:' namespace prefix...");

            /* Demonstrates:
             * Rsa.ReadPublicKey()
             * Rsa.ToXMLString() using prefix "ds:"
             */

            string keyFile = "AliceRSAPssSignByCarl.cer";  // Public key inside an X.509 certificate
            Console.WriteLine("FILE: {0}", keyFile);
            string priKey = Rsa.ReadPublicKey(keyFile).ToString();
            // Create XML string with "ds:" namespace prefix.
            string xmlKey = Rsa.ToXMLString(priKey, "ds", 0);
            // You'd insert this inside an <ds:KeyInfo><ds:KeyValue> element
            Console.WriteLine("XML:\n{0}", xmlKey);
            Debug.Assert(xmlKey.Length > 0);
        }

        static void test_CMS_MakeSigData_PSS()
        {
            Console.WriteLine("\nCreate a signed-data object using RSA-PSS...");

            /* Demonstrates:
             * Rsa.ReadPrivateKey()
             * Cms.MakeSigData() using Cms.SigAlg.Rsa_Pss_Sha224
             * Cms.QuerySigData()
             * Cms.VerifySigData()
             * Cms.ReadSigDataToString()
             */

            string outFile = "SignedData_PSS.p7s";
            string inFile = "excontent.txt";
            string certFile = "User_RSA_2048.cer";
            string priKeyFile = "User_RSA_2048.p8e";
            int r;
            string s, query;
            StringBuilder sbPriKey = Rsa.ReadPrivateKey(priKeyFile, "password");
            Debug.Assert(sbPriKey.Length > 0, "Failed to read private key file");

            r = Cms.MakeSigData(outFile, inFile, certFile, sbPriKey.ToString(), Cms.SigAlg.Rsa_Pss_Sha224, 0);
            Debug.Assert(0 == r, "Cms.MakeSigData failed");
            Console.WriteLine("Created '{0}'", outFile);
            Console.WriteLine("SIGNED-DATA:\n{0}", Asn1.TextDumpToString(outFile, 0));
            // Query the signed-data object
            query = "digestAlgorithm";
            s = Cms.QuerySigData(outFile, query);
            Console.WriteLine("Cms.QuerySigData('{0}')={1}", query, s);
            query = "signatureAlgorithm";
            s = Cms.QuerySigData(outFile, query);
            Console.WriteLine("Cms.QuerySigData('{0}')={1}", query, s);
            query = "pssParams";
            s = Cms.QuerySigData(outFile, query);
            Console.WriteLine("Cms.QuerySigData('{0}')={1}", query, s);
            // Verify the signed-data
            r = Cms.VerifySigData(outFile);
            Console.WriteLine("Cms.VerifySigData() returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "Cms.VerifySigData failed");
            // Read the signed data
            s = Cms.ReadSigDataToString(outFile);
            Console.WriteLine("signed-data='{0}'", s);

        }

        static void test_MakeSignedEnveloped_PSS_OAEP()
        {
            Console.WriteLine("\nCreate an enveloped-data object using RSA-OAEP\n" 
                +" containing a signed-data object signed using RSA-PSS-SHA256...");
            // Wrap a signed-data object directly inside an enveloped-data object (no S/MIME wrapping).
            // -- the same technique used by the latest German Health specifications (current in 2018).

            /* Demonstrates:
             * Rsa.ReadPrivateKey()
             * Cms.MakeSigDataFromString() using Cms.SigAlg.Rsa_Pss_Sha256
             * Cms.MakeEnvData() using Cms.KeyEncrAlgorithm.Rsa_Oaep and HashAlgorithm.Sha256
             * Cms.ReadEnvDataToFile()
             * Cms.ReadSigDataToString()
             * Cms.VerifySigData()
             * Cms.QueryEnvData()
             * Cms.QuerySigData()
             * Wipe.File()
             * Wipe.String()
             */

            int r;
            string s, query;
            string msg = "Hi Bob, this is a secret message from Alice.";
            string tempFile = "intermediate.tmp";
            string envDataFile = "ToBobSignedByAlice.p7m";
            StringBuilder sbPriKey;
            // 1.1 Alice signs the message, saved as a temp intermediate file
            // -- Uses RSA-PSS with SHA-256
            sbPriKey = Rsa.ReadPrivateKey("AliceRSAPSS.p8e", "password");
            r = Cms.MakeSigDataFromString(tempFile, msg, "AliceRSAPssSignByCarl.cer;CarlRSAPssSelf.cer", sbPriKey.ToString(),
                Cms.SigAlg.Rsa_Pss_Sha256, Cms.SigDataOptions.IncludeAttributes | Cms.SigDataOptions.AddSignTime);
            Console.WriteLine("Cms.MakeSigDataFromString() returns {0} (expecting 0)", r);
            Debug.Assert(0 == r);

            // 1.2 The message is encrypted in an enveloped-data file using Bob's public key from his X.509 certificate
            // -- uses RSA-OAEP + SHA-256 + AES-256 content encryption
            r = Cms.MakeEnvData(envDataFile, tempFile, "BobRSAPssSignByCarl.cer",
                CipherAlgorithm.Aes256, Cms.KeyEncrAlgorithm.Rsa_Oaep, HashAlgorithm.Sha256, Cms.EnvDataOptions.None);
            Console.WriteLine("Cms.MakeEnvData() returns {0} (expecting 1)", r);
            Debug.Assert(r > 0);    // Historical anomaly - returns number or recipients, not zero

            // 1.3 Delete the intermediate file and private key string
            Wipe.File(tempFile);
            Wipe.String(sbPriKey);

            // Show the ASN (optional - if you understand ASN.1 encoding)
            //Console.WriteLine(Asn1.TextDumpToString(envDataFile, 0));

            // Query some details in the enveloped-data file
            Console.WriteLine("Query some details in the enveloped-data file...");
            query = "contentEncryptionAlgorithm";
            s = Cms.QueryEnvData(envDataFile, query);
            Console.WriteLine("{0}={1}", query, s);
            query = "keyEncryptionAlgorithm";
            s = Cms.QueryEnvData(envDataFile, query);
            Console.WriteLine("{0}={1}", query, s);
            query = "oaepParams";
            s = Cms.QueryEnvData(envDataFile, query);
            Console.WriteLine("{0}={1}", query, s);

            // Now send the encrypted file to Bob
            Console.WriteLine("\nBob decrypts the file and verifies the signed-data...");
            string sigDataFile = "FromAlice.p7s";

            // 2.1 Bob decrypts the outer enveloped-data object using his own private key
            sbPriKey = Rsa.ReadPrivateKey("BobRSAPSS.p8e", "password");
            r = Cms.ReadEnvDataToFile(sigDataFile, envDataFile, "BobRSAPssSignByCarl.cer", sbPriKey.ToString());
            Console.WriteLine("Cms.ReadEnvDataToFile() returns {0} (expecting 0)", r);
            Debug.Assert(0 == r, "Cannot decrypt enveloped-data object");

            // 2.2a (optional) Check we have a proper signed-data object
            Console.WriteLine("Query some details in the signed-data file...");
            query = "signatureAlgorithm";
            s = Cms.QuerySigData(sigDataFile, query);
            Console.WriteLine("{0}={1}", query, s);
            query = "digestAlgorithm";
            s = Cms.QuerySigData(sigDataFile, query);
            Console.WriteLine("{0}={1}", query, s);
            query = "pssParams";
            s = Cms.QuerySigData(sigDataFile, query);
            Console.WriteLine("{0}={1}", query, s);

            // 2.2b Verify the signature
            r = Cms.VerifySigData(sigDataFile);
            Console.WriteLine("Cms.VerifySigData() returns {0} (expecting 0)", r);
            Debug.Assert(0 == r, "Cms.VerifysigData failed");

            // 2.3 Extract the signed message
            s = Cms.ReadSigDataToString(sigDataFile);
            Console.WriteLine("Cms.ReadSigDataToString() returns string of length {0} (expecting +ve)", s.Length);
            Debug.Assert(s.Length > 0);
            Console.WriteLine("message='{0}'", s);

            // 2.4 Clean up
            Wipe.String(sbPriKey);

        }

        static void test_PFX_MakeFile_PSS()
        {
            Console.WriteLine("\nCreate a PFX file using RSA-PSS...");

            string pfxFile = "AliceRSAPSS.pfx";
            string keyFile = "AliceRSAPSS.p8e";
            string certFile = "AliceRSAPssSignByCarl.cer";
            int r;
            string keyStr;

            r = Pfx.MakeFile(pfxFile, certFile, keyFile, "password", null, 0);
            Console.WriteLine("Pfx.MakeFile() returns {0} (expecting 0)", r);
            if (r != 0) disp_error(r);
            Debug.Assert(0 == r, "Pfx.MakeFile() failed");
            // Test what sort of ASN.1 file we made (expecting "PKCS12 PFX")
            Console.WriteLine("Asn1.Type({0})={1}", pfxFile, Asn1.Type(pfxFile));

            // Read in the RSA private key stored in the PFX file
            keyStr = Rsa.ReadPrivateKey(pfxFile, "password").ToString();
            // Show we got something...
            Console.WriteLine("Key bits={0}", Rsa.KeyBits(keyStr));
        }

        static void test_CIPHER_EncryptAEAD()
        {
            Console.WriteLine("\nENCRYPT USING AEAD...");

            byte[] key, iv, pt, aad;
            byte[] ct, dt;
            string okhex;

            // GCM Test Case #03 (AES-128)
            Console.WriteLine("GCM Test Case #03 (AES-128)");
            key = Cnv.FromHex("feffe9928665731c6d6a8f9467308308");
            iv = Cnv.FromHex("cafebabefacedbaddecaf888");
            pt = Cnv.FromHex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255");
            okhex = "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f59854d5c2af327cd64a62cf35abd2ba6fab4";

            Console.WriteLine("KY={0}", Cnv.ToHex(key));
            Console.WriteLine("IV={0}", Cnv.ToHex(iv));
            Console.WriteLine("PT={0}", Cnv.ToHex(pt));

            ct = Cipher.EncryptAEAD(pt, key, iv, AeadAlgorithm.Aes_128_Gcm);
            Console.WriteLine("CT={0}", Cnv.ToHex(ct));
            Console.WriteLine("OK={0}", okhex);
            if (ct.Length == 0) disp_error();
            Debug.Assert(ByteArraysEqual(ct, Cnv.FromHex(okhex)));

            Console.WriteLine("Decrypt...");
            dt = Cipher.DecryptAEAD(ct, key, iv, AeadAlgorithm.Aes_128_Gcm);
            Console.WriteLine("DT={0}", Cnv.ToHex(dt));
            Debug.Assert(ByteArraysEqual(dt, pt));

            Console.WriteLine("Same again but prepend IV to start of output");
            ct = Cipher.EncryptAEAD(pt, key, iv, null, AeadAlgorithm.Aes_128_Gcm, Cipher.Opts.PrefixIV);
            Console.WriteLine("CT={0}", Cnv.ToHex(ct));
            Console.WriteLine("Decrypt...");
            dt = Cipher.DecryptAEAD(ct, key, iv, null, AeadAlgorithm.Aes_128_Gcm, Cipher.Opts.PrefixIV);
            Console.WriteLine("DT={0}", Cnv.ToHex(dt));
            Debug.Assert(ByteArraysEqual(dt, pt));

            Console.WriteLine("GCM Test Case #16 (AES-256)");
            key = Cnv.FromHex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
            iv = Cnv.FromHex("cafebabefacedbaddecaf888");
            pt = Cnv.FromHex("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39");
            aad = Cnv.FromHex("feedfacedeadbeeffeedfacedeadbeefabaddad2");
            okhex = "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f66276fc6ece0f4e1768cddf8853bb2d551b";

            Console.WriteLine("KY={0}", Cnv.ToHex(key));
            Console.WriteLine("IV={0}", Cnv.ToHex(iv));
            Console.WriteLine("PT={0}", Cnv.ToHex(pt));
            Console.WriteLine("AD={0}", Cnv.ToHex(aad));

            ct = Cipher.EncryptAEAD(pt, key, iv, aad, AeadAlgorithm.Aes_256_Gcm, 0);
            Console.WriteLine("CT={0}", Cnv.ToHex(ct));
            Console.WriteLine("OK={0}", okhex);
            if (ct.Length == 0) disp_error();
            Debug.Assert(ByteArraysEqual(ct, Cnv.FromHex(okhex)));
            Console.WriteLine("Decrypt...");
            dt = Cipher.DecryptAEAD(ct, key, iv, aad, AeadAlgorithm.Aes_256_Gcm, 0);
            Console.WriteLine("DT={0}", Cnv.ToHex(dt));
            Debug.Assert(ByteArraysEqual(dt, pt));

            Console.WriteLine("GCM Test Case #07 (AES-192)");
            key = Cnv.FromHex("000000000000000000000000000000000000000000000000");
            iv = Cnv.FromHex("000000000000000000000000");
            // Plaintext is the empty string - a zero-length byte array
            pt = Cnv.FromHex("");
            // IV is prepended to output
            okhex = "000000000000000000000000cd33b28ac773f74ba00ed1f312572435";

            Console.WriteLine("KY={0}", Cnv.ToHex(key));
            Console.WriteLine("IV={0}", Cnv.ToHex(iv));
            Console.WriteLine("PT={0}", Cnv.ToHex(pt));

            ct = Cipher.EncryptAEAD(pt, key, iv, null, AeadAlgorithm.Aes_192_Gcm, Cipher.Opts.PrefixIV);
            Console.WriteLine("CT={0}", Cnv.ToHex(ct));
            Console.WriteLine("OK={0}", okhex);
            if (ct.Length == 0) disp_error();
            Debug.Assert(ByteArraysEqual(ct, Cnv.FromHex(okhex)));
            Console.WriteLine("Decrypt...");
            dt = Cipher.DecryptAEAD(ct, key, iv, null, AeadAlgorithm.Aes_256_Gcm, Cipher.Opts.PrefixIV);
            Console.WriteLine("DT={0}", Cnv.ToHex(dt));
            Debug.Assert(ByteArraysEqual(dt, pt));
        }

        static void test_X509_ReadCertFromP7Chain()
        {
            Console.WriteLine("\nREAD CERTS AS BASE64 STRING FROM P7 CHAIN DATA...");

            int ncerts, i;
            string certstr;
            string s;
            // Input is a P7 chain file as a string in PEM format
            // bob.p7b (contains 2 X.509 certs: BobRSA and CarlRSA)
            string p7str = 		
                "-----BEGIN PKCS7-----" +
		        "MIIERQYJKoZIhvcNAQcCoIIENjCCBDICAQExADALBgkqhkiG9w0BBwGgggQaMIICJzCCAZCgAwIB" +
		        "AgIQRjRrx4AAVrwR024uzV1x0DANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDEwdDYXJsUlNBMB4X" +
		        "DTk5MDkxOTAxMDkwMloXDTM5MTIzMTIzNTk1OVowETEPMA0GA1UEAxMGQm9iUlNBMIGfMA0GCSqG" +
		        "SIb3DQEBAQUAA4GNADCBiQKBgQCp4WeYPznVX/Kgk0FepnmJhcg1XZqRW/sdAdoZcCYXD72lItA1" +
		        "hW16mGYUQVzPt7cIOwnJkbgZaTdt+WUee9mpMySjfzu7r0YBhjY0MssHA1lS/IWLMQS4zBgIFEjm" +
		        "Txz7XWDE4FwfU9N/U9hpAfEF+Hpw0b6Dxl84zxwsqmqn6wIDAQABo38wfTAMBgNVHRMBAf8EAjAA" +
		        "MA4GA1UdDwEB/wQEAwIFIDAfBgNVHSMEGDAWgBTp4JAnrHggeprTTPJCN04irp44uzAdBgNVHQ4E" +
		        "FgQU6PS4Z9izlqQq8xGqKdOVWoYWtCQwHQYDVR0RBBYwFIESQm9iUlNBQGV4YW1wbGUuY29tMA0G" +
		        "CSqGSIb3DQEBBQUAA4GBAHuOZsXxED8QIEyIcat7QGshM/pKld6dDltrlCEFwPLhfirNnJOIh/uL" +
		        "t359QWHh5NZt+eIEVWFFvGQnRMChvVl52R1kPCHWRbBdaDOS6qzxV+WBfZjmNZGjOd539OgcOync" +
		        "f1EHl/M28FAK3Zvetl44ESv7V+qJba3JiNiPzyvTMIIB6zCCAVSgAwIBAgIQRjRrx4AAVrwR024u" +
		        "n/JQIDANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDEwdDYXJsUlNBMB4XDTk5MDgxODA3MDAwMFoX" +
		        "DTM5MTIzMTIzNTk1OVowEjEQMA4GA1UEAxMHQ2FybFJTQTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw" +
		        "gYkCgYEA5Ev/GLgkV/R3/25ze5NxXLwzGpKSciPYQUbQzRE6BLOOr4KdvVEeF3rydiwrhjmnvdeN" +
		        "GlPs5ADV6OyiNrHt4lDiMgmKP5+ZJY+4Tqu5fdWWZdoWoMW+Dq5EW+9e9Kcpy4LdrETpqpOUKQ74" +
		        "GNbIV17ydsTyEWA4uRs8HZfJavECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E" +
		        "BAMCAYYwHQYDVR0OBBYEFOngkCeseCB6mtNM8kI3TiKunji7MA0GCSqGSIb3DQEBBQUAA4GBALee" +
		        "1ATT7Snk/4mJFS5M2wzwSA8yYe7EBOwSXS3/D2RZfgrD7Rj941ZAN6cHtfA4EmFQ7e/dP+MLuGGl" +
		        "pJs85p6cVJq2ldbabDu1LUU1nUkBdvq5uTH5+WsSU6D1FGCbfco+8lNrsDdvreZ019v6WuoUQWNd" +
		        "zb7IDsHaao1TNBgCMQA=" +
		        "-----END PKCS7-----";
            // Check type of ASN.1 data
            s = Asn1.Type(p7str);
            Console.WriteLine("Data type is [{0}]", s);
            // Get count of certs in P7
            s = X509.ReadCertStringFromP7Chain(p7str, 0);
            Debug.Assert(s.Length > 0);
            // Count is returned as a string, e.g. "2", so convert to a number
            ncerts = Int32.Parse(s);
            Console.WriteLine("ncerts={0}", ncerts);
            // Read each cert in turn
            for (i = 1; i <= ncerts; i++) {
                certstr = X509.ReadCertStringFromP7Chain(p7str, i);
                Console.WriteLine("{0}", certstr);
                // Query the cert for subjectName
                s = X509.QueryCert(certstr, "subjectName");
                Console.WriteLine("subjectName='{0}'", s);
            }
        }

        static void test_X509_ReadCertFromPFX()
        {
            Console.WriteLine("\nREAD CERT AS BASE64 STRING FROM PFX...");

            string certstr;
            string s;
            // Input is a PFX file as a string in PEM format
            // bob.pfx (password="password")
            string pfxStr = 
                "-----BEGIN PKCS12-----" +
                "MIIGhAIBAzCCBkoGCSqGSIb3DQEHAaCCBjsEggY3MIIGMzCCAv8GCSqGSIb3DQEHBqCCAvAwggLsAgEAMIIC5QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIawU" +
                "AVTFvAiECAggAgIICuNwEuFcRnZamZyMyIn+vH+wC5BVUtZAWNrlIqToezF7cYqt/18+HXB/46nllz+qUD3Dv9rS78MnPeAM47afFRTricHsiOpE+2eXf32lxduoF5+" +
                "CLS3S7TAhRUMp2Fh18LlukzK9lY67BGfU9Y3yCukTmwVXqe49dkj8y9JjVJhXnoc2c7eOk3o5RjXHFsAMHwirqdsESHstrDZYLMVGw5HnAamY7zQd8WUpIweAFaEDLJ" +
                "fyzqY1/LTL/txvZ9VQ/B/36HKyEpoIvuH6iOCBkebpJwWSkkffuVFbUfMLguMztL/sf+jE2NiuljSBJ9pTNsZziZWERb6CxZH0a2xkkBTciXM5Dl5efWL0GmBg+aJSI" +
                "yh+Gw5W8Q7gmnH6H9myszvW9uYv/epwCbIpHd0dRHPbL3fR4KGhFexq24tAG86tDqPKb6H6n0lSA+Oq46SwZ00xIFpVcFaO/8yVqf6+JRDGoZ55aAZF6OCi7R1GvI+6" +
                "pzz37pvP7SWfqVSuXCTNQq9uKw97SH5YftQ9hkELQ4vHCjFh4UJSBUCZgDtqR1uB/+44H5UpP8KvbETaOFJszMxsqXBMqc1uEODSNg+EHEx+yg7Bx1CcNrm+6rtThC4" +
                "9+ow18HDMxbn3lAw1ooblANvSzR4YTt68N/4dtwROOdXjwKzyg03qWK2sJaiH5LzbB5MMmrdAChb9dLoRKBN2LREob7KRKEs6v51IW1yq4UCwSmpP+RbchZwIoKVXx/" +
                "MYKjVqzGfZAgBRpXEq/KH/8R+ttFPKdab2GAEjd7hIOmetp5einQmK4C7JYE6Uyabf1IImtVhBw2dGU3GiM2zSIGqCx3bmYETZheMTAV9MMVUYe8gQeEpbXM4GAnwX0" +
                "wpS0aYapzGeA/62X2nFh21eRHVzUcf0miXVvyOy6a1vj6O6N5F1jVaCV3jCCAywGCSqGSIb3DQEHAaCCAx0EggMZMIIDFTCCAxEGCyqGSIb3DQEMCgECoIICpjCCAqI" +
                "wHAYKKoZIhvcNAQwBAzAOBAjw/dx4SlLcWwICCAAEggKALm91I8gYuPpRTCSn5pN4OQBLbI6jSW+9FGeNYvOy/+Pt3Oq0i15ZXZZez7dP8rdb0tmTCSZwVPIwtJRKxY" +
                "UNaTppUTWZhXhnmeTMtSZpFuKmo6UhW8lGUcg45sO5UKUtdH0/UgewaSUfV4L06vp4j7Fugwbp666seJJ/9vQwMAxoqj0blxNNmASAcW7yj/lA2/p4KuGlnGkv4MSW5" +
                "ViH7T24VeFXTzyFFR7UR1Nw9Blr5jdr7b2rZSdTj0GeHZ/L3FksFWJocl8PEEL4ZdVscbvO+l7vtbeBz0y9TDr/HUwt2tfqXgjckVVoJhmsczJXrG5Ai+brKnGQ7R5u" +
                "IpIsqd9O6EpG68VMMGA5iSKsLYtibieqom8mRO00sFiQharxONEdveY+3O98nG6xzHlaBdNbxVo38Y+4LK6Gc81dUWYwss3ajdiJWe0+TYQjMPF72eWctcQAoTxITpd" +
                "/j6rD7EmvLVyPIR46L4w6Gb/uz5G1T1UiLoh9luM1nRKKICyo2XllZDNO0msaub7DH1xzJzEy2OT9cwChqYfKKeWEE2BWL699fmq5RMCbIQVtE2bJDP8obu9j6HLskC" +
                "iZcJm6nC7IKS1pQ2BA/JJVKxC8ADuLOAOdicWquDd8MWL5a9HpXd5TtUlfiRecTw8IRozTLaoDVlhaYNGPzwkjL9zZ+Up5Uy6HHXMDb0aD0fgvMqdAspB1+Xlt2RgP6" +
                "CnEH2hwQqGFoA8TtijeS+DtdMy8BxJ7g1fiEH0+4UISl1vymjPI1MJCI1VlFLvpjZvKHluwjgp1SHk3tFRJLJ8a/eApvmscKXSlxcYz+5Bv8dxPGdhO/KOLQS7XZ4a8" +
                "VSg977WS1jFYMCMGCSqGSIb3DQEJFTEWBBRj8EbS3XBC5R/cJqUR73yB6mItizAxBgkqhkiG9w0BCRQxJB4iAEIAbwBiACcAcwAgAGYAcgBpAGUAbgBkAGwAeQAgAEk" +
                "ARDAxMCEwCQYFKw4DAhoFAAQUaHSMUJ415FfKGv3cZpwloKDmqgYECAreM3EkHVjCAgIIAA==" +
                "-----END PKCS12-----";
            // Check type of ASN.1 data
            s = Asn1.Type(pfxStr);
            Console.WriteLine("Data type is [{0}]", s);
            // Read in cert as base64 string
            certstr = X509.ReadCertStringFromPFX(pfxStr, "password");   // SECURITY!!
            Console.WriteLine("{0}", certstr);
            // Query the cert for subjectName
            s = X509.QueryCert(certstr, "subjectName");
            Console.WriteLine("subjectName='{0}'", s);
        }


        // END TESTS
        //******************************************************

        //*****************
        // FILE UTILITIES *
        //*****************
        static bool FileExists(string filePath)
        {
            FileInfo fi = new FileInfo(filePath);
            return fi.Exists;
        }

        static long FileLength(string filePath)
        {
            FileInfo fi = new FileInfo(filePath);
            return fi.Length;
        }
        static bool FileIsNotPresent(string filePath, string message)
        {
            if (!FileExists(filePath)) {
                Console.WriteLine("\n{0}: {1}", message, filePath);
                return true;
            }
            return false;
        }

        //******************
        // SETUP STUFF
        //******************
        static string SetupTestFilesAndDirectory(string[] arrFileNames)
		{
			string subdir;
			//**************************************************
			// Check we have required files in local directory *
			//**************************************************
			string assemblyFile = Assembly.GetExecutingAssembly().Location;
			string assemblyDir = Path.GetDirectoryName(assemblyFile);
			Console.WriteLine("Local directory is '{0}'.", assemblyDir);
			Console.WriteLine("Checking required test files are in local directory...");
			string missingFile = "STOPPED: Required file is missing.\n Look in '" + zippedTestFiles + "'";
			foreach (string fn in arrFileNames)
			{
				if (FileIsNotPresent(fn, missingFile)) return null;
			}

			//*************************************************
			// Create a test sub-directory with a random name, 
			// copy these test files to it, and work in that sub-directory
			//*************************************************
			subdir = "pkitest." + Cnv.ToHex(Rng.Bytes(4));
			Console.WriteLine("Creating test sub-directory '{0}'", subdir);
			System.IO.Directory.CreateDirectory(subdir);
			// Copy test files
			foreach (string fn in arrFileNames)
			{
				System.IO.File.Copy(fn, subdir + "\\" + fn, true);
			}
			// Change current working directory to sub-dir
			System.IO.Directory.SetCurrentDirectory(subdir);
			Console.WriteLine("CWD is " + System.IO.Directory.GetCurrentDirectory());

			return subdir;
		}

		//*********************************************************
		// Put CWD back to parent and offer to remove the test dir
		//*********************************************************
		static void RestoreDirectory(string subdir, bool askDelete)
		{
			string s;

			System.IO.Directory.SetCurrentDirectory("..");
			Console.WriteLine("\nCWD reset to " + System.IO.Directory.GetCurrentDirectory());
			if (askDelete)
			{
				Console.Write("The temp test directory '{0}' was created by this program.\nDo you want to remove it? ([Y]/N) ", subdir);
				s = Console.ReadLine();
				if ("N" != s && "n" != s)
				{
					// Remove directory
					Console.WriteLine("Removing test directory...");
					System.IO.Directory.Delete(subdir, true);
				}
				else
				{
					Console.WriteLine("Temp directory '{0}' left in place.", subdir);
				}
			}
			else
			{
				// Remove directory regardless
				Console.WriteLine("Removing test directory '{0}'", subdir);
				System.IO.Directory.Delete(subdir, true);
			}
		}

        //***********************
        // DISPLAY ERROR DETAILS
        //***********************
        /// <summary>
        /// Display details of last error given error code
        /// </summary>
        /// <param name="nErr">Error code returned</param>
        static void disp_error(int nErr)
        {
            string s = General.LastError();
            Console.WriteLine("ERROR Returned={0}/{1}: {2}; {3}", nErr, General.ErrorCode(), General.ErrorLookup(nErr), s);
        }

        /// <summary>
        /// Display details of last error (no error code)
        /// </summary>
        static void disp_error()
        {
            string s = General.LastError();
            int nErr = General.ErrorCode();
            Console.WriteLine("ERROR {0}: {1}: {2}", nErr, General.ErrorLookup(nErr), s);
        }

        static bool ByteArraysEqual(byte[] data1, byte[] data2)
        {	// Thanks to Jon Skeet http://www.pobox.com/~skeet
            // If both are null, they're equal
            if (data1 == null && data2 == null) {
                return true;
            }
            // If either but not both are null, they're not equal
            if (data1 == null || data2 == null) {
                return false;
            }
            if (data1.Length != data2.Length) {
                return false;
            }
            for (int i = 0; i < data1.Length; i++) {
                if (data1[i] != data2[i]) {
                    return false;
                }
            }
            return true;
        }

    }

}