Imports System
Imports System.Diagnostics
Imports System.Reflection
Imports System.IO
Imports System.Text
Imports CryptoSysPKI

' This is a straight port of TestPKIcsharp.cs from C# to VB.NET.
' With thanks to Alex Lowe's "C# to VB.NET Translation tool"
' http://authors.aspalliance.com/aldotnet/examples/translate.aspx

'   $Id: TestPKIvbnet.vb $
'   Last updated:
'   $Date: 2006-08-09 08:39:00 $
'   $Version: 2.9.0 $

' NOTE: This program requires the following files to exist in the 
' same directory as the executable:
'  CarlRSASelf.cer
'  CarlRSASign.epk
'  AliceRSASignByCarl.cer
'  AlicePrivRSASign.epk
'  BobRSASignByCarl.cer
'  BobPrivRSAEncrypt.epk (NOTE: the .EPK extension is one we made up)
'  bob.p7b

Module TestPKIvbnet

    Sub Main()
        Dim s As String
        Dim ch As Char
        Dim i, n, nblock, r As Integer
        Dim b() As Byte
        Dim isok As Boolean
        Dim msg() As Byte
        Dim bcheck() As Byte
        Dim sbPrivateKey As StringBuilder
        Dim sbPublicKey As StringBuilder
        Dim keyhex, plain, cipher, ivhex, okhex As String
        Dim arrPlain() As Byte
        Dim arrCipher() As Byte
        Dim arrKey() As Byte
        Dim arrIV() As Byte
        Dim excontent, fnameData, fnameEnc, fnameCheck As String
        Dim fnameInput, fnameOutput, fnameCert, fname As String
        Dim pubkeyFile, prikeyFile As String
        Dim issuerCert As String
        Dim hexDigest As String
        Dim strCheck As String
        Dim query As String

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

        '**************************************************
        ' Check we have required files in local directory *
        '**************************************************
        Dim assemblyFile As String = [Assembly].GetExecutingAssembly().Location
        Dim assemblyDir As String = Path.GetDirectoryName(assemblyFile)
        Console.WriteLine("Local directory is '{0}'.", assemblyDir)
        Console.WriteLine("Checking required test files are in local directory...")
        Dim missingFile As String = "STOPPED: Required file is missing"
        If FileIsNotPresent("CarlRSASelf.cer", missingFile) Then
            Return
        End If
        If FileIsNotPresent("CarlPrivRSASign.epk", missingFile) Then
            Return
        End If
        If FileIsNotPresent("AliceRSASignByCarl.cer", missingFile) Then
            Return
        End If
        If FileIsNotPresent("AlicePrivRSASign.epk", missingFile) Then
            Return
        End If
        If FileIsNotPresent("BobRSASignByCarl.cer", missingFile) Then
            Return
        End If
        If FileIsNotPresent("BobPrivRSAEncrypt.epk", missingFile) Then
            Return
        End If
        If FileIsNotPresent("bob.p7b", missingFile) Then
            Return
        End If
        '*******************************************
        ' TDEA (Triple DES, 3DES) ENCRYPTION TESTS *
        '*******************************************
        Console.WriteLine("TESTING TRIPLE DES:")
        keyhex = "010101010101010101010101010101010101010101010101"
        plain = "8000000000000000"
        cipher = "95F8A5E5DD31D900"
        ' Encrypt in ECB mode using hex strings
        s = Tdea.Encrypt(plain, keyhex, Mode.ECB, Nothing)
        Console.WriteLine("KY={0}", keyhex)
        Console.WriteLine("PT={0}", plain)
        Console.WriteLine("CT={0}", s)
        Console.WriteLine("OK={0}", cipher)
        Debug.Assert([String].Compare(s, cipher, True) = 0, "Tdea.HexECB failed")
        ' Decrypt
        s = Tdea.Decrypt(cipher, keyhex, Mode.ECB, Nothing)
        Console.WriteLine("P'={0}", s)
        Console.WriteLine("OK={0}", plain)
        Debug.Assert([String].Compare(s, plain, True) = 0, "Tdea.HexECB failed")

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

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

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

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

        ' Encrypt a file
        keyhex = "fedcba98765432100123456789abcdeffedcba9876543210"
        fnameEnc = "excontent.tdea.enc.dat"
        okhex = "DD1E1FA430AE6BE1D3B83245F7A5B17C4BF03688238778E95F2CCD05AF1A8F44"
        n = Tdea.FileEncrypt(fnameEnc, fnameData, keyhex, Mode.ECB, Nothing)
        If 0 = n Then
            Console.WriteLine("Tdea.File created encrypted file '{0}'", fnameEnc)
        Else
            Console.WriteLine("Tdea.File returned error code {0}", n)
        End If
        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, Nothing)
        If 0 = n Then
            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)
        End If
        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) Then
            Console.WriteLine("OK, verification OK")
        Else
            Console.WriteLine("ERROR: verification failed")
        End If
        Debug.Assert(CompareByteArrays(bcheck, b), "Verification failed")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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



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


        '*************
        ' X509 TESTS *
        '*************
        Console.WriteLine("X509 TESTS:")
        ' create a new self-signed certificate (Issue No 1) using keys we just created
        fnameCert = "myCAcert.cer"
        n = X509.MakeCertSelf(fnameCert, prikeyFile, 1, 5, "CN=Me;C=AU", "myemail@here.com", X509.KeyUsageOptions.DigitalSignature Or 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 Or X509.Options.VersionOne)
        Console.WriteLine("X509.MakeCert returned {0}", n)

        ' Verify our two new certificates
        n = X509.VerifyCert(fnameCert, issuerCert)
        If 0 = n Then
            Console.WriteLine("OK, {0} was issued by {1}.", fnameCert, issuerCert)
        Else
            Console.WriteLine("ERROR: {0} was NOT issued by {1}.", fnameCert, issuerCert)
        End If
        fnameCert = "myCAcert.cer"
        issuerCert = "myCAcert.cer"
        n = X509.VerifyCert(fnameCert, issuerCert)
        If 0 = n Then
            Console.WriteLine("OK, {0} was issued by {1}.", fnameCert, issuerCert)
        Else
            Console.WriteLine("ERROR: {0} was NOT issued by {1}.", fnameCert, issuerCert)
        End If
        ' 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}" + ControlChars.Lf + ControlChars.Tab + "--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 Then
            Console.WriteLine("OK, {0} is valid now.", fnameCert)
        Else
            Console.WriteLine("ERROR: {0} is NOT valid now.", fnameCert)
        End If
        s = X509.CertIssuerName(fnameCert, ";")
        Console.WriteLine("Issuer Name:   {0}", s)
        s = X509.CertSerialNumber(fnameCert)
        Console.WriteLine("Serial Number: {0}", s)
        s = X509.HashIssuerAndSN(fnameCert, HashAlgorithm.Sha1)
        Console.WriteLine("Hash(IssuerName+SerialNumber) = {0}", s)

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

        ' Extract the certificates from a PKCS-7 cert chain file
        fnameInput = "bob.p7b"
        ' find the number of certs in the chain
        n = X509.GetCertFromP7Chain("", fnameInput, 0)
        Console.WriteLine("X509.GetCertFromP7Chain(0) returns {0} (expected 2).", n)
        ' extract the certs in turn
        For i = 1 To n
            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)
        Next i

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

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

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

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

        ' again using a string as input instead of a file
        s = ReadATextFile(fnameInput)
        fname = "BasicSignByAlice1.bin"
        n = Cms.MakeSigDataFromString(fname, s, fnameCert, sbPrivateKey.ToString(), 0)
        Console.WriteLine("Cms.MakeSigDataFromString returns {0} (expecting 0)", n)
        Debug.Assert(0 = n, "Cms.MakeSigDataFromString failed")

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        '************************
        ' PASSWORD PROMPT TESTS *
        '************************
        ' -- uncomment to test (these are tedious on repetitive tests!)
        ''          s = Pwd.Prompt(32, "My caption for the dialog here");
        ''          Console.WriteLine("Password=[{0}]", s);
        ''          s = Pwd.Prompt(32, "My caption for the dialog here", "My new prompt:");
        ''          Console.WriteLine("Password=[{0}]", s);

        '************
        ' RNG TESTS *
        '************
        Console.WriteLine("SOME RANDOM NUMBERS:")
        For i = 0 To 2
            b = Rng.Bytes(24)
            Console.WriteLine("RNG={0}", Cnv.ToHex(b))
        Next i
        Console.Write("{x:990<=x<=1000}=")
        For i = 0 To 4
            n = Rng.Number(990, 1000)
            Console.Write("{0} ", n)
        Next i
        Console.Write(ControlChars.Lf)
        Console.WriteLine(ControlChars.Lf + "ALL TESTS COMPLETED.")
    End Sub 'Main    End Sub

    '*****************
    ' FILE UTILITIES *
    '*****************
    Private Function MakeATextFile(ByVal fileName As String, ByVal data As String) As Boolean
        Dim fs As FileStream
        Dim sw As StreamWriter
        ' Create a test text file
        fs = New FileStream(fileName, FileMode.Create, FileAccess.Write)
        sw = New StreamWriter(fs)
        sw.Write(data)
        sw.Close()
        fs.Close()
        Return True
    End Function 'MakeATextFile


    Private Function ReadATextFile(ByVal fileName As String) As String
        Dim s As String = [String].Empty
        Dim finfo As New FileInfo(fileName)
        If finfo.Exists Then
            Dim fsi As FileStream = finfo.OpenRead()
            Dim sr As New StreamReader(fsi)
            s = sr.ReadToEnd()
            Console.WriteLine(s)
            sr.Close()
            fsi.Close()
        End If
        Debug.Assert(finfo.Exists, "File '" + fileName + "' does not exist.")
        Return s
    End Function 'ReadATextFile


    Private Function ReadABinaryFile(ByVal fileName As String) As Byte()
        Dim b(0) As Byte
        Dim finfo As New FileInfo(fileName)
        If finfo.Exists Then
            Dim fsi As FileStream = finfo.OpenRead()
            Dim br As New BinaryReader(fsi)
            Dim count As Integer = CInt(fsi.Length)
            b = br.ReadBytes(count)
            br.Close()
            fsi.Close()
        End If
        Debug.Assert(finfo.Exists, "File '" + fileName + "' does not exist.")
        Return b
    End Function 'ReadABinaryFile


    Private Function FileExists(ByVal filePath As String) As Boolean
        Dim fi As New FileInfo(filePath)
        Return fi.Exists
    End Function 'FileExists


    Private Function FileIsNotPresent(ByVal filePath As String, ByVal message As String) As Boolean
        If Not FileExists(filePath) Then
            Console.WriteLine(ControlChars.Lf + "{0}: {1}", message, filePath)
            Return True
        End If
        Return False
    End Function 'FileIsNotPresent


    Private Function CompareByteArrays(ByVal data1() As Byte, ByVal data2() As Byte) As Boolean
        ' Thanks to Jon Skeet http://www.pobox.com/~skeet
        ' If both are null, they're equal
        If data1 Is Nothing And data2 Is Nothing Then
            Return True
        End If
        ' If either but not both are null, they're not equal
        If data1 Is Nothing Or data2 Is Nothing Then
            Return False
        End If
        If data1.Length <> data2.Length Then
            Return False
        End If
        Dim i As Integer
        For i = 0 To data1.Length - 1
            If data1(i) <> data2(i) Then
                Return False
            End If
        Next i
        Return True
    End Function 'CompareByteArrays

End Module