' $Id: PKI_CMS_Examples.vb $
'  Last updated:
'	$Date: 2009-04-09 20:43:00 $
'	$Author: dai $

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

' Sample VB.NET code using CryptoSys PKI <www.cryptosys.net/pki/>
' to generate keys, create X.509 certificates, 
' and send and decrypt CMS (PKCS#7) enveloped-data files 
' (enveloped <=> encrypted)

' NOTE: yes, we know we re-define the filenames and the like in each subroutine,
' but that's so we can can use them in isolation for other examples later on.
' Please fix up to use proper global variables or whatever.

' In practice, remove debugging statements and clean up properly on error.
' And do not hard-code the password like we have done here!

Module PKI_CMS_Examples

    Sub Main()
        ' First, a quick "Hello world" check...
        Console.WriteLine("CryptoSys PKI Version={0}", General.Version())

        ' Create some keys for our Certification Authority (CA), Carol...
        ' ...but this takes time when testing, so only do it if we haven't already done it...
        If (Not FileExists("carol_pubkey.p1.txt") Or Not FileExists("carol_prikey.p8.txt")) Then
            Call carol_creates_keys()
        End If
        Call carol_checks_her_keys()

        ' Carol creates a self-signed X.509 certificate containing her public key
        Call carol_creates_ca_cert()

        ' Now Ann and Ben create their key pairs (unless already done)
        If (Not FileExists("ann_pubkey.p1.txt") Or Not FileExists("ann_prikey.p8.txt")) _
         Or (Not FileExists("ben_pubkey.p1.txt") Or Not FileExists("ben_prikey.p8.txt")) Then
            Call ann_and_ben_create_keys()
        End If

        ' Carol (as the CA) creates end-user X.509 certs for Ann and Ben
        Call carol_makes_certs_for_ann_and_ben()

        ' We are testing, so we'll create a test data file to use
        ' (in practice, use your own file)
        Call MakeATextFile("excontent.txt", "This is a sample test file.")

        Call create_enc_msg_for_ben()
        Call ben_decrypts_msg_for_him_alone()
        Call create_same_msg_to_ann_and_ben()
        Call ben_decrypts_msg_for_him_and_ann()
        Call ann_decrypts_msg_for_her_and_ben()

        ' Uncomment the next line to test the *proper* way to decrypt using a prompt for password...
        ''Call ben_decrypts_msg_prompting_for_password()

        ' Just check that all our decrypted plaintext files match the original...
        Console.WriteLine("Checking that decrypted files match original...")
        Debug.Assert(FilesAreEqual("plaintextforben.txt", "excontent.txt"), "Files do not match")
        Debug.Assert(FilesAreEqual("ptforbenandannbyben.txt", "excontent.txt"), "Files do not match")
        Debug.Assert(FilesAreEqual("ptforbenandannbyann.txt", "excontent.txt"), "Files do not match")
        Console.WriteLine("...files all check OK.")

        verify_bens_cert()

    End Sub

    Sub carol_creates_keys()
        ' Carol creates a 2048-bit RSA key pair with
        ' the private key encrypted with password 'password'
        ' Keys are saved in text PEM format.

        Dim pubFile As String = "carol_pubkey.p1.txt"
        Dim epkFile As String = "carol_prikey.p8.txt"
        Dim password As String = "password" ' CAUTION: NOT IN PRACTICE!!
        Dim keybits As Integer = 2048
        Dim r As Integer
        Console.WriteLine("About to create {0}-bit keys for Carol (this may take some time)...", keybits)
        r = Rsa.MakeKeys(pubFile, epkFile, keybits, Rsa.PublicExponent.Exp_EQ_65537, 2000, password, CipherAlgorithm.Aes128, HashAlgorithm.Sha256, Rsa.Format.PEM, True)
        Console.WriteLine("Rsa.MakeKeys returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Rsa.MakeKeys failed")
        Console.WriteLine("Created key files {0} and {1}", pubFile, epkFile)
    End Sub

    Sub carol_checks_her_keys()
        ' Carol check that her keys are valid

        Dim pubFile As String = "carol_pubkey.p1.txt"
        Dim epkFile As String = "carol_prikey.p8.txt"
        Dim password As String = "password" ' CAUTION: NOT IN PRACTICE!!
        Dim sbPrivateKey As New StringBuilder
        Dim sbPublicKey As New StringBuilder
        Dim r As Integer

        ' Read keys from files into "internal" key strings (requires StringBuilders)
        sbPrivateKey = Rsa.ReadEncPrivateKey(epkFile, password)
        sbPublicKey = Rsa.ReadPublicKey(pubFile)
        ' Display details (note we use a String here)
        Console.WriteLine("Private key is {0} bits and has key hashcode {1,8:X}", _
            Rsa.KeyBits(sbPrivateKey.ToString()), Rsa.KeyHashCode(sbPrivateKey.ToString()))
        Console.WriteLine("Public  key is {0} bits and has key hashcode {1,8:X}", _
            Rsa.KeyBits(sbPublicKey.ToString()), Rsa.KeyHashCode(sbPublicKey.ToString()))
        ' Make sure the private and public parts match...
        r = Rsa.KeyMatch(sbPrivateKey.ToString(), sbPublicKey.ToString())
        If r = 0 Then
            Console.WriteLine("OK, Keys match.")
        Else
            Console.WriteLine("ERROR!, Keys do not match.")
        End If
        ' Clean up internal private key string...
        Wipe.String(sbPrivateKey)

    End Sub

    Sub carol_creates_ca_cert()
        ' Carol creates a self-signed CA certificate
        Dim certFile As String = "carol.cer"
        Dim pubFile As String = "carol_pubkey.p1.txt"
        Dim epkFile As String = "carol_prikey.p8.txt"
        Dim password As String = "password" ' CAUTION: NOT IN PRACTICE!!
        Dim distname As String = "O=Example Corp;CN=Carol"
        Dim certNum As Integer = 1
        Dim yearsValid As Integer = 10
        Dim r As Integer

        ' Make an self-signed cert No. 1 valid for 10 years, in PEM format
        r = X509.MakeCertSelf(certFile, epkFile, certNum, yearsValid, distname, "", X509.KeyUsageOptions.None, password, X509.Options.FormatPem)
        Console.WriteLine("X509.MakeCertSelf returns {0} (expecting 0)", r)

        ' NOTE that now Carol has created an X.509 certificate, she does not need the public key (.p1) file anymore.

    End Sub

    Sub ann_and_ben_create_keys()
        ' Users Ann and Ben both create 2048-bit RSA key pairs with
        ' their own passwords for the encrypted private key.
        ' Keys are saved in text PEM format.

        Dim annpubFile As String = "ann_pubkey.p1.txt"
        Dim annepkFile As String = "ann_prikey.p8.txt"
        Dim annpassword As String = "annspassword" ' CAUTION: NOT IN PRACTICE!!
        Dim benpubFile As String = "ben_pubkey.p1.txt"
        Dim benepkFile As String = "ben_prikey.p8.txt"
        Dim benpassword As String = "benspassword" ' CAUTION: NOT IN PRACTICE!!
        Dim keybits As Integer = 2048
        Dim r As Integer

        Console.WriteLine("About to create {0}-bit keys for Ann (this may take some time)...", keybits)
        r = Rsa.MakeKeys(annpubFile, annepkFile, keybits, Rsa.PublicExponent.Exp_EQ_65537, 2000, annpassword, CipherAlgorithm.Aes128, HashAlgorithm.Sha256, Rsa.Format.PEM, True)
        Console.WriteLine("Rsa.MakeKeys returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Rsa.MakeKeys failed")
        Console.WriteLine("Created key files {0} and {1}", annpubFile, annepkFile)

        Console.WriteLine("About to create {0}-bit keys for Ben (this may take some time)...", keybits)
        r = Rsa.MakeKeys(benpubFile, benepkFile, keybits, Rsa.PublicExponent.Exp_EQ_65537, 2000, benpassword, CipherAlgorithm.Aes128, HashAlgorithm.Sha256, Rsa.Format.PEM, True)
        Console.WriteLine("Rsa.MakeKeys returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Rsa.MakeKeys failed")
        Console.WriteLine("Created key files {0} and {1}", benpubFile, benepkFile)

    End Sub

    Sub carol_makes_certs_for_ann_and_ben()
        ' Make two end-user certs valid for 2 years, signed by CA Carol,
        ' with serial numbers 257 and 258, respectively
        Dim acertfile As String = "ann.cer"
        Dim bcertfile As String = "ben.cer"
        Dim apubFile As String = "ann_pubkey.p1.txt"
        Dim bpubFile As String = "ben_pubkey.p1.txt"
        Dim issuercert As String = "carol.cer"
        ' Private key details for the issuer
        Dim epkfile As String = "carol_prikey.p8.txt"
        Dim password As String = "password" ' CAUTION: NOT IN PRACTICE!!
        ' Details for the certs
        Dim yearsValid As Integer = 2
        Dim aserialnum As Integer = &H101
        Dim bserialnum As Integer = &H102

        Dim r As Integer

        ' Make Ann's certificate...
        r = X509.MakeCert(acertfile, issuercert, apubFile, epkfile, _
            aserialnum, yearsValid, "O=Example Corp;CN=Ann", 0, 0, password, X509.Options.FormatPem)
        Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r)

        ' Make Ben's certificate...
        r = X509.MakeCert(bcertfile, issuercert, bpubFile, epkfile, _
            bserialnum, yearsValid, "O=Example Corp;CN=Ben", 0, 0, password, X509.Options.FormatPem)
        Console.WriteLine("X509.MakeCert returns {0} (expecting 0)", r)

        ' NOTE: Ann and Ben's public key (.p1) files are now redundant.

    End Sub

    Sub create_enc_msg_for_ben()
        ' Some person creates an encrypted message to Ben...
        ' NOTE: This could be done by anybody who has Ben's public certificate
        ' *BUT* it is up to them to verify that it really is Ben's certificate
        ' and not a dummy one sent by an adversary -- see verify_bens_cert().
        Dim cmsfile As String = "cmsforben.p7m"
        Dim datafile As String = "excontent.txt"    ' This could be *any* file
        Dim bcertfile As String = "ben.cer"
        Dim r As Integer

        r = Cms.MakeEnvData(cmsfile, datafile, bcertfile, 0)
        ' NB expecting # recipients == 1 to be returned
        Console.WriteLine("Cms.MakeEnvData returns {0} (expecting 1)", r)
        Debug.Assert(r = 1, "Cms.MakeEnvData failed")
        Console.WriteLine("Created CMS EnvData file {0}", cmsfile)

    End Sub

    Sub ben_decrypts_msg_for_him_alone()
        ' Ben decrypts the message encrypted for him...
        Dim cmsfile As String = "cmsforben.p7m"
        Dim outfile As String = "plaintextforben.txt"
        Dim chkfile As String = "excontent.txt"
        Dim epkfile As String = "ben_prikey.p8.txt"
        Dim password As String = "benspassword" ' CAUTION: NOT IN PRACTICE!!
        Dim sbPrivateKey As New StringBuilder
        Dim r As Integer

        ' 1. Ben reads his private key
        sbPrivateKey = Rsa.ReadEncPrivateKey(epkfile, password)
        Debug.Assert(sbPrivateKey.Length > 0, "Failed to read Ben's private key file")

        ' 2. Decrypt the file
        r = Cms.ReadEnvDataToFile(outfile, cmsfile, "", sbPrivateKey.ToString(), 0)
        Console.WriteLine("Cms.ReadEnvDataToFile returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Cms.ReadEnvDataToFile failed")
        Console.WriteLine("Decrypted CMS enveloped-data to output file {0}", outfile)

        ' 3. Clear the private key
        Wipe.String(sbPrivateKey)

    End Sub

    Sub ben_decrypts_msg_prompting_for_password()
        ' Ben decrypts the message encrypted for him
        ' but this time *properly* without hardcoding the passowrd
        Dim cmsfile As String = "cmsforben.p7m"
        Dim outfile As String = "plaintextforben.txt"
        Dim chkfile As String = "excontent.txt"
        Dim epkfile As String = "ben_prikey.p8.txt"
        Dim password As String
        Dim sbPrivateKey As New StringBuilder
        Dim r As Integer

        ' 0. Prompt user for password
        password = Pwd.Prompt(512, "Enter password:")

        ' 1. Ben reads his private key
        sbPrivateKey = Rsa.ReadEncPrivateKey(epkfile, password)
        Debug.Assert(sbPrivateKey.Length > 0, "Failed to read Ben's private key file")

        ' 2. Decrypt the file
        r = Cms.ReadEnvDataToFile(outfile, cmsfile, "", sbPrivateKey.ToString(), 0)
        Console.WriteLine("Cms.ReadEnvDataToFile returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Cms.ReadEnvDataToFile failed")
        Console.WriteLine("Decrypted CMS enveloped-data to output file {0}", outfile)

        ' 3. Clear the private key
        Wipe.String(sbPrivateKey)

    End Sub

    Sub create_same_msg_to_ann_and_ben()
        ' Some person sends the same message encrypted for both Ann and Ben, in the same file...
        Dim cmsfile As String = "cmsforannandben.p7m"
        Dim datafile As String = "excontent.txt"    ' This could be *any* file
        Dim certList As String = "ann.cer;ben.cer"  ' Include paths if nec, e.g. "C:\mydir\ann.cer;D:\foo\ben.cer"
        Dim r As Integer

        r = Cms.MakeEnvData(cmsfile, datafile, certList, 0)
        ' NB expecting # recipients == 2 to be returned
        Console.WriteLine("Cms.MakeEnvData returns {0} (expecting 2)", r)
        Debug.Assert(r = 2, "Cms.MakeEnvData failed")
        Console.WriteLine("Created CMS EnvData file {0}", cmsfile)

    End Sub

    Sub ben_decrypts_msg_for_him_and_ann()
        ' Ben decrypts the message encrypted for both him and Ann...
        Dim cmsfile As String = "cmsforannandben.p7m"
        Dim outfile As String = "ptforbenandannbyben.txt"
        Dim chkfile As String = "excontent.txt"
        Dim epkfile As String = "ben_prikey.p8.txt"
        Dim password As String = "benspassword" ' CAUTION: NOT IN PRACTICE!!
        Dim sbPrivateKey As New StringBuilder
        Dim r As Integer

        ' 1. Ben reads his private key
        sbPrivateKey = Rsa.ReadEncPrivateKey(epkfile, password)
        Debug.Assert(sbPrivateKey.Length > 0, "Failed to read Ben's private key file")

        ' 2. Decrypt the file
        r = Cms.ReadEnvDataToFile(outfile, cmsfile, "", sbPrivateKey.ToString(), 0)
        Console.WriteLine("Cms.ReadEnvDataToFile returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Cms.ReadEnvDataToFile failed")
        Console.WriteLine("Decrypted CMS enveloped-data to output file {0}", outfile)

        ' 3. Clear the private key
        Wipe.String(sbPrivateKey)

    End Sub

    Sub ann_decrypts_msg_for_her_and_ben()
        ' Ann decrypts the message encrypted for both her and Ben...
        Dim cmsfile As String = "cmsforannandben.p7m"
        Dim outfile As String = "ptforbenandannbyann.txt"
        Dim chkfile As String = "excontent.txt"
        Dim epkfile As String = "ann_prikey.p8.txt"
        Dim password As String = "annspassword" ' CAUTION: NOT IN PRACTICE!!
        Dim sbPrivateKey As New StringBuilder
        Dim r As Integer

        ' 1. Ann reads her private key
        sbPrivateKey = Rsa.ReadEncPrivateKey(epkfile, password)
        Debug.Assert(sbPrivateKey.Length > 0, "Failed to read Ben's private key file")

        ' 2. Decrypt the file
        r = Cms.ReadEnvDataToFile(outfile, cmsfile, "", sbPrivateKey.ToString(), 0)
        Console.WriteLine("Cms.ReadEnvDataToFile returns {0} (expecting 0)", r)
        Debug.Assert(r = 0, "Cms.ReadEnvDataToFile failed")
        Console.WriteLine("Decrypted CMS enveloped-data to output file {0}", outfile)

        ' 3. Clear the private key
        Wipe.String(sbPrivateKey)

    End Sub

    Sub verify_bens_cert()
        ' Verify that Ben's X.509 certificate is valid
        ' To do this we:
        ' (a) check that it has not has expired, nor has the issuer's;
        ' (b) check that it really was issued by the holder of carol.cer;
        ' (c) check that the carol.cer we have is really the one we expect using its thumbprint.

        ' If any of these tests fail, DO NOT USE the cert to create an enveloped-data message.

        Dim certFile As String = "ben.cer"
        Dim issuerCert As String = "carol.cer"
        Dim certThumb As String

        Console.WriteLine("Checking that certificate '{0}' is valid and was issued by '{1}'...", certFile, issuerCert)

        ' Certs are still valid at this time...
        If Not X509.CertIsValidNow(certFile) Then
            Console.WriteLine("*ERROR: Certificate '{0}' has expired", certFile)
        ElseIf Not X509.CertIsValidNow(issuerCert) Then
            Console.WriteLine("*ERROR: Certificate '{0}' has expired", issuerCert)
        Else
            Console.WriteLine("...certificates are both valid at this time")
        End If

        ' End-user cert was issued by issuer...
        If X509.VerifyCert(certFile, issuerCert) <> 0 Then
            Console.WriteLine("*ERROR: Certificate '{0}' was NOT issued by '{1}'", certFile, issuerCert)
        Else
            Console.WriteLine("...certificate '{0}' was issued by '{1}'", certFile, issuerCert)
        End If

        ' Issuer's cert is the one we expect...
        certThumb = X509.CertThumb(issuerCert, HashAlgorithm.Sha1)
        Console.WriteLine("...certificate {0} has SHA-1 thumbprint " _
            & vbCrLf & "{1}" _
            & vbCrLf & " -- check this separately against the known value", issuerCert, certThumb)

    End Sub

    Function FilesAreEqual(ByVal file1 As String, ByVal file2 As String) As Boolean
        ' Returns true if files are equal
        Dim flen1 As Long
        Dim flen2 As Long
        Dim fi As FileInfo
        Dim digest1 As String
        Dim digest2 As String

        ' First check lengths are equal
        fi = New FileInfo(file1)
        flen1 = fi.Length
        fi = New FileInfo(file2)
        flen2 = fi.Length
        If (flen1 <> flen2) Then
            Return False
        End If

        ' Then check that MD5 digests match
        digest1 = Hash.HexFromFile(file1, HashAlgorithm.Md5)
        digest2 = Hash.HexFromFile(file2, HashAlgorithm.Md5)
        If digest1 <> digest2 Then
            Return False
        End If

        ' Then check that SHA-1 digests match
        digest1 = Hash.HexFromFile(file1, HashAlgorithm.Sha1)
        digest2 = Hash.HexFromFile(file2, HashAlgorithm.Sha1)
        If digest1 <> digest2 Then
            Return False
        End If

        ' COMMENT: you definitely cannot find two files of the same length with both the
        ' same MD5 hash *and* the same SHA-1 hash.

        ' If we have got here, the files are equal
        Return True

    End Function

    '*****************
    ' DOTNET FILE UTILITIES *
    '*****************
    Function FileExists(ByVal filePath As String) As Boolean
        Dim fi As New FileInfo(filePath)
        Return fi.Exists
    End Function 'FileExists

    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

End Module