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: 2012-01-14 12:09:00 $
 *   $Version: 3.8.0 $
 */

/* This is a Console Application written for target .NET Framework 2.0 and above */

/* NOTE: This program requires the following files to exist in the 
 * same directory as the executable (these are in pkiDotNetTestFiles.zip):
 *  CarlRSASelf.cer
 *  CarlRSASign.epk
 *  AliceRSASignByCarl.cer
 *  AlicePrivRSASign.epk
 *  BobRSASignByCarl.cer
 *  BobPrivRSAEncrypt.epk (NOTE: the .EPK extension is one we made up)
 *  bob.p7b
 *  rfc3280bis_cert1.cer
 *  rfc3280bis_cert2.cer
 *  rfc3280bis_CRL.crl
 *  dims.cer
 *  UTNUSERFirst-Object.cer
 *  ocsp_response_ok_dims.dat
 */
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, k;
      byte[] b;
      bool isok;
      byte[] msg;
      byte[] bcheck;
      StringBuilder sbPrivateKey;
      StringBuilder sbPublicKey;
      string keyhex, plainStr, cipherStr, 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, certBase64, distname;
      string query;
      StringBuilder sbKeyCheck;
      string cert1, cert2, certList, certFile, crlFile, snStr, csrFile;
            string extns, dn, password, xmlKey, msgStr, keyStr, dateStr;
            int keyBits;
            string subdir;
            string b64str, s1;
            byte[] b1;
            long flen;
            // Required test files
            string[] arrFileNames = new string[] 
            { 
                "AlicePrivRSASign.epk", 
                "AliceRSASignByCarl.cer", 
                "bob.p7b", 
                "BobPrivRSAEncrypt.epk", 
                "BobRSASignByCarl.cer",
                "CarlPrivRSASign.epk",
                "CarlRSASelf.cer",
                "rfc3280bis_cert1.cer",
                "rfc3280bis_cert2.cer",
                "rfc3280bis_CRL.crl",
                "dims.cer",
                "UTNUSERFirst-Object.cer",
                "ocsp_response_ok_dims.dat",
            }; 
      
      // Handle arguments
      bool doPrompt = false;
            bool askDelete = false;
            bool doBigFile = false;
      for (int iarg = 0; iarg < args.Length; iarg++)
      {
        if (args[iarg] == "prompt")
          doPrompt = true;
                if (args[iarg] == "askdelete")
                    askDelete = true;
                if (args[iarg] == "bigfile")
                    doBigFile = true;
            }

      //****************
      // 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);
            n = General.IsWin64();
            Console.WriteLine("IsWin64={0}", n);
            s = General.Platform();
            Console.WriteLine("Platform={0}", s);
            s = CryptoSysPKI.General.LastError();
      Console.WriteLine("LastError='{0}' (expecting empty)", s);
      n = CryptoSysPKI.General.PowerUpTests();
      Console.WriteLine("PowerUpTests={0} (expecting 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.\n Look in pkiDotNetTestFiles.zip";
            foreach (string fn in arrFileNames)
            {
                if (FileIsNotPresent(fn, missingFile)) return;
            }

            //*************************************************
            // 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());

            // Now do the tests...

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

      // Ditto using byte arrays
      arrPlain = Cnv.FromHex(plainStr);
      arrCipher = Cnv.FromHex(cipherStr);
      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";
      plainStr = "5468697320736F6D652073616D706520636F6E74656E742E0808080808080808";
      cipherStr = "d76fd1178fbd02f84231f5c1d2a2f74a4159482964f675248254223daf9af8e4";
      s = Tdea.Encrypt(plainStr, keyhex, Mode.CBC, ivhex);
      Console.WriteLine("KY={0}",keyhex);
      Console.WriteLine("IV={0}",ivhex);
      Console.WriteLine("PT={0}",plainStr);
      Console.WriteLine("CT={0}",s);
      Console.WriteLine("OK={0}",cipherStr);
      Debug.Assert(String.Compare(s, cipherStr, true)==0, "Tdea.Encrypt{Hex,CBC} failed");
      // Decrypt
      s = Tdea.Decrypt(cipherStr, keyhex, Mode.CBC, ivhex);
      Console.WriteLine("P'={0}",s);
      Console.WriteLine("OK={0}",plainStr);
      Debug.Assert(String.Compare(s, plainStr, true)==0, "Tdea.Decrypt{Hex,CBC} failed");

      // Ditto using byte arrays
      arrPlain = Cnv.FromHex(plainStr);
      arrCipher = Cnv.FromHex(cipherStr);
      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);
      // [v3.0] This test does not work anymore: 
      // the internal strings are always different even for the same key
      //Debug.Assert(String.Compare(s, sbPrivateKey.ToString()) == 0, 
      //  "Private key string from XML does not match original");

      // Create a PKCS-12 file containing Alice's private key
            // Updated [v3.8] Default is now with "standard" encrypted cert
      fname = "alice.pfx";
      n = Pfx.MakeFile(fname, "AliceRSASignByCarl.cer", 
        "AlicePrivRSASign.epk", "password", "Alice's Key", Pfx.Options.Default);
      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 into a new PKCS-8 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");
            // Use the KeyHashCode - don't try and compare internal strings directly
            Debug.Assert(Rsa.KeyHashCode(strCheck)==Rsa.KeyHashCode(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();
      s = Rsa.ReadPrivateKeyInfo(fname).ToString();
      // Use the HashCode of the internal strings
      Console.WriteLine("Rsa.KeyHashCode(old)={0,8:X}", Rsa.KeyHashCode(strCheck));
      Console.WriteLine("Rsa.KeyHashCode(new)={0,8:X}", Rsa.KeyHashCode(s));
      Debug.Assert(Rsa.KeyHashCode(strCheck)==Rsa.KeyHashCode(s), "Private keys don't match");

            // Read the private key directly from the PFX file [new in v3.8]
      fname = "alice.pfx";
            sbPrivateKey = Rsa.ReadPrivateKeyFromPFX(fname, "password");
            Console.WriteLine("Rsa.ReadPrivateKeyFromPFX returns a string {0} characters long", sbPrivateKey.ToString().Length);
            Debug.Assert(sbPrivateKey.ToString().Length > 0, "Rsa.ReadPrivateKeyFromPFX failed");
            Console.WriteLine("Rsa.KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbPrivateKey.ToString()));
            
            // Convert this to a public key string [new in v3.8]
            sbPublicKey = Rsa.PublicKeyFromPrivate(sbPrivateKey);
            Console.WriteLine("Rsa.PublicKeyFromPrivate returns a string {0} characters long", sbPublicKey.ToString().Length);
            Debug.Assert(sbPublicKey.ToString().Length > 0, "Rsa.PublicKeyFromPrivate failed");
            Console.WriteLine("Rsa.KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbPublicKey.ToString()));
            Debug.Assert(Rsa.KeyHashCode(sbPublicKey.ToString()) == Rsa.KeyHashCode(sbPrivateKey.ToString()), 
                "Public and 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();
      s = Rsa.ReadPublicKey(fname).ToString();
      // Use the HashCode of the internal strings
      Console.WriteLine("Rsa.KeyHashCode(old)={0,8:X}", Rsa.KeyHashCode(strCheck));
      Console.WriteLine("Rsa.KeyHashCode(new)={0,8:X}", Rsa.KeyHashCode(s));
      Debug.Assert(Rsa.KeyHashCode(strCheck)==Rsa.KeyHashCode(s), "Public keys don't match");
      // NOTE: [v3.0] This comparison of internal strings will no longer work.
      // Debug.Assert(strCheck == s, "Public keys don't match");

            // Create a PKCS-12 file excluding Alice's private key; i.e. just the cert
            fname = "alice-nokey.pfx";
            n = Pfx.MakeFile(fname, "AliceRSASignByCarl.cer",
                "", "", "Alice's Cert", Pfx.Options.PlainCert);
            Console.WriteLine("Pfx.MakeFile (no key) returns {0} (expected 0)", n);
            Debug.Assert(0 == n, "Failed to create PFX file (no key)");

            // Extract the certificate from a PKCS-12 PFX file
      fnameInput = "alice.pfx";
      fnameCert = "alice_fromPFX.cer";
      n = X509.GetCertFromPFX(fnameCert, fnameInput, "password");
      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);

      // [New v3.1] Show that a private and public key string are matched
      // Read in the private key from the encrypted key file
      sbPrivateKey = Rsa.ReadEncPrivateKey("AlicePrivRSASign.epk", "password");
      Debug.Assert(sbPrivateKey.Length > 0, "Failed to read in private key");
      // Read the public key from the recipient's X.509 certificate
      sbPublicKey = Rsa.GetPublicKeyFromCert("AliceRSASignByCarl.cer");
      Debug.Assert(sbPublicKey.Length > 0, "Failed to read in public key");
      n = Rsa.KeyMatch(sbPrivateKey.ToString(), sbPublicKey.ToString());
      Console.WriteLine("Rsa.KeyMatch(PRIV_alice,PUB_alice) returned {0} (expected 0)", n);
      Debug.Assert(n == 0, "Rsa.KeyMatch failed");
      // Try a pair that are not matched: use Alice's private key and Bob's public
      sbPublicKey = Rsa.GetPublicKeyFromCert("BobRSASignByCarl.cer");
      n = Rsa.KeyMatch(sbPrivateKey.ToString(), sbPublicKey.ToString());
      Console.WriteLine("Rsa.KeyMatch(PRIV_alice,PUB_bob) returned {0} [{1}] (expected -ve)", n, General.ErrorLookup(n));
      Debug.Assert(n != 0, "Rsa.KeyMatch failed");

      // 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);
      
      // Extract details from a certificate
      fnameCert = "CarlRSASelf.cer";
      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);
      
      // Query the certificate for various details
      fnameCert = "CarlRSASelf.cer";
      Console.WriteLine("For X.509 certificate '{0}'", fnameCert);
      query = "version";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "signatureAlgorithm";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "sigAlgID";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "notBefore";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "notAfter";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "issuerName";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "subjectName";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "subjectPublicKeyAlgorithm";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);
      query = "isCA";
      s = X509.QueryCert(fnameCert, query);
      Console.WriteLine("X509.QueryCert('{0}') = {1}", query, s);

      // Read in certificate data from a file into a base64-encoded string
      fnameCert = "CarlRSASelf.cer";
      certBase64 = X509.ReadStringFromFile(fnameCert);
      Console.WriteLine("X.509 certificate '{0}' as a string=\n{1}", fnameCert, certBase64);
      Debug.Assert(certBase64.Length > 0, "X509.ReadStringFromFile failed");

      // Write back string as a new PEM-format certificate file
      fnameCheck = fnameCert + ".copy.pem.cer";
      n = X509.SaveFileFromString(fnameCheck, certBase64, true);
      Console.WriteLine("X509.SaveFileFromString('{0}') returns {1} (expecting 0)", fnameCheck, n);
      
      // Read in string from new file as a check
      strCheck = X509.ReadStringFromFile(fnameCheck);
      if (strCheck == certBase64)
        Console.WriteLine("OK, strings from new and old X.509 files are identical.");
      else
        Console.WriteLine("ERROR: Strings from new and old X.509 files do not match");
      Debug.Assert(strCheck == certBase64, "X509.SaveFileFromString failed");

      // And check the "thumbprints" of the two certificates
      s = X509.CertThumb(fnameCert, HashAlgorithm.Sha1);
      strCheck = X509.CertThumb(fnameCheck, HashAlgorithm.Sha1);
      Console.WriteLine("Thumbprint('{0}')={1}", fnameCert, s);
      Console.WriteLine("Thumbprint('{0}')={1}", fnameCheck, strCheck);
      Debug.Assert(strCheck == s, "X509.CertThumb results failed");

      // [New v3.1] Use base64 string instead of certificate filename in X.509 fn
      s = X509.CertThumb(certBase64, HashAlgorithm.Sha1);
      Console.WriteLine("Thumbprint('{0}...{1}')={2}", 
        certBase64.Substring(0,5), certBase64.Substring(certBase64.Length-5,5), 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);
      }

      // [New v3.1] Bob creates a self-signed cert using his Chinese nickname, Ben
      fnameCert = "benChina.cer";
      prikeyFile = "BobPrivRSAEncrypt.epk";
      // Set name using UTF-8-encoded chinese characters:
      // CN=ben (U+672C)
      // C= zhong guo (U+4E2D, U+570B)
      // OU=zong ju (U+7E3D, U+5C40)
      
      distname = "CN=#xe69cac;C=#xe4b8ade59c8b;OU=#xe7b8bde5b180";
      n = X509.MakeCertSelf(fnameCert, prikeyFile, 0x888, 8, distname, "ben@ho.com.cn", 
        X509.KeyUsageOptions.DigitalSignature | X509.KeyUsageOptions.KeyCertSign, 
        "password", X509.Options.UTF8String);
      Console.WriteLine("X509.MakeCertSelf(chinese) returned {0}", n);
      Debug.Assert(n == 0, "Failed to create new certificate");

      // Read in the distinguished name from our new certificate
      s = X509.CertSubjectName(fnameCert, "");
      Console.WriteLine("Subject name is '{0}'", s);

      // Ben then creates a certificate for a Mexican user 
      //(using the public key we created above and his chinese certificate!)
      issuerCert = "benChina.cer";
      fnameCert = "mariaMexico.cer";
      // passing the UTF-8 characters for the name in [v3.1] hex format
      // C=México;CN=María;OU=#xabc (yes, we want the OU to be "#xabc")
      distname = "C=#x4de97869636f;CN=#x4d6172ed61;OU=##xabc";
      n = X509.MakeCert(fnameCert, issuerCert, pubkeyFile, prikeyFile, 
        7, 2, distname, "", 0, "password", X509.Options.UTF8String);
      Console.WriteLine("X509.MakeCertSelf(mexican) returned {0}", n);
      Debug.Assert(n == 0, "Failed to create new certificate");

      // Now read in the Subject's name using QueryCert
      // forcing the name to be converted back to Latin-1 encoding
      s = X509.QueryCert(fnameCert, "subjectName", X509.Options.Latin1);
      Console.WriteLine("Subject's name is '{0}'", s);
      Debug.Assert(s.Length > 0, "Failed to read subjectName");

            // [new in v3.3] Make an end-user cert identical to RFC4134 AliceRSASignByCarl.cer 
            // First we need to extract the public key from the cert (as though we were startng with it)
            pubkeyFile = "AlicePubRSA.pub";
            sbPublicKey = Rsa.GetPublicKeyFromCert("AliceRSASignByCarl.cer");
            Debug.Assert(sbPublicKey.Length > 0, "Failed to get public key from cert");
            r = Rsa.SavePublicKey(pubkeyFile, sbPublicKey.ToString(), Rsa.Format.Default);
            // Uses Alice's public key; signed by Carl
            issuerCert = "CarlRSASelf.cer";
            prikeyFile = "CarlPrivRSASign.epk"; 
            password = "password";
            dn = "CN=AliceRSA";
            extns = "rfc822name=AliceRSA@example.com;" +
              "serialNumber=46346BC7800056BC11D36E2EC410B3B0;" +
              "subjectKeyIdentifier=77D2B4D1B74C8A8AA3CE459DCEEC3CA03AE3FF50;" +
              "notBefore=1999-09-19T01:08:47;" +
              "notAfter=2039-12-31;";
            X509.KeyUsageOptions kuo = X509.KeyUsageOptions.DigitalSignature | X509.KeyUsageOptions.NonRepudiation;
            fnameCert = "AliceRSA-dup.cer";
            r = X509.MakeCert(fnameCert, issuerCert, pubkeyFile, prikeyFile, 0, 0, dn, extns, 
                kuo, password, X509.Options.AuthKeyId);
            Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.MakeCert failed");
            Console.WriteLine("Created cert file {0}", fnameCert);
            // Now check we got the same as our original
            hexDigest = X509.CertThumb(fnameCert, HashAlgorithm.Sha1);
            Console.WriteLine("{1}=SHA-1({0})", fnameCert, hexDigest);
            fnameCheck = "AliceRSASignByCarl.cer";
            strCheck = X509.CertThumb(fnameCheck, HashAlgorithm.Sha1);
            Console.WriteLine("{1}=SHA-1({0})", fnameCheck, strCheck);
            Debug.Assert(hexDigest == strCheck, "Digests are not equal");

            // [new in v3.5] Create a new certificate using a PKCS-10 certificate signing request (CSR)
            // instead of using a subjectPublicKey file and distininguished name.
            Console.WriteLine("Create a new certificate using a PKCS-10 CSR file...");
            csrFile = "myreq.txt";
            X509.TextDump("dump.txt", csrFile);
            PrintATextFile("dump.txt");
            fnameCert = "mycert-fromCSR.cer";
            issuerCert = "CarlRSASelf.cer";
            prikeyFile = "CarlPrivRSASign.epk";
            password = "password";
            extns = "notBefore=2010-01-01T12:00";
            // Make an end-user cert valid for 4 years from 2010-01-01 using PKCS#10 CSR file.
            // Note that we pass the name of the CSR file instead of the subjectPublicKey file
            // and pass the empty string for distName to flag that we have a CSR file.
            r = X509.MakeCert(fnameCert, issuerCert, csrFile, prikeyFile, 0x109, 4, "", extns, 0, password, 0);
            Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.MakeCert failed");
            Console.WriteLine("Created cert file {0}", fnameCert);
            X509.TextDump("dump.txt", fnameCert);
            PrintATextFile("dump.txt");

            // [new in v3.5] Validate certificate paths either in a P7 cert chain file or in a list of certs
            // with and without a trusted certificate
            fname = "bob.p7b";
            Console.WriteLine("Validate certificate path in file {0}...", fname);
            r = X509.ValidatePath(fname);
            Console.WriteLine("X509.ValidatePath returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.ValidatePath failed");

            certList = "BobRSASignByCarl.cer;CarlRSASelf.cer";
            certFile = "CarlRSASelf.cer";
            Console.WriteLine("Validate certificate path in list '{0}' using trusted cert {1}...", certList, certFile);
            r = X509.ValidatePath(certList, certFile, false);
            Console.WriteLine("X509.ValidatePath returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.ValidatePath failed");

            //******************************************
            // CRL (CERTIFICATE REVOCATION LIST) TESTS *
            //******************************************
            // [new in v3.5]
            Console.WriteLine("CRL (CERTIFICATE REVOCATION LIST) TESTS:");
            // Carl creates a Certificate Revocation List (CRL)
            // -- A CRL dated with the current system time
            crlFile = "CarlsNew.crl";
            issuerCert = "CarlRSASelf.cer";
            prikeyFile = "CarlPrivRSASign.epk";
            password = "password";
            certList = "1,2007-12-31; 2, 2009-12-31T12:59:59Z; 66000,2066-01-01; #x0102deadbeef,2010-02-28T01:01:59";
            r = X509.MakeCRL(crlFile, issuerCert, prikeyFile, password, certList, "", 0);
            Console.WriteLine("X509.MakeCRL returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.MakeCRL failed");
            Console.WriteLine("Created CRL file {0}", crlFile);

            // -- A CRL using specified times (NB these are GMT times, not local)
            // -- with an empty revocation list and using sha256WithRSAEncryption to sign
            crlFile = "Carl_20100401.crl";
            extns = "thisUpdate=2010-04-01T12:00;nextUpdate=2010-05-01";
            r = X509.MakeCRL(crlFile, issuerCert, prikeyFile, password, "", extns, X509.Options.SigAlg_Sha256WithRSAEncryption);
            Console.WriteLine("X509.MakeCRL returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.MakeCRL failed");
            Console.WriteLine("Created CRL file {0}", crlFile);

            // Show we can now use VerifyCert to check the signature in a CRL
            r = X509.VerifyCert(crlFile, issuerCert);
            Console.WriteLine("X509.VerifyCert returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.VerifyCert failed");
            Console.WriteLine("OK, CRL file {0} was signed by owner of {1}", crlFile, issuerCert);

            // Check if a given cert is in a CRL
            // Use test CRL and certs from RFC3280
            crlFile = "rfc3280bis_CRL.crl";
            // This cert has not been revoked -- expected result = zero
            certFile = "rfc3280bis_cert1.cer";
            Console.WriteLine("CRL ={0}", crlFile);
            Console.WriteLine("Cert={0}", certFile);
            r = X509.CheckCertInCRL(certFile, crlFile, "", "");
            Console.WriteLine("X509.CheckCertInCRL returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.CheckCertInCRL failed");
            if (X509.Revoked == r)
                Console.WriteLine("CERT HAS BEEN REVOKED");
            else if (0 == r)
                Console.WriteLine("Cert has not been revoked");
            else
                Console.WriteLine("ERROR: {0}: {1}", General.ErrorLookup(r), General.LastError());

            // This cert HAS been revoked -- expected result = +1
            certFile = "rfc3280bis_cert2.cer";
            Console.WriteLine("CRL ={0}", crlFile);
            Console.WriteLine("Cert={0}", certFile);
            r = X509.CheckCertInCRL(certFile, crlFile, "", "");
            Console.WriteLine("X509.CheckCertInCRL returns {0} (expecting 0)", r);
            Debug.Assert(r == 1, "X509.CheckCertInCRL failed");
            if (X509.Revoked == r)
                Console.WriteLine("CERT HAS BEEN REVOKED");
            else if (0 == r)
                Console.WriteLine("Cert has not been revoked");
            else
                Console.WriteLine("ERROR: {0}: {1}", General.ErrorLookup(r), General.LastError());

            // But the same cert was not revoked as at 15:00 GMT on 19 November 2004 -- expected result = 0
            certFile = "rfc3280bis_cert2.cer";
            dateStr = "2004-11-19T15:00Z";
            Console.WriteLine("CRL ={0}", crlFile);
            Console.WriteLine("Cert={0}", certFile);
            Console.WriteLine("Date={0}", dateStr);
            r = X509.CheckCertInCRL(certFile, crlFile, "", dateStr);
            Console.WriteLine("X509.CheckCertInCRL returns {0} (expecting 0)", r);
            Debug.Assert(r == 0, "X509.CheckCertInCRL failed");
            if (X509.Revoked == r)
                Console.WriteLine("CERT HAS BEEN REVOKED");
            else if (0 == r)
                Console.WriteLine("Cert has not been revoked");
            else
                Console.WriteLine("ERROR: {0}: {1}", General.ErrorLookup(r), General.LastError());

            //**************************************************
            // OCSP (ONLINE CERTIFICATE STATUS PROTOCOL) TESTS *
            //**************************************************
            // [new in v3.5]
            Console.WriteLine("OCSP (ONLINE CERTIFICATE STATUS PROTOCOL) TESTS:");
            // Creates an OCSP request to check our own current code signing certificate file dims.cer. 
            // This was issued by the holder of certificate in the file UTNUSERFirst-Object.cer.
            issuerCert = "UTNUSERFirst-Object.cer";
            certFile = "dims.cer";
            Console.WriteLine("IssuerFile={0}", issuerCert);
            Console.WriteLine("CertFile={0}", certFile);
            s = Ocsp.MakeRequest(issuerCert, certFile, 0);
            Debug.Assert(s.Length > 0, "Ocsp.MakeRequest failed");
            Console.WriteLine("OCSPRequest={0}", s);

            // Pass a hex serial number instead of filename
            snStr = "#x 00 FB C7 23 22 8C 8C 80 22 D8 85 92 23 DE E7 06 60";
            Console.WriteLine("Cert SerialNumber={0}", snStr);
            s = Ocsp.MakeRequest(issuerCert, snStr, 0);
            Debug.Assert(s.Length > 0, "Ocsp.MakeRequest failed");
            Console.WriteLine("OCSPRequest={0}", s);

            // We use a response received from ocsp.usertrust.com for our own code signing certificate 
            // (this HAS been signed by our cert's issuer)
            fname = "ocsp_response_ok_dims.dat";
            issuerCert = "UTNUSERFirst-Object.cer";
            Console.WriteLine("ResponseFile={0}", fname);
            Console.WriteLine("IssuerFile={0}", issuerCert);
            s = Ocsp.ReadResponse(fname, issuerCert);
            Debug.Assert(s.Length > 0, "Ocsp.ReadResponse failed");
            Console.WriteLine("OCSPResponse={0}", 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");

            // Try the BigFile option [new in v3.7]
            if (doBigFile)
            {
                fname = "bigfile.txt";
                flen = MakeALargeTextFile(fname);
                Console.WriteLine("Created *big* file '{0}' of length {1} bytes", fname, flen);
                // Create an enveloped CMS object from Alice to Bob using Bob's X.509 certificate
                fnameInput = fname;
                fnameOutput = "cmsalice2bob-big.p7m";
                fnameCert = "BobRSASignByCarl.cer";
                // Call MakeEnvData using BigFile option (may be a noticable delay here)
                Console.WriteLine("About to envelope using BigFile option...");
                n = Cms.MakeEnvData(fnameOutput, fnameInput, fnameCert, Cms.Options.BigFile);
                Console.WriteLine("Cms.MakeEnvData returns {0} (expecting 1)", n);
                Debug.Assert(1 == n, "Cms.MakeEnvData failed");
                
                // Now try and decrypt using Bob's private key (which we already have from above)
                fnameInput = fnameOutput;
                fnameOutput = fnameInput + ".chk.txt";
                // Use BigFile option
                Console.WriteLine("About to decrypt using BigFile option...");
                n = Cms.ReadEnvDataToFile(fnameOutput, fnameInput, "", sbPrivateKey.ToString(), Cms.Options.BigFile);
                Console.WriteLine("Cms.ReadEnvData returns {0} (expecting 0)", n);
                Console.WriteLine("Decrypted file '{0}' is {1} bytes long.", fnameOutput, FileLength(fnameOutput));
                Debug.Assert(0 == n, "Cms.ReadEnvData 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);
      // 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);
      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);
      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);
      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, "");
      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);
      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);
      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);
      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);
      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
            // [v3.7] new option for HashAlgorithm
      fnameOutput = "BasicSignByAlice_MD5.txt";
      fnameInput = "excontent.txt";
      fnameCert = "AliceRSASignByCarl.cer";
      n = Cms.MakeSigData(fnameOutput, fnameInput, fnameCert, sbPrivateKey.ToString(), 
        HashAlgorithm.Md5, Cms.SigDataOptions.FormatBase64);
      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);
      Console.WriteLine("Cms.GetSigHashAlgorithm returns {0} (expecting 1 => MD5)", n);
      Debug.Assert(1 == n, "Cms.GetSigHashAlgorithm failed");

            // Added new overload v3.7 [2011-06-27]
            // Make a signed-data object file using SHA-256
            fnameOutput = "BasicSignByAlice_SHA256.bin";
            fnameInput = "excontent.txt";
            fnameCert = "AliceRSASignByCarl.cer";
            n = Cms.MakeSigData(fnameOutput, fnameInput, fnameCert, sbPrivateKey.ToString(), HashAlgorithm.Sha256, 0);
            Console.WriteLine("Cms.MakeSigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigData failed");

            // Get the hash algorithm used in the signature
            fnameInput = fnameOutput;
            s = Cms.QuerySigData(fnameInput, "digestAlgorithm");
            Console.WriteLine("digestAlgorithm={0}", s);

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

            // Again using a string as input instead of a file 
            // Use SHA-224 and include signed attributes with sign-time 
            s = ReadATextFile("excontent.txt");
            fname = "BasicSignByAlice_224.bin";
            n = Cms.MakeSigDataFromString(fname, s, fnameCert, sbPrivateKey.ToString(),
                HashAlgorithm.Sha224, Cms.SigDataOptions.IncludeAttributes | Cms.SigDataOptions.AddSignTime);
            Console.WriteLine("Cms.MakeSigDataFromString returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigDataFromString failed");

            // Query the signature file we've just made
            fnameInput = fname;
            Console.WriteLine("For file '{0}':", fnameInput);
            query = "digestAlgorithm";
            s = Cms.QuerySigData(fnameInput, query);
            Console.WriteLine("{0}={1}", query, s);
            query = "HASsignedAttributes";
            s = Cms.QuerySigData(fnameInput, query);
            Console.WriteLine("{0}={1}", query, s);
            query = "signingTime";
            s = Cms.QuerySigData(fnameInput, query);
            Console.WriteLine("{0}={1}", query, s);

            // Verify the signature
            n = Cms.VerifySigData(fnameInput);
            Console.WriteLine("Cms.VerifySigData returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.VerifySigData failed");
            
            // Added v3.4.1 [2010-02-23]
            // Create a "certs-only" SignedData file == certificate chain file
            fnameOutput = "Alice_new.p7c";
            certList = "AliceRSASignByCarl.cer" +";" + "CarlRSASelf.cer";
            //n = Cms.MakeSigData(fnameOutput, "", certList, "", (Cms.Options)0x0400);
            n = Cms.MakeSigData(fnameOutput, "", certList, "", Cms.Options.CertsOnly);
            Console.WriteLine("Cms.MakeSigData(certs-only) returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Cms.MakeSigData failed");

      // Query our SignedData files
      fnameInput = "BasicSignByAlice_attr.bin";
      query = "version";
      s = Cms.QuerySigData(fnameInput, query);
      Console.WriteLine("Cms.QuerySigData('{0}') returns '{1}'", query, s);
      Debug.Assert(s.Length > 0, "QuerySigData failed");
      query = "digestAlgorithm";
      s = Cms.QuerySigData(fnameInput, query);
      Console.WriteLine("Cms.QuerySigData('{0}') returns '{1}'", query, s);
      Debug.Assert(s.Length > 0, "QuerySigData failed");
      query = "signingTime";
      s = Cms.QuerySigData(fnameInput, query);
      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')=\n{0}", s);
      s = Hash.HexFromString("abc", HashAlgorithm.Sha384);
      Console.WriteLine("SHA-384('abc')=\n{0}", s);
      s = Hash.HexFromString("abc", HashAlgorithm.Sha512);
      Console.WriteLine("SHA-512('abc')=\n{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);

      //*************
      // HMAC TESTS *
      //*************
      Console.WriteLine("HMAC TESTS:");
      //  Test case 2 from RFC 2202 and RFC 4231
      //  key =           "Jefe"
      //  key_len         4
      //  data =          "what do ya want for nothing?"
      //  data_len =      28
      
      Console.WriteLine("Test case 2 from RFC 2202 and RFC 4231...");
      // Convert strings to byte arrays
      arrKey = System.Text.Encoding.Default.GetBytes("Jefe");
      msg = System.Text.Encoding.Default.GetBytes("what do ya want for nothing?");
      // Compute HMAC-SHA-1 then check against known test vector
      s = Hmac.HexFromBytes(msg, arrKey, HashAlgorithm.Sha1);
      Console.WriteLine("HMAC-SHA-1('WDYWFN?','Jefe')={0}", s);
      strCheck = "effcdf6ae5eb2fa2d27416d5f184df9c259a7c79";
      Console.WriteLine("CORRECT=                     {0}", strCheck);
      Debug.Assert(String.Compare(strCheck, s, true)==0, "HMAC does not match test vector");
      // Compute HMAC-SHA-256 then check against known test vector
      s = Hmac.HexFromBytes(msg, arrKey, HashAlgorithm.Sha256);
      Console.WriteLine("HMAC-SHA-256('WDYWFN?','Jefe')=\n{0}", s);
      strCheck = "5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843";
      Console.WriteLine("CORRECT=\n{0}", strCheck);
      Debug.Assert(String.Compare(strCheck, s, true)==0, "HMAC does not match test vector");

      //  Test case 4 from RFC 2202 and RFC 4231
      //  key =           0x0102030405060708090a0b0c0d0e0f10111213141516171819
      //  key_len         25
      //  data =          0xcd repeated 50 times
      //  data_len =      50
      
      Console.WriteLine("Test case 4 from RFC 2202 and RFC 4231...");
      arrKey = new byte[25];
      for (i = 0; i < 25; i++)
        arrKey[i] = (byte)(i+1);
      Console.WriteLine("Key={0}", Cnv.ToHex(arrKey));
      msg = new byte[50];
      for (i = 0; i < 50; i++)
        msg[i] = 0xcd;
      Console.WriteLine("Msg={0}", Cnv.ToHex(msg));
      // Output in byte format
      // Compute HMAC-SHA-1 then check against known test vector
      b = Hmac.BytesFromBytes(msg, arrKey, HashAlgorithm.Sha1);
      Console.WriteLine("HMAC-SHA-1(50(0xcd), 0x0102..19)={0}", Cnv.ToHex(b));
      strCheck = "4c9007f4026250c6bc8414f9bf50c86c2d7235da";
      Console.WriteLine("CORRECT=                         {0}", strCheck);
      Debug.Assert(String.Compare(strCheck, Cnv.ToHex(b), true)==0, "HMAC does not match test vector");
      // Compute HMAC-SHA-256 then check against known test vector
      b = Hmac.BytesFromBytes(msg, arrKey, HashAlgorithm.Sha256);
      Console.WriteLine("HMAC-SHA-256(50(0xcd), 0x0102..19)=\n{0}", Cnv.ToHex(b));
      strCheck = "82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b";
      Console.WriteLine("CORRECT=\n{0}", strCheck);
      Debug.Assert(String.Compare(strCheck, Cnv.ToHex(b), true)==0, "HMAC does not match test vector");

      //******************
      // 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());

      //************************
      // PASSWORD PROMPT TESTS *
      //************************
      if (doPrompt)
      {
        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("GENERATE SOME RANDOM DATA...");
      for (i = 0; i < 3; i++)
      {
        b = Rng.Bytes(24);
        Console.WriteLine("RNG={0}", Cnv.ToHex(b));
      }
      // And some random numbers in a given range
      Console.Write("{random x : -10<=x<=+10} = ");
      for (i = 0; i < 12; i++)
      {
        n = Rng.Number(-10, +10);
        Console.Write("{0} ", n);
      }
      Console.Write("\n");

      // Initialize with a seed file
      fname = "seed.dat";
      if (!FileExists(fname))
      { // No seed file yet, so we'll make one
                if (doPrompt)
                {   // prompting the user for keyboard entropy
                    Console.WriteLine("Creating a new seed file...");
                    isok = Rng.MakeSeedFile(fname);
                    Console.WriteLine("Rng.MakeSeedFile('{0}') returns {1} (expecting 0)", fname, isok);
                    Debug.Assert(true == isok, "Rng.MakeSeedFile failed");
                }
                else
                {
                    // Create a dummy seed file to avoid the annoying prompt.
                    // (this will get overwritten with real random data when initialized)
                    b = new byte[Rng.SeedFileSize];
                    MakeABinaryFile(fname, b);
                }
      }
      isok = Rng.Initialize(fname);
      Console.WriteLine("Rng.Initialize('{0}') returns {1} (expecting 0)", fname, isok);
      Debug.Assert(true==isok, "Rng.Initialize failed");

      Console.WriteLine("Generate some more random data...");
      for (i = 0; i < 3; i++)
      {
        b = Rng.Bytes(24);
        Console.WriteLine("RNG={0}", Cnv.ToHex(b));
      }
      // Update the seedfile
      isok = Rng.UpdateSeedFile(fname);
      Console.WriteLine("Rng.UpdateSeedFile('{0}') returns {1} (expecting 0)", fname, isok);
      Debug.Assert(true==isok, "Rng.UpdateSeedFile failed");

      if (doPrompt)
      {
        Console.WriteLine("Ask user to type some random keystrokes to add entropy...");
        b = Rng.BytesWithPrompt(16);
        Console.WriteLine("RNG={0}", Cnv.ToHex(b));
        Console.WriteLine("And again...");
        b = Rng.BytesWithPrompt(16, "Type until we reach 128 bits...", CryptoSysPKI.Rng.Strength.Bits_128);
        Console.WriteLine("RNG={0}", Cnv.ToHex(b));
      }

      // Test the ability to add "user-entropy"...
      s = "this is some user entropy in a string, well it should be!";
      b = Rng.Bytes(16, s);
      Console.WriteLine("RNG={0}", Cnv.ToHex(b));
      // and with some bytes as a "seed"...
      b = new byte[4] { 0xde, 0xad, 0xbe, 0xef };
      Console.WriteLine("'seed'={0}", Cnv.ToHex(b));
      b = Rng.Bytes(16, b);
      Console.WriteLine("RNG={0}", Cnv.ToHex(b));

      // Do some tests on the RNG function
      fname = "Fips140.txt";
      isok = Rng.Test(fname);
      Console.WriteLine("Rng.Test('{0}') returns {1} (expecting True)", fname, isok);
      Debug.Assert(true==isok, "Rng.Test failed");
      isok = Rng.Test("");
      Console.WriteLine("Rng.Test('{0}') returns {1} (expecting True)", "", isok);
      Debug.Assert(true==isok, "Rng.Test failed");

      //****************************************
      // GENERIC BLOCK CIPHER ENCRYPTION TESTS *
      //****************************************
      Console.WriteLine("TESTING CIPHER(tdea, hex):");
      // Encrypt in CBC mode using hex strings
      keyhex = "737C791F25EAD0E04629254352F7DC6291E5CB26917ADA32";
      ivhex = "B36B6BFB6231084E";
      plainStr = "5468697320736F6D652073616D706520636F6E74656E742E0808080808080808";
      cipherStr = "d76fd1178fbd02f84231f5c1d2a2f74a4159482964f675248254223daf9af8e4";
      s = Cipher.Encrypt(plainStr, keyhex, ivhex, CipherAlgorithm.Tdea, Mode.CBC);
      Console.WriteLine("KY={0}",keyhex);
      Console.WriteLine("IV={0}",ivhex);
      Console.WriteLine("PT={0}",plainStr);
      Console.WriteLine("CT={0}",s);
      Console.WriteLine("OK={0}",cipherStr);
      Debug.Assert(String.Compare(s, cipherStr, true)==0, "Cipher.Encrypt{Hex,tdea-CBC} failed");
      // Decrypt
      s = Cipher.Decrypt(cipherStr, keyhex, ivhex, CipherAlgorithm.Tdea, Mode.CBC);
      Console.WriteLine("P'={0}",s);
      Console.WriteLine("OK={0}",plainStr);
      Debug.Assert(String.Compare(s, plainStr, true)==0, "Cipher.Decrypt{Hex,tdea-CBC} failed");
      
      Console.WriteLine("TESTING CIPHER(aes128, hex):");
      // AES using hex strings
      keyhex = "0123456789ABCDEFF0E1D2C3B4A59687";
      ivhex = "FEDCBA9876543210FEDCBA9876543210";
      // "Now is the time for all good men" 
      plainStr = "4E6F77206973207468652074696D6520666F7220616C6C20676F6F64206D656E";
      cipherStr = "c3153108a8dd340c0bcb1dfe8d25d2320ee0e66bd2bb4a313fb75c5638e9e177";
      s = Cipher.Encrypt(plainStr, keyhex, ivhex, CipherAlgorithm.Aes128, Mode.CBC);
      Console.WriteLine("KY={0}",keyhex);
      Console.WriteLine("IV={0}",ivhex);
      Console.WriteLine("PT={0}",plainStr);
      Console.WriteLine("CT={0}",s);
      Console.WriteLine("OK={0}",cipherStr);
      Debug.Assert(String.Compare(s, cipherStr, true)==0, "Cipher.Encrypt{Hex,aes128-CBC} failed");
      // Decrypt
      s = Cipher.Decrypt(cipherStr, keyhex, ivhex, CipherAlgorithm.Aes128, Mode.CBC);
      Console.WriteLine("P'={0}",s);
      Console.WriteLine("OK={0}",plainStr);
      Debug.Assert(String.Compare(s, plainStr, true)==0, "Cipher.Decrypt{Hex,tdea-CBC} failed");

      // Test some errors
      Console.WriteLine("TESTING CIPHER ERRORS:");
      Console.WriteLine("Test an invalid key...");
      s = Cipher.Encrypt(plainStr, "GARBAGEKEY", "", CipherAlgorithm.Tdea, Mode.ECB);
      Console.Write("Cipher.Encrypt returns '{0}' ",s);
      n = General.ErrorCode();
      Console.WriteLine("ErrorCode={0} {1}", n, General.ErrorLookup(n));
      Console.WriteLine("Test an invalid IV...");
      s = Cipher.Encrypt(plainStr, keyhex, "DEADIV", CipherAlgorithm.Tdea, Mode.CTR);
      Console.Write("Cipher.Encrypt returns '{0}' ",s);
      n = General.ErrorCode();
      Console.WriteLine("ErrorCode={0} {1}", n, General.ErrorLookup(n));
      Console.WriteLine("Test invalid length for plaintext...");
      s = Cipher.Encrypt("DEADBEEF", keyhex, ivhex, CipherAlgorithm.Tdea, Mode.ECB);
      Console.Write("Cipher.Encrypt returns '{0}' ",s);
      n = General.ErrorCode();
      Console.WriteLine("ErrorCode={0} {1}", n, General.ErrorLookup(n));
      Console.WriteLine("...END TESTING CIPHER ERRORS.");

      // ADVANCED PRIVATE KEY SAVING OPTIONS - NEW OVERLOAD for Rsa.SaveEncPrivateKey
      // Read in a known private key from its encrypted key file
      Console.WriteLine("\nTEST RSA SAVE ENCRYPTED PRIVATE KEY OPTIONS:");
      Console.WriteLine("Read in a private key...");
      sbPrivateKey = Rsa.ReadEncPrivateKey("AlicePrivRSASign.epk", "password");
      Console.WriteLine("PrivateKeyBits={0}", Rsa.KeyBits(sbPrivateKey.ToString()));
      Console.WriteLine("PrivateKeyBytes={0}", Rsa.KeyBytes(sbPrivateKey.ToString()));
      Console.WriteLine("KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbPrivateKey.ToString()));

      // Save with some new options, then check we can read
      fname = "alice_rsa_aes128sha1_epk.bin";
      n = Rsa.SaveEncPrivateKey(fname, sbPrivateKey.ToString(), 3000, "password",
        CipherAlgorithm.Aes128, HashAlgorithm.Sha1, Rsa.Format.Default);
      Console.WriteLine("Rsa.SaveEncPrivateKey returns {0} (expected 0)", n);
      Debug.Assert(n == 0, "Rsa.SaveEncPrivateKey failed");
      Console.WriteLine("Created private key file {0}", fname);
      sbKeyCheck = Rsa.ReadEncPrivateKey(fname, "password");
      Console.WriteLine("KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbKeyCheck.ToString()));
      Debug.Assert(Rsa.KeyHashCode(sbKeyCheck.ToString()) == Rsa.KeyHashCode(sbPrivateKey.ToString()),
        "KeyHashCodes do not match");

      fname = "alice_rsa_aes192sha256_epk.bin";
      n = Rsa.SaveEncPrivateKey(fname, sbPrivateKey.ToString(), 3000, "password",
        CipherAlgorithm.Aes192, HashAlgorithm.Sha256, Rsa.Format.Default);
      Console.WriteLine("Rsa.SaveEncPrivateKey returns {0} (expected 0)", n);
      Debug.Assert(n == 0, "Rsa.SaveEncPrivateKey failed");
      Console.WriteLine("Created private key file {0}", fname);
      sbKeyCheck = Rsa.ReadEncPrivateKey(fname, "password");
      Console.WriteLine("KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbKeyCheck.ToString()));
      Debug.Assert(Rsa.KeyHashCode(sbKeyCheck.ToString()) == Rsa.KeyHashCode(sbPrivateKey.ToString()),
        "KeyHashCodes do not match");

      fname = "alice_rsa_aes256sha512_epk.pem.txt";
      n = Rsa.SaveEncPrivateKey(fname, sbPrivateKey.ToString(), 3000, "password",
        CipherAlgorithm.Aes192, HashAlgorithm.Sha256, Rsa.Format.PEM);
      Console.WriteLine("Rsa.SaveEncPrivateKey returns {0} (expected 0)", n);
      Debug.Assert(n == 0, "Rsa.SaveEncPrivateKey failed");
      Console.WriteLine("Created private key file {0}", fname);
      sbKeyCheck = Rsa.ReadEncPrivateKey(fname, "password");
      Console.WriteLine("KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbKeyCheck.ToString()));
      Debug.Assert(Rsa.KeyHashCode(sbKeyCheck.ToString()) == Rsa.KeyHashCode(sbPrivateKey.ToString()),
        "KeyHashCodes do not match");

      Console.WriteLine("\nHASH DIGEST TEST FOR SHA-224:");
      // Hash of "abc" with input in hex format
      s = Hash.HexFromHex("616263", HashAlgorithm.Sha224);
      Console.WriteLine("SHA-224('abc')  ={0}", s);
      strCheck = "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7";
      Console.WriteLine("Correct         ={0}", strCheck);
      Debug.Assert(String.Compare(s, strCheck, true)==0, "SHA-224('abc') failed");

      Console.WriteLine("\nMORE HMAC TESTS:");
      // Hmac of <Test Case 1> with input in hex format
      s = Hmac.HexFromHex("4869205468657265", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", HashAlgorithm.Sha1);
      Console.WriteLine("HMAC-SHA-1('Hi There', (0x0b)*20)={0}", s);
      strCheck = "B617318655057264E28BC0B6FB378C8EF146BE00";
      Console.WriteLine("Correct                          ={0}", strCheck);
      Debug.Assert(String.Compare(s, strCheck, true)==0, "HMAC-SHA-1('Hi There', (0x0b)*20) failed");

      s = Hmac.HexFromHex("4869205468657265", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", HashAlgorithm.Sha224);
      Console.WriteLine("HMAC-SHA-224('Hi There', (0x0b)*20)=\n{0}", s);
      strCheck = "896FB1128ABBDF196832107CD49DF33F47B4B1169912BA4F53684B22";
      Console.WriteLine("Correct                            =\n{0}", strCheck);
      Debug.Assert(String.Compare(s, strCheck, true)==0, "HMAC-SHA-224('Hi There', (0x0b)*20) failed");

      s = Hmac.HexFromHex("4869205468657265", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", HashAlgorithm.Sha256);
      Console.WriteLine("HMAC-SHA-256('Hi There', (0x0b)*20)=\n{0}", s);
      strCheck = "B0344C61D8DB38535CA8AFCEAF0BF12B881DC200C9833DA726E9376C2E32CFF7";
      Console.WriteLine("Correct                            =\n{0}", strCheck);
      Debug.Assert(String.Compare(s, strCheck, true)==0, "HMAC-SHA-256('Hi There', (0x0b)*20) failed");

      Console.WriteLine("");    
      Console.WriteLine("ADVANCED ENVELOPED-DATA OBJECTS:");
      // Create an enveloped CMS object Alice to Bob using Bob's X.509 certificate
      fname = "cms2bob_aes128.p7m";
      fnameCert = "BobRSASignByCarl.cer";
      // This should return 1 (indicating one successful recipient)
      s = "This is some sample content.";
      n = Cms.MakeEnvDataFromString(fname, s, fnameCert, CipherAlgorithm.Aes128, Cms.KeyEncrAlgorithm.Rsa_Pkcs1v1_5, 0, 0);
      Console.WriteLine("Cms.MakeEnvDataFromString returns {0} (expecting 1)", n);
      Debug.Assert(1 == n, "Cms.MakeEnvDataFromString failed");

      // Query the enveloped-data file
      query = "keyEncryptionAlgorithm";
      s = Cms.QueryEnvData(fname, query, false);
      Console.WriteLine("{0}='{1}'", query, s);
      query = "contentEncryptionAlgorithm";
      s = Cms.QueryEnvData(fname, query, false);
      Console.WriteLine("{0}='{1}'", query, s);

      // Now try and read it using Bob's private key
      sbPrivateKey = Rsa.ReadEncPrivateKey("BobPrivRSAEncrypt.epk", "password");
      Debug.Assert(sbPrivateKey.ToString().Length > 0, "Unable to read Bob's private key");
      s = Cms.ReadEnvDataToString(fname, "", sbPrivateKey.ToString(), 0);
      Console.WriteLine("MSG={0}", s);
      Debug.Assert(s.Length > 0, "Cms.ReadEnvDataToString failed");

            /* [RSA-KEM withdrawn in v3.4]
             * -----BEGIN WITHDRAWN CODE-----
            // Repeat using RSA-KEM with AES-192 and SHA-256 with output in base 64
            fname = "cms2bob_kem_a196s256.p7m.txt";
            fnameCert = "BobRSASignByCarl.cer";
            // This should return 1 (indicating one successful recipient)
            s = "This is some sample content.";
            n = Cms.MakeEnvDataFromString(fname, s, fnameCert, 
                CipherAlgorithm.Aes192, Cms.KeyEncrAlgorithm.Rsa_Kem, HashAlgorithm.Sha256, 
                Cms.EnvDataOptions.FormatBase64);
            Console.WriteLine("Cms.MakeEnvDataFromString returns {0} (expecting 1)", n);
            Debug.Assert(1 == n, "Cms.MakeEnvDataFromString failed");

            // Query the enveloped-data file (note flag for isBase64)
            query = "keyEncryptionAlgorithm";
            s = Cms.QueryEnvData(fname, query, true);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "contentEncryptionAlgorithm";
            s = Cms.QueryEnvData(fname, query, true);
            Console.WriteLine("{0}='{1}'", query, s);

            // Now try and read it using Bob's private key
            sbPrivateKey = Rsa.ReadEncPrivateKey("BobPrivRSAEncrypt.epk", "password");
            Debug.Assert(sbPrivateKey.ToString().Length > 0, "Unable to read Bob's private key");
            s = Cms.ReadEnvDataToString(fname, "", sbPrivateKey.ToString(), Cms.Options.FormatBase64);
            Wipe.String(sbPrivateKey);
            Console.WriteLine("MSG={0}", s);
            Debug.Assert(s.Length > 0, "Cms.ReadEnvDataToString failed");
             * -----END WITHDRAWN CODE-----
             */

            Console.WriteLine("");    
            Console.WriteLine("STRONGER RSA KEY ENCRYPTION:");
            // Read in Carl's key
            fname = "CarlPrivRSASign.epk";
            Console.WriteLine("Reading private key file {0}", fname);
            sbPrivateKey = Rsa.ReadEncPrivateKey(fname, "password");
            Debug.Assert(sbPrivateKey.ToString().Length > 0, "Unable to read Carl's private key");
            k = Rsa.KeyHashCode(sbPrivateKey.ToString());
            Console.WriteLine("Existing KeyHashCode= {0,8:X}", k);

            // Now save again in a new file but using stronger encryption 
            // (pity about the strength of the password!)
            fname = "Carl_aes256sha256.epk.txt";
            n = Rsa.SaveEncPrivateKey(fname, sbPrivateKey.ToString(), 3000, "password", 
                CipherAlgorithm.Aes256, HashAlgorithm.Sha256, Rsa.Format.PEM);
            Wipe.String(sbPrivateKey);
            Debug.Assert(0 == n, "Rsa.SaveEncPrivateKey failed");
            Console.WriteLine("Created new encrypted private key file '{0}'", fname);
            // Check we can read in the new format
            sbPrivateKey = Rsa.ReadEncPrivateKey(fname, "password");
            Debug.Assert(sbPrivateKey.ToString().Length > 0, "Unable to read Carl's private key");
            Console.WriteLine("Key length={0} bits", Rsa.KeyBits(sbPrivateKey.ToString()));
            Console.WriteLine("Recreated KeyHashCode={0,8:X}", Rsa.KeyHashCode(sbPrivateKey.ToString()));
            Debug.Assert(k == Rsa.KeyHashCode(sbPrivateKey.ToString()), "KeyHashCodes do not match");
            Wipe.String(sbPrivateKey);

            // **** [2008-05-03] WE MESSED UP THE FLAGS FOR PKI_KEYGEN_INDICATE and PKI_BC_TDEA
            Console.WriteLine("GENERATE NEW RSA KEY PAIR WITH STRONGER ENCRYPTION - TEST FUDGE FOR TDEA:");
            // Generate another new RSA key using stronger encryption algorithm for private key
            pubkeyFile = "test_tdea_pub.bin";
            prikeyFile = "test_tdea_epk.bin";
            n = Rsa.MakeKeys(pubkeyFile, prikeyFile, 512, Rsa.PublicExponent.Exp_EQ_65537,
                3000, "password", 0, HashAlgorithm.Sha1, Rsa.Format.Binary, false);
            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");
            // And check the key pair we just made
            sbPublicKey = Rsa.ReadPublicKey(pubkeyFile);
            sbPrivateKey = Rsa.ReadEncPrivateKey(prikeyFile, "password");
            Console.WriteLine("Key length={0} bits", Rsa.KeyBits(sbPublicKey.ToString()));
            Console.WriteLine("Key length={0} bytes", Rsa.KeyBytes(sbPrivateKey.ToString()));
            n = Rsa.KeyMatch(sbPrivateKey.ToString(), sbPublicKey.ToString());
            Console.WriteLine("Rsa.KeyMatch returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Rsa.KeyMatch failed.");

            Console.WriteLine("GENERATE NEW RSA KEY PAIR WITH STRONGER ENCRYPTION:");
            // Generate another new RSA key using stronger encryption algorithm for private key
            // CAUTION: we make this quite small (only 768 bits) for speed in testing; 
            // this is too small for real use!
            pubkeyFile = "carol_pub.pem.txt";
            prikeyFile = "carol_epk.pem.txt";
            n = Rsa.MakeKeys(pubkeyFile, prikeyFile, 768, Rsa.PublicExponent.Exp_EQ_65537,
                3000, "password", CipherAlgorithm.Aes128, HashAlgorithm.Sha224, Rsa.Format.PEM, 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");
            // And check the key pair we just made
            sbPublicKey = Rsa.ReadPublicKey(pubkeyFile);
            sbPrivateKey = Rsa.ReadEncPrivateKey(prikeyFile, "password");
            Console.WriteLine("Key length={0} bits", Rsa.KeyBits(sbPublicKey.ToString()));
            Console.WriteLine("Key length={0} bytes", Rsa.KeyBytes(sbPrivateKey.ToString()));
            n = Rsa.KeyMatch(sbPrivateKey.ToString(), sbPublicKey.ToString());
            Console.WriteLine("Rsa.KeyMatch returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "Rsa.KeyMatch failed.");

            Console.WriteLine("");    
            Console.WriteLine("STRONGER X.509 SIGNATURE ALGORITHMS:");
            // Create a new self-signed CA certificate for Carol
            issuerCert = "carolSelf_384.cer";
            distname = "CN=Carol;O=Test";
            Console.WriteLine("Creating new self-signed X.509 cert '{0}' for '{1}'" +
                " signed by private key in '{2}'", issuerCert, distname, prikeyFile);
            n = X509.MakeCertSelf(issuerCert, prikeyFile, 0x1, 10, distname, "", 
                X509.KeyUsageOptions.KeyCertSign | X509.KeyUsageOptions.DigitalSignature |
                X509.KeyUsageOptions.CrlSign | X509.KeyUsageOptions.DataEncipherment,
                "password", X509.Options.SigAlg_Sha384WithRSAEncryption);
            Debug.Assert(0 == n, "X509.MakeCertSelf failed.");
            Console.WriteLine("Created self-signed X.509 cert '{0}'", issuerCert);
            // Query the new certificate
            query = "signatureAlgorithm";
            s = X509.QueryCert(issuerCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "subjectName";
            s = X509.QueryCert(issuerCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "serialNumber";
            s = X509.QueryCert(issuerCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "subjectName";
            s = X509.QueryCert(issuerCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            // Get the key usage flags
            k = X509.KeyUsageFlags(issuerCert);
            // (Note silly efforts required to get leading zeroes displayed in X format)
            Console.WriteLine("KeyUsage={0}", k.ToString("X").PadLeft(8, '0'));

            // Now let's create an X.509 certificate for Bob with a stronger signature algorithm
            // To do that we need the issuer's encrypted private key file (which we just made)
            // and the subject's public key file (which we don't have). So, for this example, 
            // we'll just create a new public key file from the key in Bob's existing certificate
            // and use the same KeyUsage flags from the existing certificate
            // (just because it's easier for testing).
            k = X509.KeyUsageFlags("BobRSASignByCarl.cer");
            Console.WriteLine("Bob's key usage flags are {0}", k.ToString("X").PadLeft(8, '0'));
            sbPublicKey = Rsa.GetPublicKeyFromCert("BobRSASignByCarl.cer");
            Debug.Assert(sbPublicKey.ToString().Length > 0, "Unable to read Bob's public key from certificate");
            pubkeyFile = "Bob_pubkey.dat";
            // And we'll save the public key in SSL format, just for fun :-)
            n = Rsa.SavePublicKey(pubkeyFile, sbPublicKey.ToString(), Rsa.Format.SSL);
            Debug.Assert(0 == n, "Rsa.SavePublicKey failed.");
            // Make the new certificate, using the new key files
            distname = "CN=Bob";
            fnameCert = "BobByCarol_256.cer";
            Console.WriteLine("Creating new X.509 cert '{0}' for '{1}' using public key in '{2}'" +
                " signed by private key in '{3}'", fnameCert, distname, pubkeyFile, prikeyFile);
            n = X509.MakeCert(fnameCert, issuerCert, pubkeyFile, prikeyFile, 0x256, 9, distname, "", 
                (X509.KeyUsageOptions)k,
                "password", X509.Options.SigAlg_Sha256WithRSAEncryption);
            Debug.Assert(0 == n, "X509.MakeCert failed.");
            Console.WriteLine("Created X.509 cert '{0}'", fnameCert);

            // Query the new certificate
            query = "signatureAlgorithm";
            s = X509.QueryCert(fnameCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "issuerName";
            s = X509.QueryCert(fnameCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "serialNumber";
            s = X509.QueryCert(fnameCert, query);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "subjectName";
            s = X509.QueryCert(fnameCert, query);
            Console.WriteLine("{0}='{1}'", query, s);

            // And verify the issuer
            n = X509.VerifyCert(fnameCert, issuerCert);
            Console.WriteLine("X509.VerifyCert returns {0} (expecting 0)", n);
            Debug.Assert(0 == n, "X509.VerifyCert failed.");

            // OK, as tricky as we can think of...
            Console.WriteLine("");    
            Console.WriteLine("MAKE ENVELOPED-DATA USING PEM-STYLE STRINGS FOR CERTS:");

            // First, we'll re-save a couple of X.509 certificates in PEM format,
            // then we'll read these PEM files (which are text) into strings.
            fname = "CarolSelf_384.cer";
            s = X509.ReadStringFromFile(fname);
            fname = fname+".pem.txt";
            n = X509.SaveFileFromString(fname, s, true);
            Console.WriteLine("Created {0}", fname);
            cert1 = ReadATextFile(fname);
            fname = "BobByCarol_256.cer";
            s = X509.ReadStringFromFile(fname);
            fname = fname+".pem.txt";
            n = X509.SaveFileFromString(fname, s, true);
            Console.WriteLine("Created {0}", fname);
            cert2 = ReadATextFile(fname);

            // Create an enveloped-data object for Carol and Bob using the PEM cert strings we just read
            certList = cert1 + ";" + cert2;
            fname = "cms2carol_bob.p7m";
            s = "Hello all. Here is a secret message.";
            Console.WriteLine("About to create enveloped-data file '{0}'", fname);
            n = Cms.MakeEnvDataFromString(fname, s, certList,
                CipherAlgorithm.Aes128, Cms.KeyEncrAlgorithm.Rsa_Pkcs1v1_5, 0, 0);
            // Expecting 2 = number of valid recipients
            Console.WriteLine("Cms.MakeEnvDataFromString returns {0} (expecting 2)", n);
            Debug.Assert(2 == n, "Cms.MakeEnvDataFromString failed.");
            // Check the details in the new CMS file
            query = "contentEncryptionAlgorithm";
            s = Cms.QueryEnvData(fname, query, false);
            Console.WriteLine("{0}='{1}'", query, s);
            query = "countOfRecipientInfos";
            s = Cms.QueryEnvData(fname, query, false);
            Console.WriteLine("{0}='{1}'", query, s);
            // NB always returns a string, so convert to an integer
            n = Convert.ToInt32(s);
            // Loop through all recipients
            for (i = 1; i <= n; i++)
            {
                Console.WriteLine("For recipient {0}:", i);
                query = String.Concat("recipientIssuerName", "/", i);
                s = Cms.QueryEnvData(fname, query, false);
                Console.WriteLine("{0}='{1}'", query, s);
                query = String.Concat("recipientSerialNumber", "/", i);
                s = Cms.QueryEnvData(fname, query, false);
                Console.WriteLine("{0}='{1}'", query, s);
            }

            // Carol reads in her encrypted private key data to a string
            // (NB this is not the same as an "internal" key string)
            Console.WriteLine("Reading file '{0}'", prikeyFile);
            s = ReadATextFile(prikeyFile);
            // so instead of using the filename, we can read this string instead
            // [new feature v3.2]
            sbPrivateKey = Rsa.ReadEncPrivateKey(s, "password");
            Debug.Assert(sbPrivateKey.Length > 0, "Rsa.ReadEncPrivateKey failed.");
            Console.WriteLine("Key length={0} bits", Rsa.KeyBits(sbPrivateKey.ToString()));

            // Now we have the private key as an internal string (actually StringBuilder)
            // Carol can read the message sent to her in the enveloped-data file
            s = Cms.ReadEnvDataToString(fname, "", sbPrivateKey.ToString(), 0);
            Debug.Assert(s.Length > 0, "Cms.ReadEnvDataToString failed.");
            Console.WriteLine("Msg='{0}'", s);
            // Clean up
            Wipe.String(sbPrivateKey);


            // **************
            // KEY WRAPPING
            // **************
            Console.WriteLine("");    
            Console.WriteLine("KEY WRAPPING WITH BLOCK CIPHER:");
            // Some test AES-128 key material data
            arrPlain = Cnv.FromHex("00112233 44556677 8899aabb ccddeeff");
            Console.WriteLine("Key material={0}", Cnv.ToHex(arrPlain));
            // Key Encryption Key
            arrKey = Cnv.FromHex("c17a44e8 e28d7d64 81d1ddd5 0a3b8914");
            Console.WriteLine("KEK         ={0}", Cnv.ToHex(arrKey));
            // Wrap with AES128-Wrap algorithm
            Console.WriteLine("Using AES128-Wrap");
            arrCipher = Cipher.KeyWrap(arrPlain, arrKey, CipherAlgorithm.Aes128);
            Debug.Assert(arrCipher.Length > 0, "Cipher.KeyWrap failed.");
            Console.WriteLine("Wrapped Key ={0}", Cnv.ToHex(arrCipher));
            // Check we can unwrap it
            bcheck = Cipher.KeyUnwrap(arrCipher, arrKey, CipherAlgorithm.Aes128);
            Debug.Assert(arrCipher.Length > 0, "Cipher.KeyUnwrap failed.");
            Console.WriteLine("Check       ={0}", Cnv.ToHex(bcheck));
            Debug.Assert(CompareByteArrays(bcheck, arrPlain), "Unwrapped key is different.");

            /* [RSA-KEM withdrawn in v3.4]
             * -----BEGIN WITHDRAWN CODE-----
            Console.WriteLine("");    
            Console.WriteLine("KEY WRAPPING WITH RSA-KEM:");
            // We use the same key material
            Console.WriteLine("Key material={0}", Cnv.ToHex(arrPlain));
            // but this time wrap using RSA-KEM with Bob's public key
            fnameCert = "BobRSASignByCarl.cer";
            Console.WriteLine("Reading public key from '{0}'", fnameCert);
            sbPublicKey = Rsa.GetPublicKeyFromCert(fnameCert);
            Debug.Assert(sbPublicKey.Length > 0, "Rsa.GetPublicKeyFromCert failed.");
            Console.WriteLine("Bob's public key length={0} bits", Rsa.KeyBits(sbPublicKey.ToString()));
            Console.WriteLine("Using RSA-KEM with AES128 and SHA-224");
            arrCipher = Rsa.KemWrap(arrPlain, sbPublicKey.ToString(), CipherAlgorithm.Aes128, HashAlgorithm.Sha224);
            Debug.Assert(arrCipher.Length > 0, "Rsa.KemWrap failed.");
            Console.WriteLine("Wrapped Key ={0}", Cnv.ToHex(arrCipher));
            // Check we can unwrap it using bob's private key
            fname = "BobPrivRSAEncrypt.epk";
            sbPrivateKey = Rsa.ReadEncPrivateKey(fname, "password");
            Debug.Assert(sbPrivateKey.Length > 0, "Rsa.ReadEncPrivateKey failed.");
            bcheck = Rsa.KemUnwrap(arrCipher, sbPrivateKey.ToString(), CipherAlgorithm.Aes128, HashAlgorithm.Sha224);
            Debug.Assert(arrCipher.Length > 0, "Rsa.KemUnwrap failed.");
            Console.WriteLine("Check       ={0}", Cnv.ToHex(bcheck));
            Debug.Assert(CompareByteArrays(bcheck, arrPlain), "Unwrapped key is different.");
             * -----END WITHDRAWN CODE-----
             */

            // *********************
            // AUTACK SIGNATURES 
            // *********************
            // (This is specialist stuff)
            Console.WriteLine("");
            Console.WriteLine("AUTACK SIGNATURES:");
            /* Ref: EDIFACT, Section 12.9 Worked Examples */
            /* Short RSA private key in XML format -- this is NOT SECURE! */
            xmlKey =
            "<RSAKeyPair>"+
            "<Modulus EncodingType='hexBinary'>" +
            "A59FBFE322244760E5B430B197967FDCF240D6B134D0F3783EDE652B565AC9C6" +
            "105768F11EE59AED359ED6DB6CE6AFC84233F35B60895B90AF85A66E598C15CE" +
            "FB860EA37CCEDB4A45B4C0594974EB76BC955C43B56B17940DFDCAB3E0C03F49" +
            "A308835772405E74085BF59FBA726969CBE348DAB6F9456DA57B40B64E6E3C65</Modulus>" +
            "<Exponent EncodingType='hexBinary'>010001</Exponent>" +
            "<D EncodingType='hexBinary'>" +
            "244FFDD97DED0FD4441089638A7D85FBAA86823BB87D7E7FF4E2BC322FFCF843" +
            "AB660ABD60DD8CE5E8AD72648A001AF6B06324FE3A106B89B19DFF232F116A5F" +
            "4C7151C38D4A132E0EEBC55EC0DC44EBE0CCBA8FCACB6C93F32997DAD8B9ACEA" +
                "B4BEC5D4A1F38A2218337C8C92D301C433A7E02EB4A456F5DE83AE1AD76E3D31</D>" +
            "</RSAKeyPair>";
            msgStr = "abc";
            // The correct answer
            strCheck = "4897C41FFCB27C4B77F0711890C5C48E9C42AE5A1548E1A4653CDF444C60350F" +
                "635A16393D5862DCBD83EF3727435B750CE889EB3C48C02EA0B14F6F6B4BA0D1" +
                "E16A010D42830110AB36AB183F2976B784656D4272A6215A44EAA504610C59AC" +
                "C615E661BE4EC5ACE09B8D9DCE165F0CE71AE8743266ED2F20F35862B3C9252D";

            keyStr = Rsa.FromXMLString(xmlKey, false);
            keyBits = Rsa.KeyBits(keyStr);
            Console.WriteLine("Private key is {0} bits long", keyBits);
            // Compute message digest of data
            hexDigest = Hash.HexFromString(msgStr, HashAlgorithm.Sha1);
            Console.WriteLine("Message-to-encode=Digest={0}", hexDigest);
            // Convert digest to byte array
            msg = Cnv.FromHex(hexDigest);
            // Encode digest using ISO-9796-1
            b = Rsa.EncodeMsgIso9796(msg, keyBits);
            Debug.Assert(b.Length > 0, "Failed to Encode for ISO9796-1");
            Console.WriteLine("Encoded block=\n{0}", Cnv.ToHex(b));
            // Sign block with RSA private key
            // -- use special RSA2 method with magic nibble value 6
            b = Rsa.RawPrivate(b, keyStr, 0x6);
            // Convert to hex encoding
            s = Cnv.ToHex(b);
            Console.WriteLine("Autack Signature=\n{0}", s);
            Debug.Assert(string.Compare(s, strCheck, true) == 0, "Autack signature is wrong");

            // Now we verify the Autack signature using the public key
            xmlKey =
                "<RSAKeyValue>" +
                "<Modulus EncodingType='hexBinary'>" +
                "A59FBFE322244760E5B430B197967FDCF240D6B134D0F3783EDE652B565AC9C6" +
                "105768F11EE59AED359ED6DB6CE6AFC84233F35B60895B90AF85A66E598C15CE" +
                "FB860EA37CCEDB4A45B4C0594974EB76BC955C43B56B17940DFDCAB3E0C03F49" +
                "A308835772405E74085BF59FBA726969CBE348DAB6F9456DA57B40B64E6E3C65</Modulus>" +
                "<Exponent EncodingType='hexBinary'>010001</Exponent>" +
                "</RSAKeyValue>";
            // Read in the public key from the XML string
            keyStr = Rsa.FromXMLString(xmlKey, true);
            keyBits = Rsa.KeyBits(keyStr);
            Console.WriteLine("Public key is {0} bits long", keyBits);
            // Convert the hex singature string to a byte array
            b = Cnv.FromHex(s);
            // Decrypt the signature block using the public key
            // -- use special RSA2 method with magic nibble value 0x6
            b = Rsa.RawPublic(b, keyStr, 0x6);
            Console.WriteLine("Decrypted block=\n{0}", Cnv.ToHex(b));
            // Recover the message from the block using the ISO9796-1 method
            msg = Rsa.DecodeMsgIso9796(b, keyBits);
            Debug.Assert(msg.Length > 0, "Failed to Decode for ISO9796-1");
            s = Cnv.ToHex(msg);
            Console.WriteLine("Recovered message='{0}'", s);
            // This should be equal to the message digest we prepared earlier
            Debug.Assert(string.Compare(s, hexDigest, true) == 0, "Recovered message is wrong");

            // *********************
            // PEM FILE CONVERSIONS
            // *********************
            Console.WriteLine("");
            Console.WriteLine("PEM FILE CONVERSIONS:");
            fnameInput = "AliceRSASignByCarl.cer";
            fnameOutput = "AliceRSASignByCarl.pem.cer";
            // Make a PEM file from a binary one
            r = Pem.FileFromBinFile(fnameOutput, fnameInput, "CERTIFICATE", 72);
            Debug.Assert(r == 0, "Pem.FileFromBinFile failed.");
            Console.WriteLine("Created new PEM file {0}", fnameOutput);
            // Read and display
            s = ReadATextFile(fnameOutput);
            Console.WriteLine("{0}", s);
            // Check these two certs have the same thumbprint
            hexDigest = X509.CertThumb(fnameOutput, HashAlgorithm.Sha1);
            Console.WriteLine("{1}=SHA-1({0})", fnameOutput, hexDigest);
            strCheck = hexDigest;
            hexDigest = X509.CertThumb(fnameInput, HashAlgorithm.Sha1);
            Console.WriteLine("{1}=SHA-1({0})", fnameInput, hexDigest);
            Debug.Assert(strCheck == hexDigest, "Digests are not equal");
            // Now make a new binary file back from the PEM file
            fnameInput = fnameOutput;
            fnameOutput = "AliceRSASignByCarl-dup.cer";
            r = Pem.FileToBinFile(fnameOutput, fnameInput);
            Debug.Assert(r == 0, "Pem.FileToBinFile failed.");
            Console.WriteLine("Created binary file {0}", fnameOutput);
            // Check we have the same thumbprint
            hexDigest = X509.CertThumb(fnameOutput, HashAlgorithm.Sha1);
            Console.WriteLine("{1}=SHA-1({0})", fnameOutput, hexDigest);
            Debug.Assert(strCheck == hexDigest, "Digests are not equal");

            // *************************
            // PADDING FOR BLOCK CIPHERS
            // *************************
            Console.WriteLine("");
            Console.WriteLine("PADDING FOR BLOCK CIPHERS:");
            // 1. Pad a byte array of 5 bytes for Triple DES ECB/CBC mode
            b = new byte[5] { 0xff, 0xff, 0xff, 0xff, 0xff };
            Console.WriteLine("Input   =0x{0}", Cnv.ToHex(b));
            b = Cipher.Pad(b, CipherAlgorithm.Tdea);
            Console.WriteLine("Padded  =0x{0}", Cnv.ToHex(b));
            // Now strip the padding
            b = Cipher.Unpad(b, CipherAlgorithm.Tdea);
            Console.WriteLine("Unpadded=0x{0}", Cnv.ToHex(b));
            // 2. Pad a byte array of 18 bytes for AES ECB/CBC mode
            b = new byte[18];
            for (i = 0; i < b.Length; i++)
            {
                b[i] = 0xFF;
            }
            Console.WriteLine("Input   =0x{0}", Cnv.ToHex(b));
            b = Cipher.Pad(b, CipherAlgorithm.Aes128);
            Console.WriteLine("Padded  =0x{0}", Cnv.ToHex(b));
            // Now strip the padding
            b = Cipher.Unpad(b, CipherAlgorithm.Aes128);
            Console.WriteLine("Unpadded=0x{0}", Cnv.ToHex(b));
            // 3. Pad an empty byte array for TDEA
            b = new byte[0];
            Console.WriteLine("Input   =0x{0}", Cnv.ToHex(b));
            b = Cipher.Pad(b, CipherAlgorithm.Tdea);
            Console.WriteLine("Padded  =0x{0}", Cnv.ToHex(b));
            // Now strip the padding
            b = Cipher.Unpad(b, CipherAlgorithm.Tdea);
            Console.WriteLine("Unpadded=0x{0}", Cnv.ToHex(b));
            // 4. Pad a hex string for AES
            s = "ff";
            Console.WriteLine("Input   ='{0}'", s);
            s = Cipher.Pad(s, CipherAlgorithm.Aes128);
            Console.WriteLine("Padded  ='{0}'", s);
            // Now strip the padding
            s = Cipher.Unpad(s, CipherAlgorithm.Aes128);
            Console.WriteLine("Unpadded='{0}'", s);
            // 5. Pad an empty hex string for AES
            s = "";
            Console.WriteLine("Input   ='{0}'", s);
            s = Cipher.Pad(s, CipherAlgorithm.Aes128);
            Console.WriteLine("Padded  ='{0}'", s);
            // Now strip the padding
            s = Cipher.Unpad(s, CipherAlgorithm.Aes128);
            Console.WriteLine("Unpadded='{0}'", s);

            //**************************
            // BASE64 CONVERSION TESTS *
            //**************************
            // New in [v3.6]
            Console.WriteLine("");
            Console.WriteLine("BASE64 CONVERSION TESTS:");
            // 0xFEDCBA9876543210 in base64
            b64str = "/ty6mHZUMhA=";
            b = Cnv.FromBase64(b64str);
            Console.WriteLine("'{0}'=0x{1}", b64str, Cnv.ToHex(b));
            // Back to base64
            s = Cnv.ToBase64(b);
            Console.WriteLine("In base64='{0}'", s);
            // Directly to hex
            Console.WriteLine("In hex='{0}'", Cnv.HexFromBase64(b64str));
            // hex -> base64
            s = "FEDCBA9876543210";
            b64str = Cnv.Base64FromHex(s);
            Console.WriteLine("0x{0}='{1}'", s, b64str);
            // string -> base64
            s = "México";
            b64str = Cnv.ToBase64(s);
            // base64 -> hex
            Console.WriteLine("'{0}'=>'{1}'", s, b64str);
            Console.WriteLine("In hex='{0}'", Cnv.HexFromBase64(b64str));
            // base64 -> string
            s = Cnv.StringFromBase64(b64str);
            Console.WriteLine("'{0}'=>'{1}'", b64str, s);
            // Filter out non-base64 chars (%.!*&) and e-acute
            s = "%/ty6m.!*HZUMhA=&é";
            b64str = Cnv.Base64Filter(s);
            Console.WriteLine("Filter: '{0}'=>'{1}'=>0x{2}", s, b64str, Cnv.HexFromBase64(b64str));
            Debug.Assert(Cnv.HexFromBase64(b64str) == "FEDCBA9876543210", "Cnv.FilterBase64 failed");

            //*******************
            // CONVERSION TESTS *
            //*******************
            Console.WriteLine("");
            Console.WriteLine("OTHER CONVERSION TESTS:");
            
            // Create a string that includes some Latin-1 accented characters:
            s = "abcóéíáñ";
            // This should contain the following 8 characters:
            //  U+0061 LATIN SMALL LETTER A
            //  U+0062 LATIN SMALL LETTER B
            //  U+0063 LATIN SMALL LETTER C
            //  U+00F3 LATIN SMALL LETTER O WITH ACUTE
            //  U+00E9 LATIN SMALL LETTER E WITH ACUTE
            //  U+00ED LATIN SMALL LETTER I WITH ACUTE
            //  U+00E1 LATIN SMALL LETTER A WITH ACUTE
            //  U+00F1 LATIN SMALL LETTER N WITH TILDE

            Console.WriteLine("Use GetBytes to convert a string to bytes...");
            Console.WriteLine("Test string='{0}' ({1} chars)", s, s.Length);
            Debug.Assert(8 == s.Length, "String must be 8 chars for these tests");

            Console.WriteLine("Use GetBytes in 'Default' mode:");
            b = System.Text.Encoding.Default.GetBytes(s);
            Console.WriteLine("Default.GetBytes=>{0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            Console.WriteLine("Use GetBytes in 'Latin-1' mode. Three alternatives:");
            b = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(s);
            Console.WriteLine("GetEncoding(\"iso-8859-1\").GetBytes=>{0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            // Code page 28591 == iso-8859-1
            b = System.Text.Encoding.GetEncoding(28591).GetBytes(s);
            Console.WriteLine("GetEncoding(28591).GetBytes=>{0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            // Note: Windows-1252 is *almost* the same as ISO-8859-1
            b = System.Text.Encoding.GetEncoding(1252).GetBytes(s);
            Console.WriteLine("GetEncoding(1252).GetBytes =>{0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            Console.WriteLine("But in UTF-8 the bytes are different:");
            b = System.Text.Encoding.UTF8.GetBytes(s);
            Console.WriteLine("UTF8.GetBytes=>{0} ({1} bytes)", Cnv.ToHex(b), b.Length);

            // Check that byte array contains valid UTF-8 characters
            b = System.Text.Encoding.UTF8.GetBytes(s);
            n = Cnv.CheckUTF8(b);
            Console.WriteLine("Cnv.CheckUTF8(b)={0} (expected 2)", n);
            Debug.Assert(2 == n, "Expected return value 2");
            // Test a file instead of a byte array [New in v3.7]
            fname = "test-utf8.txt";
            MakeABinaryFile(fname, b);
            n = Cnv.CheckUTF8File(fname);
            Console.WriteLine("Cnv.CheckUTF8File({1})={0} (expected 2)", n, fname);
            Debug.Assert(2 == n, "Expected return value 2");
         

            // AN ASIDE TO DO SOME TESTS ON THE BOUNDARIES...
            
            // Test subtle differences between ISO-8859-1 and WINDOWS-1252
            // Create a byte array representing some characters in ISO-8859-1 "hole"
            b1 = new byte[] { 0x61, 0x93, 0x86, 0x87, 0x8c, 0x94, 0x62 };
            Console.WriteLine("Show difference between ISO-8859-1 and Windows-1252 for chars 0x80 to 0x9E...");
            Console.WriteLine("Test bytes for GetString={0}", Cnv.ToHex(b1));
            s1 = System.Text.Encoding.GetEncoding("iso-8859-1").GetString(b1);
            Console.WriteLine("s1(iso-8859-1)='{0}'", s1);
            s1 = System.Text.Encoding.GetEncoding(1252).GetString(b1);
            Console.WriteLine("s1(Windows-1252)='{0}'", s1);
            // Check code page values
            Encoding encod = Encoding.Default;
            Console.WriteLine("Default code page={0} (usually 1252, but might not be)", encod.CodePage);
            encod = Encoding.GetEncoding("iso-8859-1");
            Console.WriteLine("iso-8859-1 code page={0} (expecting 28591)", encod.CodePage);

            // Try converting invalid UTF-8 bytes 
            Console.WriteLine("Try passing invalid UTF-8 bytes to UTF8.GetString...");
            b1 = new byte[] {0xef, 0xbf, 0xbf};
            Console.WriteLine("Pass illegal UTF-8 bytes to UTF8.GetString <- 0x{0}", Cnv.ToHex(b1));
            s1 = System.Text.Encoding.UTF8.GetString(b1);
            Console.WriteLine("s1(UTF8)='{0}'  Cnv.CheckUTF={1} (0=>invalid UTF-8)", s1, Cnv.CheckUTF8(b1));
            b1 = new byte[] { 0xc3, 0xb3, 0xc3, 0xa9, 0xc3, 0xad, 0xc3, 0xa1, 0xc3 };
            Console.WriteLine("Pass chopped UTF-8 bytes to UTF8.GetString <- 0x{0}", Cnv.ToHex(b1));
            s1 = System.Text.Encoding.UTF8.GetString(b1);
            Console.WriteLine("s1(UTF8)='{0}'  Cnv.CheckUTF={1}", s1, Cnv.CheckUTF8(b1));

            // CONVERT OUR BYTE ARRAY FROM THE TEST STRING BACK TO A STRING TYPE

            Console.WriteLine("Use GetString to convert bytes back to a string...");
            // b contains test string encoded into UTF-8 bytes
            // First we do the right thing
            s1 = System.Text.Encoding.UTF8.GetString(b);
            Console.WriteLine("UTF8.GetString(b)='{0}' (should display OK with accents and tilde)", s1);
            n = Cnv.CheckUTF8(s);
            Console.WriteLine("Cnv.CheckUTF8(s) returns {0} (expected 0 => not valid UTF-8)", n);
            // Then we force "UTF-8" bytes into a "Latin-1" string which will print "funny"
            s1 = System.Text.Encoding.GetEncoding("iso-8859-1").GetString(b);
            Console.WriteLine("(\"iso-8859-1\").GetString(b)='{0}' (should show 'funny' characters)", s1);
            n = Cnv.CheckUTF8(s);
            Console.WriteLine("Cnv.CheckUTF8(string) returns {0} (expected 0=>invalid)", n);
            n = Cnv.CheckUTF8(b);
            Console.WriteLine("Cnv.CheckUTF8(bytes) returns {0} (expected 2 => Valid UTF-8 w/8-bit ANSI)", n);

            // Use byte-to-byte encoding conversions
            Console.WriteLine("Use Cnv.ByteEncoding to convert bytes between different encodings...");
            // b contains UTF-8-encoded data, so convert to Latin-1
            b1 = Cnv.ByteEncoding(b, Cnv.EncodingConversion.Latin1_From_Utf8);
            Console.Write("Cnv.ByteEncoding(Latin1_From_Utf8): ");
            Console.WriteLine("{0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            Console.WriteLine("                                --> {0} ({1} bytes)", Cnv.ToHex(b1), b1.Length);
            Debug.Assert(8 == b1.Length, "Invalid conversion: expected 8 bytes");
            n = Cnv.CheckUTF8(b1);
            Console.WriteLine("Cnv.CheckUTF8(b1) returns {0} (expected 0 => not valid UTF-8)", n);
            Debug.Assert(0 == n, "Cnv.CheckUTF8(b1) failed");
            // and back to UTF-8
            b = Cnv.ByteEncoding(b1, Cnv.EncodingConversion.Utf8_From_Latin1);
            Console.Write("Cnv.ByteEncoding(Utf8_From_Latin1): ");
            Console.WriteLine("{0} ({1} bytes)", Cnv.ToHex(b1), b1.Length);
            Console.WriteLine("                                --> {0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            Debug.Assert(13 == b.Length, "Invalid conversion: expected 13 bytes");
            n = Cnv.CheckUTF8(b);
            Console.WriteLine("Cnv.CheckUTF8(b) returns {0} (expected 2 => Valid UTF-8 w/8-bit ANSI)", n);
            Debug.Assert(2 == n, "Cnv.CheckUTF8(b) failed");
            // Do test in the documentation that fails
            b = Cnv.FromHex("61E962"); // A valid byte array representing the Latin-1 string "aéb" but invalid UTF-8
            b1 = Cnv.ByteEncoding(b, Cnv.EncodingConversion.Latin1_From_Utf8);
            n = General.ErrorCode();    // NB remember ErrorCode before we call Cnv that clears it!
            Console.WriteLine("Cnv.ByteEncoding(0x{0},Latin1_From_Utf8) = '{1}' (expected empty result)", 
                Cnv.ToHex(b), Cnv.ToHex(b1));
            Console.WriteLine("General.ErrorCode={0} {1} (expected error here)", n, General.ErrorLookup(n));


            /* HEX CONVERSIONS */
            Console.WriteLine("Show the Cnv.FromHex method will filter out non-hex characters...");
            // Convert a hex string containing ":" characters -> 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}')=>", s);
            Console.WriteLine("...to give a valid byte array:");
            Console.WriteLine("{0} ({1} bytes)", Cnv.ToHex(b), b.Length);
            Debug.Assert(16 == b.Length, "Cnv.FromHex failed: wrong # of bytes");


            // ********************************************
            // FINALLY, PRINT OUT DETAILS OF CORE DLL AGAIN
            // ********************************************
            Console.WriteLine("");
            Console.WriteLine("Details of core DLL...");
            n = General.Version();
            ch = General.LicenceType();
            s = General.Platform();
            Console.WriteLine("Version={0}, LicenceType={1}, Platform={2}", n, ch, s);
            s = General.ModuleName();
            Console.WriteLine("ModuleName={0}", s);
            s = General.CompileTime();
            Console.WriteLine("CompileTime={0}", s);


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

            //*********************************************************
            // Put CWD back to parent and offer to remove the test dir
            //*********************************************************
            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);
            }
    }

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

        static bool MakeABinaryFile(string fileName, byte[] data)
        {
            FileStream fs; 
            BinaryWriter bw;
            fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
            bw = new BinaryWriter(fs);
            bw.Write(data);
            bw.Close();
            fs.Close();
            return true;
        }

        static long MakeALargeTextFile(string fileName)
        {   // Make a large (~10MB) text file. Return length.
            int targetsize = 1024 * 1024 * 10;
            int nblocks;
            string s;
            StringBuilder sb = new StringBuilder();

            // Make a string of all printable ASCII chars
            for (int i = 32; i <= 126; i++)
            {
                sb.Append((char)i);
            }
            sb.Append('\r');
            sb.Append('\n');
            s = sb.ToString();

            // Write out the text file
            nblocks = targetsize / s.Length;
            FileStream fs;
            StreamWriter sw;
            fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);
            sw = new StreamWriter(fs);
            for (int i = 0; i < nblocks; i++)
            {
                sw.Write(s);
            }
            sw.Close();
            fs.Close();

            return FileLength(fileName);
        }

        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 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 string PrintATextFile(string fileName)
        {   // Same as ReadATextFile except it prints to the console
            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();
            }
            return s;
        }

    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;
    }

        // Amazingly, .NET doesn't have a Compare Bytes function...
        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;
    }
  }
}