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

/* Some tests using the CryptoSysPKI .NET interface.
*/

/*   $Id: TestPKIcsharp.cs $
 *   Last updated:
 *   $Date: 2006-08-09 08:39:00 $
 *   $Version: 2.9.0 $
 */

/* NOTE: This program requires the following files to exist in the 
 * same directory as the executable:
 *  CarlRSASelf.cer
 *  CarlRSASign.epk
 *  AliceRSASignByCarl.cer
 *  AlicePrivRSASign.epk
 *  BobRSASignByCarl.cer
 *  BobPrivRSAEncrypt.epk (NOTE: the .EPK extension is one we made up)
 *  bob.p7b
 */
namespace TestPKIExamples
{
    /// <summary>
    /// Test examples for CryptoSysPKI interface
    /// </summary>
    class TestPKIExamples
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main(string[] args)
        {
            string s;
            char ch;
            int i, n, nblock, r;
            byte[] b;
            bool isok;
            byte[] msg;
            byte[] bcheck;
            StringBuilder sbPrivateKey;
            StringBuilder sbPublicKey;
            string keyhex, plain, cipher, ivhex, okhex;
            byte[] arrPlain;
            byte[] arrCipher;
            byte[] arrKey;
            byte[] arrIV;
            string excontent, fnameData, fnameEnc, fnameCheck;
            string fnameInput, fnameOutput, fnameCert, fname;
            string pubkeyFile, prikeyFile;
            string issuerCert;
            string hexDigest;
            string strCheck;
            string query;

            //****************
            // GENERAL TESTS *
            //****************
            Console.WriteLine("GENERAL FUNCTIONS:");
            n = CryptoSysPKI.General.Version();
            Console.WriteLine("Version={0}", n);
            ch = CryptoSysPKI.General.LicenceType();
            Console.WriteLine("LicenceType={0}", ch);
            s = CryptoSysPKI.General.ModuleName();
            Console.WriteLine("ModuleName={0}", s);
            s = CryptoSysPKI.General.CompileTime();
            Console.WriteLine("CompileTime={0}", s);
            s = CryptoSysPKI.General.LastError();
            Console.WriteLine("LastError='{0}' (expecting empty)", s);
            n = CryptoSysPKI.General.PowerUpTests();
            Console.WriteLine("PowerUpTests={0}", n);

            //**************************************************
            // 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";
            if (FileIsNotPresent("CarlRSASelf.cer", missingFile)) return; 
            if (FileIsNotPresent("CarlPrivRSASign.epk", missingFile)) return; 
            if (FileIsNotPresent("AliceRSASignByCarl.cer", missingFile)) return; 
            if (FileIsNotPresent("AlicePrivRSASign.epk", missingFile)) return; 
            if (FileIsNotPresent("BobRSASignByCarl.cer", missingFile)) return; 
            if (FileIsNotPresent("BobPrivRSAEncrypt.epk", missingFile)) return;
            if (FileIsNotPresent("bob.p7b", missingFile)) return;

            //*******************************************
            // TDEA (Triple DES, 3DES) ENCRYPTION TESTS *
            //*******************************************
            Console.WriteLine("TESTING TRIPLE DES:");
            keyhex = "010101010101010101010101010101010101010101010101";
            plain = "8000000000000000";
            cipher = "95F8A5E5DD31D900";
            // Encrypt in ECB mode using hex strings
            s = Tdea.Encrypt(plain, keyhex, Mode.ECB, null);
            Console.WriteLine("KY={0}",keyhex);
            Console.WriteLine("PT={0}",plain);
            Console.WriteLine("CT={0}",s);
            Console.WriteLine("OK={0}",cipher);
            Debug.Assert(String.Compare(s, cipher, true)==0, "Tdea.HexECB failed");
            // Decrypt
            s = Tdea.Decrypt(cipher, keyhex, Mode.ECB, null);
            Console.WriteLine("P'={0}",s);
            Console.WriteLine("OK={0}",plain);
            Debug.Assert(String.Compare(s, plain, true)==0, "Tdea.HexECB failed");

            // Ditto using byte arrays
            arrPlain = Cnv.FromHex(plain);
            arrCipher = Cnv.FromHex(cipher);
            arrKey = Cnv.FromHex(keyhex);
            b = Tdea.Encrypt(arrPlain, arrKey, Mode.ECB, null);
            Console.WriteLine("CT={0}",Cnv.ToHex(b));
            b = Tdea.Decrypt(arrCipher, arrKey, Mode.ECB, null);
            Console.WriteLine("P'={0}",Cnv.ToHex(b));

            // Encrypt in CBC mode using hex strings
            keyhex = "737C791F25EAD0E04629254352F7DC6291E5CB26917ADA32";
            ivhex = "B36B6BFB6231084E";
            plain = "5468697320736F6D652073616D706520636F6E74656E742E0808080808080808";
            cipher = "d76fd1178fbd02f84231f5c1d2a2f74a4159482964f675248254223daf9af8e4";
            s = Tdea.Encrypt(plain, keyhex, Mode.CBC, ivhex);
            Console.WriteLine("KY={0}",keyhex);
            Console.WriteLine("IV={0}",ivhex);
            Console.WriteLine("PT={0}",plain);
            Console.WriteLine("CT={0}",s);
            Console.WriteLine("OK={0}",cipher);
            Debug.Assert(String.Compare(s, cipher, true)==0, "Tdea.Encrypt{Hex,CBC} failed");
            // Decrypt
            s = Tdea.Decrypt(cipher, keyhex, Mode.CBC, ivhex);
            Console.WriteLine("P'={0}",s);
            Console.WriteLine("OK={0}",plain);
            Debug.Assert(String.Compare(s, plain, true)==0, "Tdea.Decrypt{Hex,CBC} failed");

            // Ditto using byte arrays
            arrPlain = Cnv.FromHex(plain);
            arrCipher = Cnv.FromHex(cipher);
            arrKey = Cnv.FromHex(keyhex);
            arrIV = Cnv.FromHex(ivhex);
            b = Tdea.Encrypt(arrPlain, arrKey, Mode.CBC, arrIV);
            Console.WriteLine("CT={0}",Cnv.ToHex(b));
            b = Tdea.Decrypt(arrCipher, arrKey, Mode.CBC, arrIV);
            Console.WriteLine("P'={0}",Cnv.ToHex(b));
            Debug.Assert(String.Compare(arrPlain.ToString(), b.ToString())==0, "Tdea.BytesCBC failed");

            // Create a test text file
            excontent = "This is some sample content.";
            fnameData = "excontent.txt";
            MakeATextFile(fnameData, excontent);

            // Encrypt a file
            keyhex = "fedcba98765432100123456789abcdeffedcba9876543210";
            fnameEnc = "excontent.tdea.enc.dat";
            okhex = "DD1E1FA430AE6BE1D3B83245F7A5B17C4BF03688238778E95F2CCD05AF1A8F44";
            n = Tdea.FileEncrypt(fnameEnc, fnameData, keyhex, Mode.ECB, null);
            if (0 == n)
                Console.WriteLine("Tdea.File created encrypted file '{0}'", fnameEnc);
            else
                Console.WriteLine("Tdea.File returned error code {0}", n);
            Debug.Assert(0 == n, "Tdea.File failed.");

            // Check we got what we should
            b = ReadABinaryFile(fnameEnc);
            Console.WriteLine("CT={0}", Cnv.ToHex(b));
            Debug.Assert(String.Compare(Cnv.ToHex(b), okhex, true)==0, "Tdea.FileEncrypt failed");

            // Decrypt it using byte format of key instead of hex
            fnameCheck = "excontent.tdea.chk.txt";
            b = Cnv.FromHex(keyhex);
            n = Tdea.FileDecrypt(fnameCheck, fnameEnc, b, Mode.ECB, null);
            if (0 == n)
            {
                Console.WriteLine("Tdea.File decrypted to file '{0}'", fnameCheck);
                // Show contents of file
                s = ReadATextFile(fnameCheck);
                Debug.Assert(String.Compare(s, excontent)==0, "Decrypted file data does not match");
            }
            else
                Console.WriteLine("Tdea.File returned error code {0}", n);
            Debug.Assert(0 == n, "Tdea.File failed.");

            //************
            // RSA TESTS *
            //************
            Console.WriteLine("RSA FUNCTION TESTS:");
            // Read the public key from the recipient's X.509 certificate
            sbPublicKey = Rsa.GetPublicKeyFromCert("AliceRSASignByCarl.cer");
            Console.WriteLine("PublicKey={0}", sbPublicKey.ToString());
            Console.WriteLine("PublicKeyBits={0}", Rsa.KeyBits(sbPublicKey.ToString()));
            Console.WriteLine("PublicKeyBytes={0}", Rsa.KeyBytes(sbPublicKey.ToString()));

            // A message to transmit in byte format
            msg = System.Text.Encoding.Default.GetBytes("Hello world!");
            // Make an RSA data block of same length in bytes as key
            // using EME-PKCS1-v1_5 encoding
            nblock = Rsa.KeyBytes(sbPublicKey.ToString());
            // [old, deprecated way:] b = Rsa.EncodeMsg(nblock, msg, Rsa.EncodeFor.Encryption);
            // [New way in v2.9:]
            b = Rsa.EncodeMsgForEncryption(nblock, msg, Rsa.EME.PKCSv1_5);
            Console.WriteLine("BLK={0}", Cnv.ToHex(b));

            // Encrypt with RSA public key
            b = Rsa.RawPublic(b, sbPublicKey.ToString());
            Console.WriteLine("ENC={0}", Cnv.ToHex(b));

            // Read in the private key from the encrypted key file
            sbPrivateKey = Rsa.ReadEncPrivateKey("AlicePrivRSASign.epk", "password");
            Console.WriteLine("PrivateKey={0}", sbPrivateKey.ToString());
            Console.WriteLine("PrivateKeyBits={0}", Rsa.KeyBits(sbPrivateKey.ToString().ToString()));
            Console.WriteLine("PrivateKeyBytes={0}", Rsa.KeyBytes(sbPrivateKey.ToString().ToString()));

            // Decrypt with private key
            b = Rsa.RawPrivate(b, sbPrivateKey.ToString().ToString());
            Console.WriteLine("DEC={0}", Cnv.ToHex(b));

            // Extract the message from the encryption block
            // [old, deprecated way:] b = Rsa.DecodeMsg(b, Rsa.EncodeFor.Encryption);
            // [New way in v2.9:]
            b = Rsa.DecodeMsgForEncryption(b, Rsa.EME.PKCSv1_5);
            Console.WriteLine("MSG={0}", Cnv.ToHex(b));
            // Convert back to a string
            s = System.Text.Encoding.Default.GetString(b);
            Console.WriteLine("MSG={0}", s);

            // Now use to do RSA digital signing

            // A message to sign in byte format
            msg = System.Text.Encoding.Default.GetBytes("abc");
            // Make an RSA data block of same length in bytes as key
            // using EMSA-PKCS1-v1_5 encoding with SHA-1
            nblock = Rsa.KeyBytes(sbPrivateKey.ToString().ToString());
            // [old, deprecated way:] b = Rsa.EncodeMsg(nblock, msg, Rsa.EncodeFor.Signature);
            // [New way in v2.9:]
            b = Rsa.EncodeMsgForSignature(nblock, msg, HashAlgorithm.Sha1);
            Console.WriteLine("BLK={0}", Cnv.ToHex(b));

            // Sign with RSA private key
            b = Rsa.RawPrivate(b, sbPrivateKey.ToString().ToString());
            Console.WriteLine("SIG={0}", Cnv.ToHex(b));

            // Transmit the message + SIG block to recipient...

            // Decrypt to verify with RSA public key
            b = Rsa.RawPublic(b, sbPublicKey.ToString());
            Console.WriteLine("VER={0}", Cnv.ToHex(b));

            // Create an independent Encoded block from message
            // [old, deprecated way:] bcheck = Rsa.EncodeMsg(nblock, msg, Rsa.EncodeFor.Signature);
            // [New way in v2.9:]
            bcheck = Rsa.EncodeMsgForSignature(nblock, msg, HashAlgorithm.Sha1);
            // And compare to see if they are the same
            // (mmm, C# doesn't have a memcmp function, so we need our own function)
            if (CompareByteArrays(bcheck, b))
                Console.WriteLine("OK, verification OK");
            else
                Console.WriteLine("ERROR: verification failed");
            Debug.Assert(CompareByteArrays(bcheck, b), "Verification failed");

            // Test Encode & Decode functions directly
            Console.WriteLine("Testing RSA Encoding methods...");
            Console.WriteLine("EME-PKCS1-V1_5 method");
            msg = Cnv.FromHex("deadbeef");
            //b = Rsa.EncodeMsg(64, msg, Rsa.EncodeFor.Encryption);
            b = Rsa.EncodeMsgForEncryption(64, msg, Rsa.EME.PKCSv1_5);
            Console.WriteLine("MSG={0}", Cnv.ToHex(msg));
            Console.WriteLine("EME={0}", Cnv.ToHex(b));
            //bcheck = Rsa.DecodeMsg(b, Rsa.EncodeFor.Encryption);
            bcheck = Rsa.DecodeMsgForEncryption(b, Rsa.EME.PKCSv1_5);
            Console.WriteLine("MSG={0}", Cnv.ToHex(bcheck));
            Debug.Assert(CompareByteArrays(bcheck, msg), "EME-PKCSv1_5 Decoding failed");

            // Again using OAEP algorithm instead of default
            Console.WriteLine("EME-OAEP method");
            msg = Cnv.FromHex("deadbeef");
            //b = Rsa.EncodeMsg(64, msg, Rsa.EncodeFor.Encryption_OAEP);
            b = Rsa.EncodeMsgForEncryption(64, msg, Rsa.EME.OAEP);
            Console.WriteLine("MSG={0}", Cnv.ToHex(msg));
            Console.WriteLine("EME={0}", Cnv.ToHex(b));
            //bcheck = Rsa.DecodeMsg(b, Rsa.EncodeFor.Encryption_OAEP);
            bcheck = Rsa.DecodeMsgForEncryption(b, Rsa.EME.OAEP);
            Console.WriteLine("MSG={0}", Cnv.ToHex(bcheck));
            Debug.Assert(CompareByteArrays(bcheck, msg), "EME-OAEP Decoding failed");

            // Ditto for signature
            Console.WriteLine("EME-PKCS1-V1_5 method for signature");
            msg = System.Text.Encoding.Default.GetBytes("abc");
            //b = Rsa.EncodeMsg(64, msg, Rsa.EncodeFor.Signature);
            b = Rsa.EncodeMsgForSignature(64, msg, HashAlgorithm.Sha1);
            Console.WriteLine("MSG={0}", Cnv.ToHex(msg));
            Console.WriteLine("EMSA={0}", Cnv.ToHex(b));
            //bcheck = Rsa.DecodeMsg(b, Rsa.EncodeFor.Signature);
            // Note we can only ever extract the *digest* out of an EMSA block, not the original message
            bcheck = Rsa.DecodeDigestForSignature(b);
            Console.WriteLine("MD={0}", Cnv.ToHex(bcheck));
            // We expect the value of SHA-1("abc")
            Debug.Assert(CompareByteArrays(bcheck, Cnv.FromHex("A9993E364706816ABA3E25717850C26C9CD0D89D")));

            // Check our valid keys
            n = Rsa.CheckKey(sbPrivateKey.ToString());
            Console.WriteLine("Rsa.CheckKey returns {0} for private key (expecting 0)", n);
            n = Rsa.CheckKey(sbPublicKey.ToString());
            Console.WriteLine("Rsa.CheckKey returns {0} for public key (expecting 1)", n);
            // and try something non-valid
            Console.WriteLine("Try an invalid key string...");
            n = Rsa.CheckKey("Some garbage in a string");
            Console.WriteLine("Rsa.CheckKey returns {0} (expecting -ve error code) {1};{2}", n, 
                General.ErrorLookup(n), General.LastError());

            // Export key to XML format
            s = Rsa.ToXMLString(sbPrivateKey.ToString(), Rsa.XmlOptions.ForceRSAKeyValue);
            Console.WriteLine("Key in XML format={0}", s);

            // Test re-import from XML
            s = Rsa.FromXMLString(s, false);
            Debug.Assert(String.Compare(s, sbPrivateKey.ToString().ToString()) == 0, 
                "Private key string from XML does not match original");

            // Create a PKCS-12 file containing Alice's private key
            fname = "alice.pfx";
            n = Pfx.MakeFile(fname, "AliceRSASignByCarl.cer", 
                "AlicePrivRSASign.epk", "password", "Alice's Key", false);
            Console.WriteLine("Pfx.MakeFile returns {0} (expected 0)", n);
            Debug.Assert(0 == n, "Failed to create PFX file");

            // Verify that we saved it OK
            isok = Pfx.SignatureIsValid(fname, "password");
            Console.WriteLine("Signature in {0} is {1}", fname, (isok ? "OK" : "INVALID"));
            Debug.Assert (isok, "PFX signature is invalid");

            // Extract the encrypted private key from the PKCS12 file
            fnameOutput = "alice_epk_from_pfx.bin";
            n = Rsa.GetPrivateKeyFromPFX(fnameOutput, fname);
            Console.WriteLine("Rsa.GetPrivateKeyFromPFX returns {0} (expected +ve)", n);
            Debug.Assert(n > 0, "Failed to extract private key from PFX file");

            // Check we got the same private key as we had before
            sbPrivateKey = Rsa.ReadEncPrivateKey(fnameOutput, "password");
            strCheck = sbPrivateKey.ToString();
            sbPrivateKey = Rsa.ReadEncPrivateKey("AlicePrivRSASign.epk", "password");
            Debug.Assert(strCheck == sbPrivateKey.ToString(), "Private keys don't match");

            // Save private key in unencrypted format compatible with OpenSSL
            fname = "AlicePriInfo_ssl.txt";
            n = Rsa.SavePrivateKeyInfo(fname, sbPrivateKey.ToString(), Rsa.Format.SSL);
            Console.WriteLine("Rsa.SavePrivateKeyInfo returns {0} (expected 0)", n);
            Debug.Assert(0 == n, "Failed to create unencrypted private key file");

            // Check we can read it and that it's the same as before
            strCheck = sbPrivateKey.ToString();
            sbPrivateKey = Rsa.ReadPrivateKeyInfo(fname);
            Debug.Assert(strCheck == sbPrivateKey.ToString(), "Private keys don't match");

            // Now save the public key in PEM format
            fname = "pubkey_check.txt";
            n = Rsa.SavePublicKey(fname, sbPublicKey.ToString(), Rsa.Format.PEM);
            Console.WriteLine("Rsa.SavePublicKey returns {0} (expected 0)", n);
            Debug.Assert(0 == n, "Failed to create public key file");

            // And check we can read it and it is the same
            strCheck = sbPublicKey.ToString();
            sbPublicKey = Rsa.ReadPublicKey(fname);
            Debug.Assert(strCheck == sbPublicKey.ToString(), "Public keys don't match");

            // Extract the certificate from a PKCS-12 PFX file
            fnameInput = "alice.pfx";
            fnameCert = "alice_fromPFX.cer";
            n = X509.GetCertFromPFX(fnameCert, fnameInput);
            Console.WriteLine("X509.GetCertFromPFX returns {0} (expected +ve).", n);
            Debug.Assert(n > 0, "Failed to extract certificate from PFX file");
            // Check it is an X.509 cert by getting its subject
            s = X509.CertSubjectName(fnameCert, "");
            Console.WriteLine("{0} has subject {1}.", fnameCert, s);



            // Generate a fresh RSA key which we'll use later
            pubkeyFile = "mykey_pub.bin";
            prikeyFile = "mykey_epk.bin";
            n = Rsa.MakeKeys(pubkeyFile, prikeyFile, 512, Rsa.PublicExponent.Exp_EQ_65537, 
                1024, "password", Rsa.PbeOptions.Default, true);
            Console.WriteLine("Rsa.MakeKeys returned {0}", n);
            Debug.Assert(n == 0, "Failed to create RSA key pair");
            Console.WriteLine("Created public/private key pair OK");


            //*************
            // X509 TESTS *
            //*************
            Console.WriteLine("X509 TESTS:");
            // create a new self-signed certificate (Issue No 1) using keys we just created
            fnameCert = "myCAcert.cer";
            n = X509.MakeCertSelf(fnameCert, prikeyFile, 1, 5, "CN=Me;C=AU", "myemail@here.com", 
                X509.KeyUsageOptions.DigitalSignature | X509.KeyUsageOptions.KeyCertSign, 
                "password", X509.Options.FormatPem);
            Console.WriteLine("X509.MakeCertSelf returned {0}", n);

            // create a new certificate for me (Issue No 101) as issued by Carl
            fnameCert = "mycert.cer";
            issuerCert = "CarlRSASelf.cer";
            prikeyFile = "CarlPrivRSASign.epk";
            n = X509.MakeCert(fnameCert, issuerCert, pubkeyFile, prikeyFile, 
                101, 2, "CN=Me;C=US;O=MyOrg", "", 0, "password", 
                X509.Options.SigAlg_Md5WithRSAEncryption | X509.Options.VersionOne);
            Console.WriteLine("X509.MakeCert returned {0}", n);

            // Verify our two new certificates
            n = X509.VerifyCert(fnameCert, issuerCert);
            if (0 == n)
                Console.WriteLine("OK, {0} was issued by {1}.", fnameCert, issuerCert);
            else
                Console.WriteLine("ERROR: {0} was NOT issued by {1}.", fnameCert, issuerCert);
            
            fnameCert = "myCAcert.cer";
            issuerCert = "myCAcert.cer";
            n = X509.VerifyCert(fnameCert, issuerCert);
            if (0 == n)
                Console.WriteLine("OK, {0} was issued by {1}.", fnameCert, issuerCert);
            else
                Console.WriteLine("ERROR: {0} was NOT issued by {1}.", fnameCert, issuerCert);

            // Get subject name from cert
            s = X509.CertSubjectName(fnameCert, "");
            Console.WriteLine("{0} has subject {1}.", fnameCert, s);
            // and again
            fnameCert = "mycert.cer";
            s = X509.CertSubjectName(fnameCert, "|");
            Console.WriteLine("{0} has subject {1}.", fnameCert, s);

            // Get SHA-1 "thumbprint" (i.e. hash digest) of cert file
            s = X509.CertThumb(fnameCert, HashAlgorithm.Sha1);
            Console.WriteLine("{0} has SHA-1 Thumbprint {1}\n\t--Go on, use CERTMGR and check!", fnameCert, s);

            // Verify a known certificate is still valid
            fnameCert = "CarlRSASelf.cer";
            Console.WriteLine("For certificate '{0}':", fnameCert);
            s = X509.CertIssuedOn(fnameCert);
            Console.WriteLine("Issued at  {0}", s);
            s = X509.CertExpiresOn(fnameCert);
            Console.WriteLine("Expires at {0}", s);
            isok = X509.CertIsValidNow(fnameCert);
            if (isok)
                Console.WriteLine("OK, {0} is valid now.", fnameCert);
            else
                Console.WriteLine("ERROR: {0} is NOT valid now.", fnameCert);
            s = X509.CertIssuerName(fnameCert, ";");
            Console.WriteLine("Issuer Name:   {0}", s);
            s = X509.CertSerialNumber(fnameCert);
            Console.WriteLine("Serial Number: {0}", s);
            s = X509.HashIssuerAndSN(fnameCert, HashAlgorithm.Sha1);
            Console.WriteLine("Hash(IssuerName+SerialNumber) = {0}", s);

            // Make a certificate signing request using our new private key
            prikeyFile = "mykey_epk.bin";
            n = X509.CertRequest("myreq.txt", prikeyFile, 
                "CN=myuser;O=Test Org;C=AU;L=Sydney;S=NSW", "password", 0);
            Console.WriteLine("X509.CertRequest returned {0} (expected 0).", n);

            // Extract the certificates from a PKCS-7 cert chain file
            fnameInput = "bob.p7b";
            // find the number of certs in the chain
            n = X509.GetCertFromP7Chain("", fnameInput, 0);
            Console.WriteLine("X509.GetCertFromP7Chain(0) returns {0} (expected 2).", n);
            // extract the certs in turn
            for (i = 1; i <= n; i++)
            {
                fnameCert = "certfile" + i  + ".cer";
                r = X509.GetCertFromP7Chain(fnameCert, fnameInput, i);
                Debug.Assert(r > 0);
                Console.WriteLine("Extracted certificate '{0}' ({1} bytes)", fnameCert, r);
                // check its subject name
                s = X509.CertSubjectName(fnameCert, "");
                Console.WriteLine("{0} has subject {1}.", fnameCert, s);
            }

            //*****************************
            // CMS (S/MIME OBJECTS) TESTS *
            //*****************************
            Console.WriteLine("CMS (S/MIME OBJECTS) TESTS:");
            // Create a test text file
            fnameInput = "excontent.txt";
            MakeATextFile(fnameInput, "This is some sample content.");
            // Create an enveloped CMS object from Alice to Bob using Bob's X.509 certificate
            fnameOutput = "cmsalice2bob.p7m";
            fnameCert = "BobRSASignByCarl.cer";
            // This should return 1 (indicating one successful recipient)
            n = Cms.MakeEnvData(fnameOutput, fnameInput, fnameCert, 0);
            Console.WriteLine("Cms.MakeEnvData returns {0} (expecting 1)", n);
            Debug.Assert(1 == n, "Cms.MakeEnvData failed");

            // Now try and read it using Bob's private key
            fnameOutput = "cmsalice2bob.p7m.txt";
            fnameInput = "cmsalice2bob.p7m";
            sbPrivateKey = Rsa.ReadEncPrivateKey("BobPrivRSAEncrypt.epk", "password");
            Debug.Assert(sbPrivateKey.ToString().Length > 0, "Unable to read Bob's private key");
            n = Cms.ReadEnvDataToFile(fnameOutput, fnameInput, "", sbPrivateKey.ToString(), 0);
            Console.WriteLine("Cms.ReadEnvData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.ReadEnvData failed");
            s = ReadATextFile(fnameOutput);
            Console.WriteLine("MSG={0}", s);

            // and again but read directly into a string
            s = Cms.ReadEnvDataToString(fnameInput, "", sbPrivateKey.ToString(), 0);
            Console.WriteLine("MSG={0}", s);
            Debug.Assert(s.Length > 0, "Cms.ReadEnvDataToString failed");

            // Generate a BER-encoded CMS signedData object as a file
            // using Alice's private key
            sbPrivateKey = Rsa.ReadEncPrivateKey("AlicePrivRSASign.epk", "password");
            fnameOutput = "BasicSignByAlice.bin";
            fnameInput = "excontent.txt";
            fnameCert = "AliceRSASignByCarl.cer";
            n = Cms.MakeSigData(fnameOutput, fnameInput, fnameCert, sbPrivateKey.ToString(), 0);
            Console.WriteLine("Cms.MakeSigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigData failed");
            
            // again using a string as input instead of a file
            s = ReadATextFile(fnameInput);
            fname = "BasicSignByAlice1.bin";
            n = Cms.MakeSigDataFromString(fname, s, fnameCert, sbPrivateKey.ToString(), 0);
            Console.WriteLine("Cms.MakeSigDataFromString returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigDataFromString failed");

            // Check we got the same result by comparing the files
            b = ReadABinaryFile(fnameOutput);
            bcheck = ReadABinaryFile(fname);
            Debug.Assert(CompareByteArrays(b, bcheck) == true, "SigData files are not identical");

            // Make a detached signature using the message digest hash
            fnameOutput = "DetSignByAlice.bin";
            hexDigest = "406aec085279ba6e16022d9e0629c0229687dd48";
            n = Cms.MakeDetachedSig(fnameOutput, hexDigest, fnameCert, sbPrivateKey.ToString(), 0);
            Console.WriteLine("Cms.MakeDetachedSig returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeDetachedSig failed");

            // Extract the contents from the signed object into another file
            fnameOutput = "excontent_chk.txt";
            fnameInput = "BasicSignByAlice.bin";
            n = Cms.ReadSigDataToFile(fnameOutput, fnameInput, false);
            // returns value is size of file or -ve error code
            Console.WriteLine("Cms.ReadSigDataToFile returns {0} (expecting 28)", n);
            Debug.Assert(n >= 0, "Cms.ReadSigDataToFile failed");
            strCheck = ReadATextFile(fnameOutput);
            Console.WriteLine("MSG(file)={0}", strCheck);

            // Extract the contents directly into a string instead
            s = Cms.ReadSigDataToString(fnameInput, false);
            Console.WriteLine("MSG(string)={0}", s);
            Debug.Assert(String.Compare(s, strCheck)==0, "Contents are different");

            // Make a signed object in base64 format
            fnameOutput = "BasicSignByAlice_64.txt";
            fnameInput = "excontent.txt";
            fnameCert = "AliceRSASignByCarl.cer";
            n = Cms.MakeSigData(fnameOutput, fnameInput, fnameCert, sbPrivateKey.ToString(), Cms.Options.FormatBase64);
            Console.WriteLine("Cms.MakeSigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigData failed");

            // and read back into a string
            fnameInput = fnameOutput;
            s = Cms.ReadSigDataToString(fnameInput, true);
            Console.WriteLine("MSG(string)={0}", s);
            Debug.Assert(String.Compare(s, strCheck)==0, "Contents are different");

            // Make a signed object with signingTime attribute
            fnameOutput = "BasicSignByAlice_attr.bin";
            fnameInput = "excontent.txt";
            fnameCert = "AliceRSASignByCarl.cer";
            n = Cms.MakeSigData(fnameOutput, fnameInput, fnameCert, sbPrivateKey.ToString(), Cms.Options.IncludeAttributes|Cms.Options.AddSignTime);
            Console.WriteLine("Cms.MakeSigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigData failed");

            // Extract and verify the signed hash digest from the signed-data object
            fnameInput = "BasicSignByAlice.bin";
            fnameCert = "AliceRSASignByCarl.cer";
            s = Cms.GetSigDataDigest(fnameInput, fnameCert, false);
            Console.WriteLine("DIG={0}", s);
            Debug.Assert(String.Compare(s, hexDigest)==0, "Hash digests are different");

            // ditto from a file in base64 format but with no signature verification
            fnameInput = "BasicSignByAlice_64.txt";
            s = Cms.GetSigDataDigest(fnameInput, "", true);
            Console.WriteLine("DIG={0}", s);
            Debug.Assert(String.Compare(s, hexDigest)==0, "Hash digests are different");

            // Verify the signature directly
            fnameInput = "BasicSignByAlice.bin";
            n = Cms.VerifySigData(fnameInput, "", "", false);
            Console.WriteLine("Cms.VerifySigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.VerifySigData failed");

            // Get the hash algorithm ID of the signature
            fnameInput = "BasicSignByAlice.bin";
            n = Cms.GetSigHashAlgorithm(fnameInput, fnameCert, false);
            Console.WriteLine("Cms.GetSigHashAlgorithm returns {0} (expecting 0 => SHA-1)", n);
            Debug.Assert(0 == n, "Cms.GetSigHashAlgorithm failed");

            // Try using the wrong certificate
            fnameInput = "BasicSignByAlice.bin";
            fnameCert = "BobRSASignByCarl.cer";
            n = Cms.GetSigHashAlgorithm(fnameInput, fnameCert, false);
            Console.WriteLine("Cms.GetSigHashAlgorithm returns {0} (expecting -ve error code)", n);
            Console.WriteLine("{0};{1}", General.ErrorLookup(n), General.LastError());
            Debug.Assert(n < 0, "Cms.GetSigHashAlgorithm succeeded when should have failed!");

            // Try using a file that's not a valid signed-data object, e.g. a cert
            fnameInput = "CarlRSASelf.cer";
            fnameCert = "AliceRSASignByCarl.cer";
            n = Cms.GetSigHashAlgorithm(fnameInput, fnameCert, false);
            Console.WriteLine("Cms.GetSigHashAlgorithm returns {0} (expecting -ve error code)", n);
            Console.WriteLine("{0};{1}", General.ErrorLookup(n), General.LastError());
            Debug.Assert(n < 0, "Cms.GetSigHashAlgorithm succeeded when should have failed!");

            // Make a signed-data object file using MD5 and in base64 format
            fnameOutput = "BasicSignByAlice_MD5.txt";
            fnameInput = "excontent.txt";
            fnameCert = "AliceRSASignByCarl.cer";
            n = Cms.MakeSigData(fnameOutput, fnameInput, fnameCert, sbPrivateKey.ToString(), 
                Cms.Options.FormatBase64 | Cms.Options.UseMD5);
            Console.WriteLine("Cms.MakeSigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigData failed");

            // Get the hash algorithm ID of the signature
            fnameInput = fnameOutput;
            n = Cms.GetSigHashAlgorithm(fnameInput, fnameCert, true);
            Console.WriteLine("Cms.GetSigHashAlgorithm returns {0} (expecting 1 => MD5)", n);
            Debug.Assert(1 == n, "Cms.GetSigHashAlgorithm failed");

            // Query our SignedData files
            fnameInput = "BasicSignByAlice_attr.bin";
            query = "version";
            s = Cms.QuerySigData(fnameInput, query, false);
            Console.WriteLine("Cms.QuerySigData('{0}') returns '{1}'", query, s);
            Debug.Assert(s.Length > 0, "QuerySigData failed");
            query = "digestAlgorithm";
            s = Cms.QuerySigData(fnameInput, query, false);
            Console.WriteLine("Cms.QuerySigData('{0}') returns '{1}'", query, s);
            Debug.Assert(s.Length > 0, "QuerySigData failed");
            query = "signingTime";
            s = Cms.QuerySigData(fnameInput, query, false);
            Console.WriteLine("Cms.QuerySigData('{0}') returns '{1}'", query, s);
            Debug.Assert(s.Length > 0, "QuerySigData failed");

            // ...that's enough CMS tests.

            //*************
            // HASH TESTS *
            //*************
            Console.WriteLine("HASH DIGEST TESTS:");
            s = Hash.HexFromString("abc", HashAlgorithm.Sha1);
            Console.WriteLine("SHA-1('abc')  ={0}", s);
            s = Hash.HexFromString("abc", HashAlgorithm.Md5);
            Console.WriteLine("MD5('abc')    ={0}", s);
            s = Hash.HexFromString("abc", HashAlgorithm.Md2);
            Console.WriteLine("MD2('abc')    ={0}", s);
            s = Hash.HexFromString("abc", HashAlgorithm.Sha256);
            Console.WriteLine("SHA-256('abc')={0}", s);
            // Create a test file
            fnameInput = "hello.txt";
            MakeATextFile(fnameInput, "hello world\r\n");
            // get digest from binary file
            s = Hash.HexFromFile(fnameInput, HashAlgorithm.Sha1);
            Console.WriteLine("SHA1('hello world+CR+LF')={0}", s);
            // and again treating CR-LF as a single LF
            // (we use this when moving between Unix and Windows systems)
            s = Hash.HexFromTextFile(fnameInput, HashAlgorithm.Sha1);
            Console.WriteLine("SHA1('hello world+LF')=   {0}", s);

            //******************
            // WIPE DATA TESTS *
            //******************
            Console.WriteLine("WIPE DATA TESTS:");
            fname = "ImportantSecret.txt";
            MakeATextFile(fname, "Very important secrets here.");
            s = ReadATextFile(fname);
            isok = Wipe.File(fname);
            Console.WriteLine("Wipe.File {0}", (isok ? "OK" : "FAILED!"));

            b = System.Text.Encoding.Default.GetBytes("Secret data");
            Console.WriteLine("Before Wipe.Data, b = [{0}]", System.Text.Encoding.Default.GetString(b));
            Wipe.Data(b);
            Console.WriteLine("After Wipe.Data,  b = [{0}]", System.Text.Encoding.Default.GetString(b));

            Console.WriteLine("Before Wipe.String, sbPrivateKey contains {0} characters.", sbPrivateKey.Length);
            Wipe.String(sbPrivateKey);
            Console.WriteLine("After Wipe.String, sbPrivateKey = [{0}]", sbPrivateKey.ToString());

            //*******************
            // CONVERSION TESTS *
            //*******************
            // Convert an "impure" hex string of 16 bytes
            s = "30:21:30:09:06:05:2B:0E:03:02:1A:05:00:04:14:00";
            b = Cnv.FromHex(s);
            Console.WriteLine("Cnv.FromHex('{0}')={1} ({2} bytes)", s, Cnv.ToHex(b), b.Length);
            Debug.Assert(16==b.Length, "Cnv.FromHex failed: wrong # of bytes");

            // Given a string that includes some Latin-1 accented characters:
            s = "abcóéíáñ";
            Console.WriteLine("Original Latin-1 string={0}", s);
            Console.WriteLine("# of characters in Latin-1 string  = {0}", s.Length);
            // Check and see if this tests as valid UTF-8
            n = Cnv.CheckUTF8(s);
            Console.WriteLine("Cnv.CheckUTF8(s) returns {0} (expected 0 => not valid UTF-8)", n);

            //************************
            // PASSWORD PROMPT TESTS *
            //************************
        /* -- uncomment to test (these are tedious on repetitive tests!)
            s = Pwd.Prompt(32, "My caption for the dialog here");
            Console.WriteLine("Password=[{0}]", s);
            s = Pwd.Prompt(32, "My caption for the dialog here", "My new prompt:");
            Console.WriteLine("Password=[{0}]", s);
        */
            //************
            // RNG TESTS *
            //************
            Console.WriteLine("SOME RANDOM NUMBERS:");
            for (i = 0; i < 3; i++)
            {
                b = Rng.Bytes(24);
                Console.WriteLine("RNG={0}", Cnv.ToHex(b));
            }
            Console.Write("{x:990<=x<=1000}=");
            for (i = 0; i < 5; i++)
            {
                n = Rng.Number(990, 1000);
                Console.Write("{0} ", n);
            }
            Console.Write("\n");

            Console.WriteLine("\nALL TESTS COMPLETED.");
        }

        //*****************
        // FILE UTILITIES *
        //*****************
        static bool MakeATextFile(string fileName, string data)
        {
            FileStream fs;
            StreamWriter sw;
            // Create a test text file
            fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
            sw = new StreamWriter(fs);
            sw.Write(data);
            sw.Close();
            fs.Close();
            return true;
        }

        static string ReadATextFile(string fileName)
        {
            string s = String.Empty;
            FileInfo finfo = new FileInfo(fileName);
            if (finfo.Exists)
            {
                FileStream fsi = finfo.OpenRead();
                StreamReader sr = new StreamReader(fsi);
                s = sr.ReadToEnd();
                Console.WriteLine(s);
                sr.Close();
                fsi.Close();
            }
            Debug.Assert(finfo.Exists, "File '" + fileName + "' does not exist.");
            return s;
        }

        static byte[] ReadABinaryFile(string fileName)
        {
            byte[] b = new byte[0];
            FileInfo finfo = new FileInfo(fileName);
            if (finfo.Exists)
            {
                FileStream fsi = finfo.OpenRead();
                BinaryReader br = new BinaryReader(fsi);
                int count = (int)fsi.Length;
                b = br.ReadBytes(count);
                br.Close();
                fsi.Close();
            }
            Debug.Assert(finfo.Exists, "File '" + fileName + "' does not exist.");
            return b;
        }

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

        static bool FileIsNotPresent(string filePath, string message)
        {
            if (!FileExists(filePath))
            {
                Console.WriteLine("\n{0}: {1}", message, filePath);
                return true;
            }
            return false;
        }

        public static bool CompareByteArrays (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;
        }
    }
}