Imports CryptoSysPKI
Imports System.Text
Imports System.IO

' $Id: PortugalTax2.bas $
' $Date: 2010-11-25 21:44Z $
' $Revision: 2.0 $
' $Author: dai $

' *************************** COPYRIGHT NOTICE ******************************
' This code was originally written by David Ireland and is copyright
' (C) 2010 DI Management Services Pty Ltd <www.di-mgt.com.au>.
' Provided "as is". No warranties. Use at your own risk. You must make your
' own assessment of its accuracy and suitability for your own purposes.
' It is not to be altered or distributed, except as part of an application.
' You are free to use it in any application, provided this copyright notice
' is left unchanged.
' ************************ END OF COPYRIGHT NOTICE **************************

' This module uses functions from the CryptoSys (tm) PKI Toolkit available from
' <www.cryptosys.net/pki/>.
' Include the module `basCrPKI` in your project.

' NOTES:
' (1) The key files in these tests are expected to exist in the current working directory.
' (2) The word "signature" or "signature value" == the <Hash> field of the specification.

' REFERENCES:
' [ESPECIF-2010] "Especificação das Regras Técnicas para Certificação de Software
' Portaria n.º 363/2010, de 23 de Junho", Direcção Geral dos Impostos (DGCI), Especificacao_regras_tecnicas_Certificacao_Softwar.pdf
' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/BF3D4A62-3243-404F-8F94-DCB2B19547C3/44379/Especificacao_regras_tecnicas_Certificacao_Softwar.pdf>
' (accessed 7 August 2010).
'
' [ADIT-2010] "- 1º Aditamento - Especificação das Regras Técnicas para Certificação de Software
' Portaria n.º 363/2010, de 23 de Junho", Direcção Geral dos Impostos (DGCI), 1_Aditamento_Especificaca_regras_tecnicas.pdf
' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/84B18C77-577B-4581-A846-2DB0201B0FB4/0/1_Aditamento_Especificaca_regras_tecnicas.pdf>
' (accessed 21 November 2010).



Module PortugalTax2

    Sub Main()
        Console.WriteLine("PKI Version={0}", General.Version)
        Call Pt_CreateSignature_Especificacao()
        Call Pt_CreateSignature_SAFT_IDEMO()
        Call Pt_VerifySignature()
        Call Pt_ExtractDigest()
        Call Pt_CreateSignatureWithKeyAsString()
        Call Pt_Create_Keys()
        Call Pt_Test_DoKeyPairFilesMatch()
        Call Pt_SavePrivateKeyAsEncrypted()
        Call Pt_Make_X509_CertSelfSigned()
        Call Pt_QueryCert()
        Call Pt_GetPublicKeyFromFileAndCert()

    End Sub

    ' *******************
    ' GENERIC FUNCTIONS *
    ' *******************

    Public Function rsaCreateSignatureInBase64(ByVal strMessage As String, ByVal strKeyFile As String, Optional ByVal ShowDebug As Boolean = False) As String
        ' $GENERIC-FUNCTION$
        ' INPUT:  Message string to be signed; filename of private RSA key file (unencrypted OpenSSL format)
        ' OUTPUT: Signature in base64 format
        Dim strPrivateKey As String
        Dim abMessage() As Byte
        Dim nMsgLen As Integer
        Dim abBlock() As Byte
        Dim nBlkLen As Integer
        Dim strSigBase64 As String

        ' 1. Convert message into unambigous array of bytes and compute length
        abMessage = System.Text.Encoding.Default.GetBytes(strMessage)
        nMsgLen = abMessage.Length
        If ShowDebug Then Console.WriteLine("Message length = " & nMsgLen & " bytes.")

        ' 1a. While we're here, compute the digest of the input. (We don't need it but it's a check for later)
        Dim strDigest As String
        strDigest = Hash.HexFromBytes(abMessage, HashAlgorithm.Sha1)
        If ShowDebug Then Console.WriteLine("DIGEST=" & strDigest)

        ' 2. Read the private key file into our internal string format
        ' (Note that strPrivateKey is a one-off, ephemeral, internal string we made when reading the key file.
        ' You can't save it to use again.)
        strPrivateKey = Rsa.ReadPrivateKeyInfo(strKeyFile).ToString
        If strPrivateKey.Length <= 0 Then
            rsaCreateSignatureInBase64 = "**ERROR: cannot read private key file"
            Exit Function
        End If
        If ShowDebug Then Console.WriteLine("Private key size is " & Rsa.KeyBits(strPrivateKey) & " bits.")

        ' 3. Encode (i.e. digest and pad) the message into format required for PKCS#1v1.5 signature
        ' Required block length is key size in bytes
        nBlkLen = Rsa.KeyBytes(strPrivateKey)
        If ShowDebug Then Console.WriteLine("Key/block size is " & nBlkLen & " bytes.")
        abBlock = Rsa.EncodeMsgForSignature(nBlkLen, abMessage, HashAlgorithm.Sha1)
        ' Show the encoded block in hex format (should be 0001FFFF...ending with the 20-byte digest)
        If ShowDebug Then Console.WriteLine(Cnv.ToHex(abBlock))

        ' 4. Create the signature block using the private key
        abBlock = Rsa.RawPrivate(abBlock, strPrivateKey)
        ' Show the signature block in hex format
        If ShowDebug Then Console.WriteLine(Cnv.ToHex(abBlock))

        ' 5. Convert to base64 format
        strSigBase64 = Cnv.ToBase64(abBlock)
        If ShowDebug Then Console.WriteLine(strSigBase64)

        ' Return base64 signature
        rsaCreateSignatureInBase64 = strSigBase64

    End Function

    Public Function rsaVerifySignature(ByVal strSigBase64 As String, ByVal strPublicKeyFileOrCert As String, ByVal strTextToSign As String, _
        Optional ByVal ShowDebug As Boolean = False) As String
        ' $GENERIC-FUNCTION$
        ' INPUT:  Signature value in base64 format (the <Hash> field);
        '         filename of RSA public key file or X.509 certificate containing the same public key;
        '         text that was signed.
        ' OUTPUT: "OK" if signature is valid or error message beginning "**ERROR" if not.

        Dim strPublicKey As String
        Dim abBlock() As Byte
        Dim abDigest() As Byte
        Dim strDigest As String
        Dim strDigest1 As String

        ' 1. Read the public key file into our internal string format
        strPublicKey = Rsa.ReadPublicKey(strPublicKeyFileOrCert).ToString

        If Len(strPublicKey) <= 0 Then
            ' Was not a public key file, so try reading an X.509 certificate instead
            strPublicKey = Rsa.GetPublicKeyFromCert(strPublicKeyFileOrCert).ToString
            If Len(strPublicKey) <= 0 Then
                rsaVerifySignature = "**ERROR: cannot read public key file"
                Exit Function
            End If
        End If
        If ShowDebug Then Console.WriteLine("Public key size is " & Rsa.KeyBits(strPublicKey) & " bits.")

        ' 2. Convert base64 signature to byte array
        abBlock = Cnv.FromBase64(strSigBase64)
        If ShowDebug Then Console.WriteLine("Signature block length = " & abBlock.Length & " bytes")
        If ShowDebug Then Console.WriteLine(Cnv.ToHex(abBlock))

        ' 3. Decrypt the signature block using the RSA public key
        ' (Note that strPublicKey is a one-off, ephemeral, internal string we made when reading the key file.
        ' You can't save it to use again.)
        abBlock = Rsa.RawPublic(abBlock, strPublicKey)
        If abBlock.Length <= 0 Then
            rsaVerifySignature = "**ERROR: invalid signature"
            Exit Function
        End If
        ' Show the decrypted signature block in hex format
        If ShowDebug Then Console.WriteLine(Cnv.ToHex(abBlock))

        ' 4. Extract the message digest from the block (presumed SHA-1)
        abDigest = Rsa.DecodeDigestForSignature(abBlock)
        If abDigest.Length <= 0 Then
            rsaVerifySignature = "**ERROR: invalid signature"
            Exit Function
        End If
        If ShowDebug Then Console.WriteLine("Message digest is " & abDigest.Length & " bytes long")
        strDigest = Cnv.ToHex(abDigest)
        If ShowDebug Then Console.WriteLine("EXTRACTED DIGEST=" & strDigest)

        ' 5. Compute the SHA-1 message digest of the text that was signed
        strDigest1 = Hash.HexFromString(strTextToSign, HashAlgorithm.Sha1)
        If ShowDebug Then Console.WriteLine("COMPUTED DIGEST =" & strDigest1)

        ' 6. Compare these two digest values and return OK only if they match
        If UCase(strDigest) = UCase(strDigest1) Then
            rsaVerifySignature = "OK"
        Else
            rsaVerifySignature = "**ERROR: invalid signature"
        End If

    End Function

    Public Function hashHexFromString_SHA1(ByVal strMessage As String) As String
        ' $GENERIC-FUNCTION$
        ' INPUT:  Message to be hashed in a string of ANSI characters
        ' OUTPUT: SHA-1 digest in hex-encoded format
        ' REMARK: Hardly worth it as a function in .NET!

        hashHexFromString_SHA1 = Hash.HexFromString(strMessage, HashAlgorithm.Sha1)

    End Function

    Public Function rsaGetDigestFromBase64Signature(ByVal strSigBase64 As String, ByVal strKeyFile As String, Optional ByVal ShowDebug As Boolean = False) As String
        ' $GENERIC-FUNCTION$
        ' INPUT:  Signature value in base64 format; filename of public key file.
        ' OUTPUT: SHA-1 digest of signed message in hex-encoded format
        Dim strPublicKey As String
        Dim abBlock() As Byte
        Dim abDigest() As Byte
        Dim strDigest As String

        ' 1. Convert to byte array
        abBlock = Cnv.FromBase64(strSigBase64)
        If ShowDebug Then Console.WriteLine("Signature block length = " & abBlock.Length & " bytes")
        If ShowDebug Then Console.WriteLine(Cnv.ToHex(abBlock))

        ' 2. Read the public key file into our internal string format
        strPublicKey = Rsa.ReadPublicKey(strKeyFile).ToString
        If strPublicKey.Length <= 0 Then
            rsaGetDigestFromBase64Signature = "**ERROR: cannot read public key file"
            Exit Function
        End If
        If ShowDebug Then Console.WriteLine("Public key size is " & Rsa.KeyBits(strPublicKey) & " bits.")

        ' 3. Decrypt the signature block using the public key
        abBlock = Rsa.RawPublic(abBlock, strPublicKey)
        ' Show the decrypted signature block in hex format
        If ShowDebug Then Console.WriteLine(Cnv.ToHex(abBlock))

        ' 4. Extract the SHA-1 message digest from the block
        abDigest = Rsa.DecodeDigestForSignature(abBlock)
        If abDigest.Length < 0 Then
            rsaGetDigestFromBase64Signature = "**ERROR: Decryption Error"
            Exit Function
        End If
        If ShowDebug Then Console.WriteLine("Message digest is " & abDigest.Length & " bytes long")
        strDigest = Cnv.ToHex(abDigest)
        If ShowDebug Then Console.WriteLine("DIGEST=" & strDigest)

        ' Return extracted digest in hex form
        rsaGetDigestFromBase64Signature = strDigest

    End Function


    ' *******
    ' TESTS *
    ' *******

    Public Sub Pt_CreateSignature_Especificacao()
        ' Compute the correct signature values for the examples given in [ESPECIF-2010]
        Dim strMessage As String
        Dim strKeyFile As String
        Dim strSigBase64 As String

        ' Private key file: sample provided by DGCI
        strKeyFile = "Chave_Privada.txt"

        ' Registo 1
        ' Message string to be signed as per [REF] specifications
        ' (not including quotes; no intermediate spaces or CR-LF chars)
        strMessage = "2010-05-18;2010-05-18T11:22:19;FAC 001/14;3.12;"

        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("1: " & strSigBase64)
        ' Original value = "Am1K5+CP4LDNVDZYvcL...UnuJrca+7emgb/kpU="
        ' Correct value = "OpE9IFpK5cJO8SwC5BUy3XTCkjVK5JsjHo3TvWjM9D09aw9wabH+sGNOs7hx4iEoOP9UY6DGsR6PgIkAZSTYInhbgs2x9sxWkr417aCKoSGY4awDIVB9aUlQ91SseH3Hk5S24PfjXFDn44acWhQL4INp9Re+dC51YNC7MrpAmP4="

        ' Registo 2
        strMessage = "2010-05-18;2010-05-18T15:43:25;FAC 001/15;25.62;" & _
            "Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVS" & _
            "EDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8D" & _
            "YYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU="

        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("2: " & strSigBase64)
        ' Original value = "Jh7/rmIILVwbrPLTdk...RG8JS1Uos78="
        ' Correct value = "hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw="

    End Sub

    Public Sub Pt_CreateSignature_SAFT_IDEMO()
        ' Reproduce the signatures (<Hash>) values in SAFT_IDEMO599999999.XML
        Dim strMessage As String
        Dim strKeyFile As String
        Dim strSigBase64 As String

        ' Sample XML data file and private key file provided by DGCI at:
        ' <http://info.portaldasfinancas.gov.pt/pt/apoio_contribuinte/certificacaosoftware.htm>
        ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/371795DE-D83B-4B0E-B673-010C0F523EFB/0/SAFT_IDEMO599999999.XML>

        ' Private key file:
        ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/70FDBA7F-1C48-496C-B9C3-4F45B4FAA55F/0/Chave_Privada.txt>
        strKeyFile = "Chave_Privada.txt"

        ' Message string from 1st record in SAFT_IDEMO599999999.XML (starting at line 9492)
        ' This is the first record of the series, so the "Hash" field is empty
        strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;"
        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("FT 1/1: " & strSigBase64)
        ' Expected signature = "F8952fjEClltx2tF9m6/...jsablpR6A4="

        ' Record 2: carry forward the Hash field from record 1
        strMessage = "2008-09-16;2008-09-16T15:58:00;FT 1/2;235.15;" & _
            "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4="
        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("FT 1/2: " & strSigBase64)
        ' Expected signature = "wh0uUgI/fLTt9Kpb/hFw.../bU651c3va0="

        ' Record 3: carry forward the Hash field from record 2
        strMessage = "2008-09-16;2008-09-16T15:58:00;FT 1/3;679.61;" & _
            "wh0uUgI/fLTt9Kpb/hFwN6VIkjWZWI8R2TxtHUMyRL0a7hyQLIvoxuqGzKfzUfvAV3E1gxpKZtai5qli6Nx7unqzC4vIoc6rtb3ObuxifXiBAUD95BMh31T73O6cgcwhGR0YhiV/E6jfCbihJL2B/2s+/qsaL7OY/bU651c3va0="
        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("FT 1/3: " & strSigBase64)
        ' Expected signature = "iVYbEDuefMedP5DHBfl+...Z4+0oX3qdxY="

        ' ...etc, etc, ...
        Console.WriteLine("...")

        ' Record 6: carry forward the Hash field from record 5
        strMessage = "2008-10-21;2008-10-21T15:32:00;FT 1/6;3600.00;" & _
            "nv2NKxZ5c/1aC/D6RgCL0Z1EmvkELlxQ0qUQwu/5C+5fvDwb5+nigoN8G5NZjebQTJefCK3nT7DxYjfuTLaVwkDHsHDqW+WzNJ7r2VlGeeBV/TKpgYwy45Vb9dlpx3pwDftlfV44yLJN/uO6RIQnTU4o9+r0DtoPibhm8zEAaA4="
        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("FT 1/6: " & strSigBase64)
        ' Expected signature = "V5HNew6rKFxmSeNTSmp5...AqTsAdmi9WU="


        ' Record NC 1/1: We start a new series, so leave hash field empty
        strMessage = "2008-09-16;2008-09-16T15:58:00;NC 1/1;235.15;" & _
            ""
        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("NC 1/1: " & strSigBase64)
        ' Expected signature = "jTCuqNUzz+QDJiHeOGwk...DpQ3kO770ko="

        ' Record NC 1/2: carry forward the Hash field from record 1
        strMessage = "2008-09-16;2008-09-16T15:58:00;NC 1/2;2261.34;" & _
            "jTCuqNUzz+QDJiHeOGwkJzBoJwqNOLRMs0ISI7TXddv5RrH8KmKtaMgzaZxWY9QO4U5aoasqHRieqof+7oXq0fALKcROyVxU/PQRsh7eKani46ENkrkQNXREjAdz1nvoCSAKphd21nfMJupWlYTAJV2H0A7I+MGcDpQ3kO770ko="
        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)
        Console.WriteLine("NC 1/2: " & strSigBase64)
        ' Expected signature = "YIt8KKn+0m9HpK2BpsnY...vfxhM7re2SU="

    End Sub



    Public Sub Pt_VerifySignature()
        Dim strMessage As String
        Dim strKeyFile As String
        Dim strSigBase64 As String
        Dim strStatus As String

        ' Public key file:
        ' <http://info.portaldasfinancas.gov.pt/NR/rdonlyres/547D8EFD-4B88-4072-8CD8-17DF08FE847A/0/Chave_Publica.txt>
        strKeyFile = "Chave_Publica.txt"

        ' Message string and "Hash" from 1st record in SAFT_IDEMO599999999.XML (starting at line 9492)
        strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;"
        strSigBase64 = "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4="
        strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage, True)
        Console.WriteLine("Result=" & strStatus)

        ' Corrected version of Registo 2 in Especificacao [RE-1]
        strMessage = "2010-05-18;2010-05-18T15:43:25;FAC 001/15;25.62;" & _
            "Am1K5+CP4LDNVDZYvcLYGpnu8/1b+WWkzgoe8sbZhvk6QFzFvNN77Zsq+cHNm52jCVS" & _
            "EDgWLGHgPS1wcT8ZG7w6KgVq+2/VgOU+xKNt0lcC3gouyarZvcZpZclIReDgLh6m3nv8D" & _
            "YYHKAOQc+eCi/BQ4LqUnuJrca+7emgb/kpU="
        strSigBase64 = "hsR2TYJtl0mad+zVAhGxNLxs6matD+T8Y8IpEo12I3szSohdwwWVOfPclnu6D23pZ0w8g/Eh0TOMzYNsdkkUJpM68/nKH2d8ehI8HT85NUyLgrGhC8msXHK+ASCCOU0RN4mr04249IG+MuOAlnW8EcMJNZA+lTf94MbpJNqRYUw="
        strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage, True)
        Console.WriteLine("Result=" & strStatus)

    End Sub

    ' Extract the message digest from a given signature.
    ' (Use this in your debugging)

    Public Sub Pt_ExtractDigest()
        Dim strKeyFile As String
        Dim strSigBase64 As String
        Dim strDigest As String

        ' Public key file we created ourselves
        strKeyFile = "Chave_Publica.txt"

        ' Signature in base64 form.
        strSigBase64 = "F8952fjEClltx2tF9m6/QTFynFjSuiboMslNZ1ag9oR5iIivgYYa0cNa0wJeWXlsf8QQVHUol303hp7XmIy5/kFOiV0Cv8QH6SF0Q5zNsDtpeFh2ZJ256y0DkJMSQqCq3oSka+9zIXXRkXgEsSv6VScCYv8VTlIcGjsablpR6A4="
        strDigest = rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile)
        Console.WriteLine("DIGEST FOUND=" & strDigest)
        Console.WriteLine("EXPECTED    =" & "BB5C0F8FF294016FA4F0A3265410249D275B0986")
    End Sub

    ' Example to read in key files directly as a string...

    ' Use this as an alternative to passing filenames.
    ' The CryptoSys RSA_Read* functions will accept a string containing the file contents.
    ' You must still use the RSA_Read* functions to obtain the ephemeral "internal" key strings to use with the RSA_Raw* functions.

    Public Sub Pt_CreateSignatureWithKeyAsString()
        Dim strMessage As String
        Dim strKeyFile As String
        Dim strSigBase64 As String
        Dim strDigest As String
        Dim strStatus As String

        ' As an alternative to passing a filename, you may instead pass the key data directly as a "PEM" string
        strKeyFile = _
            "-----BEGIN RSA PRIVATE KEY-----" & _
            "MIICXgIBAAKBgQDWDX9wVqj6ZqNZU1ojwBpyKKkuzHTCmfK39xx/T9vWkqpcV7h3sx++ZOv2KhhNkIe/1I4OCWDPCXRE4g0uIQr0NS29vMlP3aHHayy76+lbBCNVcHFxM0ggjre1acnD0qUpZ6Vza7F+PpCyuypD2V/pkL1nX9Z6z5uYyqc0XaSFdwIDAQABAoGBAJCA7j6Vkl/w+GeuOJUX9AK" & _
            "LZqN8TXquWUhOX4OnEt9Jhg7u/U55s31iPlWh12RNpQcg5IGfXSaH2GFEReeVUQGMrb89kkfbeY5HSRHh3/sBSyJTMn2cjsqfUnUJhywJPxT8NFIcS2pRBJe/QN/pL+M2jk+Fl40wyVXRhnog+4fhAkEA//Tijl5SA7a/uCyfOQkJ6yop13dfN4EHEWYMzI6SlnYWuJfdIOz4wkzBWgD0r/btFA" & _
            "ths1zElmRWINjWsB84ZwJBANYWywqsZA4FShXkDEWfG1GbrEIXiOnPJay2p7en3DQ+lx4GfE10iO52f54QRu13SZp06050YkrWcRfBGCXaYHECQQCU8vMsmmLr2ltzWDRIQqRM/7pdsw/sAuAUFej42Tcg7BOI1IdQc9bHa1dRgyDhjbalZYIzmJamVjlw3/7/ewudAkB/ipatpiP5YldPkUtqU" & _
            "q5QwOAvg5vSRtEYAr0KIZuDGGKoxY5aCnnlLn06qlHG+JDFzq+8ToOcOAKp9yQusNlRAkEA+0DarosTmn2I7+fj2/3ojVKdW/eIisz547U3bGbW/hBCZRi+y+cQnPlZ7Cr4LcGInhdxR+fSWptMNwrDCUiYHA==" & _
            "-----END RSA PRIVATE KEY-----"

        ' Exact message string to be signed:
        strMessage = "2008-03-10;2008-03-10T15:58:00;FT 1/1;28.07;"

        strSigBase64 = rsaCreateSignatureInBase64(strMessage, strKeyFile)

        Console.WriteLine("<Hash>=" & strSigBase64)
        ' Expected signature = "F8952fjEClltx2tF9m6/...jsablpR6A4="

        ' Similarly, we can pass the public key data as a "PEM" string
        strKeyFile = _
            "-----BEGIN PUBLIC KEY-----" & _
            "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWDX9wVqj6ZqNZU1ojwBpyKKkuzHTCmfK39xx/T9vWkqpcV7h3sx++ZOv2KhhNkIe/1I4OCWDPCXRE4g0uIQr0NS29vMlP3aHHayy76+lbBCNVcHFxM0ggjre1acnD0qUpZ6Vza7F+PpCyuypD2V/pkL1nX9Z6z5uYyqc0XaSFdwIDAQAB" & _
            "-----END PUBLIC KEY-----"

        strDigest = rsaGetDigestFromBase64Signature(strSigBase64, strKeyFile)
        Console.WriteLine("DIGEST EXTRACTED=" & strDigest)

        strStatus = rsaVerifySignature(strSigBase64, strKeyFile, strMessage)
        Console.WriteLine("Result=" & strStatus)

    End Sub

    '****************************************************************************************************************
    ' THE FOLLOWING SHOWS HOW TO CREATE A PAIR OF RSA PUBLIC AND PRIVATE KEYS COMPATIBLE WITH THE OPENSSL PEM FORMAT.
    ' AND HOW TO CREATE AN X.509 CERTIFICATE FROM THE KEYS AND HOW TO VERIFY THAT A PAIR OF KEYS MATCH.
    '****************************************************************************************************************

    Public Sub Pt_Create_Keys()
        ' Create an RSA key pair
        Dim nRet As Integer
        Dim strPublicKeyFile As String
        Dim strEncPrivateKeyFile As String
        Dim strPemPrivateKeyFile As String
        Dim sbIntPrivateKey As StringBuilder
        Dim strPassword As String

        strPassword = "password"    ' Password for encrypted private key file (please pick something stronger!)

        ' OpenSSL commands to create an RSA key pair:
        '   cmd> openssl genrsa -out PrivateKey.PEM 1024
        '   cmd> openssl rsa -in PrivateKey.PEM -out PublicKey.PEM -outform PEM –pubout
        '
        strPublicKeyFile = "Pt_PublicKey.PEM"       ' This is created in OpenSSL PEM format
        strEncPrivateKeyFile = "Pt_PrivateKey.EPK"  ' This is in encrypted form
        strPemPrivateKeyFile = "Pt_PrivateKey.PEM"  ' This is in OpenSSL PEM format

        ' RSA_MakeKeys creates the key pair with an encrypted private key.
        Console.WriteLine("Creating keys. This may take a few seconds...")
        nRet = Rsa.MakeKeys(strPublicKeyFile, strEncPrivateKeyFile, 1024, Rsa.PublicExponent.Exp_EQ_65537, 2048, strPassword, 0, HashAlgorithm.Sha1, Rsa.Format.SSL, False)
        Console.WriteLine("RSA_MakeKeys returns " & nRet & " (expected 0)")

        ' Adjust the white space in the public key file created here to suit the requirements of the DGCI.
        ' The file is a valid PEM format either way, but DGCI insists it should be exactly 272 bytes long.
        ' See <http://www.cryptosys.net/pki/portugal_DGCI_billing_software.html#key278vs272>.
        Call FixFileDosToUnix(strPublicKeyFile)

        ' To save the encrypted private key in unencrypted OpenSSL format, we read the key into an internal key string
        ' and then save to a file in the correct format.
        ' Note that the "internal" key string is ephemeral.
        ' CAUTION: saving a production private key in unencrypted form is a huge security risk!
        sbIntPrivateKey = Rsa.ReadEncPrivateKey(strEncPrivateKeyFile, strPassword)
        If Len(sbIntPrivateKey.Length) = 0 Then
            MsgBox("Error reading encrypted private key file", vbCritical)
            Exit Sub
        End If
        ' Now save in correct form
        nRet = Rsa.SavePrivateKeyInfo(strPemPrivateKeyFile, sbIntPrivateKey.ToString, Rsa.Format.SSL)
        Console.WriteLine("RSA_SavePrivateKeyInfo returns " & nRet & " (expected 0)")

        ' Clear the private key
        Wipe.String(sbIntPrivateKey)

        ' Do some checks that the OpenSSL keys match
        If Not Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile, strPublicKeyFile) Then
            MsgBox("Error: keys do not match", vbCritical)
            Exit Sub
        End If

    End Sub

    Public Function FixFileDosToUnix(ByVal fileName As String) As Boolean
        ' Converts the line endings in a file from CR-LF pairs to single LF characters
        Dim strBuffer As String
        Dim finfo As New FileInfo(fileName)

        ' Check if file exists
        If finfo.Exists Then
            ' Read in the file to a string
            Dim fsi As FileStream = finfo.OpenRead()
            Dim sr As New StreamReader(fsi)
            strBuffer = sr.ReadToEnd()
            ''Console.WriteLine(strBuffer)
            sr.Close()
            fsi.Close()
        Else
            Exit Function
        End If

        ' Edit the string
        Console.WriteLine("Input is " & Len(strBuffer) & " bytes long")
        strBuffer = Replace(strBuffer, vbCrLf, vbLf)
        Console.WriteLine("Output is " & Len(strBuffer) & " bytes long")

        ' Re-write the file
        Dim fs As FileStream
        Dim sw As StreamWriter
        fs = New FileStream(fileName, FileMode.Create, FileAccess.Write)
        sw = New StreamWriter(fs)
        sw.Write(strBuffer)
        sw.Close()
        fs.Close()

        ' Success
        FixFileDosToUnix = True

    End Function

    Public Sub Pt_Test_DoKeyPairFilesMatch()
        ' Check that the two OpenSSL-format key files match...
        Dim strPublicKeyFile As String
        Dim strPemPrivateKeyFile As String

        strPublicKeyFile = "Pt_PublicKey.PEM"       ' This is in OpenSSL PEM format
        strPemPrivateKeyFile = "Pt_PrivateKey.PEM"  ' This is in OpenSSL PEM format

        If Not Pt_DoKeyPairFilesMatch(strPemPrivateKeyFile, strPublicKeyFile) Then
            Console.WriteLine("Error: keys do not match")
        Else
            Console.WriteLine("OK, keys match.")
        End If

    End Sub

    Public Function Pt_DoKeyPairFilesMatch(ByVal strPemPrivateKeyFile As String, ByVal strPublicKeyFile As String) As Boolean
        ' Returns TRUE if the public and private keys in the given files match or FALSE if they do not.
        Dim nRet As Integer
        Dim sbIntPrivateKey As StringBuilder
        Dim strIntPublicKey As String
        Dim nHashCodePub As Integer
        Dim nHashCodePri As Integer

        ' Read in the keys from the files to internal key strings
        sbIntPrivateKey = Rsa.ReadPrivateKeyInfo(strPemPrivateKeyFile)
        If Len(sbIntPrivateKey.Length) = 0 Then
            MsgBox("Error reading PEM private key file", vbCritical)
            Exit Function
        End If
        strIntPublicKey = Rsa.ReadPublicKey(strPublicKeyFile).ToString
        If Len(strIntPublicKey) = 0 Then
            MsgBox("Error reading PEM private key file", vbCritical)
            Exit Function
        End If
        ' Display the key lengths
        Console.WriteLine("Private key is " & Rsa.KeyBits(sbIntPrivateKey.ToString) & " bits")
        Console.WriteLine("Public key is  " & Rsa.KeyBits(strIntPublicKey) & " bits")
        ' Display the "hashcode" (this is an internal hash code which should be equal for matching keys)
        nHashCodePri = Rsa.KeyHashCode(sbIntPrivateKey.ToString)
        nHashCodePub = Rsa.KeyHashCode(strIntPublicKey)
        Console.WriteLine("Hashcodes are " & Hex(nHashCodePri) & " and " & Hex(nHashCodePub))
        ' Verify that a pair of "internal" RSA private and public key strings are matched
        nRet = Rsa.KeyMatch(sbIntPrivateKey.ToString, strIntPublicKey)
        Console.WriteLine("RSA_KeyMatch returns " & nRet & " (0 => keys match)")

        ' Clear the private key
        Wipe.String(sbIntPrivateKey)

        Pt_DoKeyPairFilesMatch = (nRet = 0)

    End Function

    Public Sub Pt_SavePrivateKeyAsEncrypted()
        ' Save an unencrypted OpenSSL private key in PKCS-8 encrypted form
        Dim nRet As Integer
        Dim strEncPrivateKeyFile As String
        Dim strOpenSSLPrivateKeyFile As String
        Dim sbIntPrivateKey As StringBuilder
        Dim strPassword As String

        strOpenSSLPrivateKeyFile = "Pt_PrivateKey.pem"          ' This was created in unencrypted OpenSSL PEM format
        strEncPrivateKeyFile = "Pt_PrivateKeyEncrypted.pem"     ' This is in encrypted form
        strPassword = "password"    ' CAUTION: Pick something better than this!

        ' Read private key info into ephemeral internal string
        sbIntPrivateKey = Rsa.ReadPrivateKeyInfo(strOpenSSLPrivateKeyFile)
        ' Save in encrypted file form (set nCount to 2000 or so)
        nRet = Rsa.SaveEncPrivateKey(strEncPrivateKeyFile, sbIntPrivateKey.ToString, 2000, strPassword, Rsa.PbeOptions.Default, Rsa.Format.PEM)
        Console.WriteLine("RSA_SaveEncPrivateKey returns " & nRet & " (expected 0)")

        ' Clear the private key
        Wipe.String(sbIntPrivateKey)
    End Sub

    Public Sub Pt_Make_X509_CertSelfSigned()
        ' Use the RSA key file to make a self-signed X.509 certificate containing the public key

        ' Requirements from [ESPECIF-2010] 5.2.2:
        ' Formato = x.509
        ' Charset = UTF-8
        ' Encoding = Base-64
        ' Endianess = Little Endian         [COMMENT: this is not relevant for X.509!]
        ' OAEP Padding = PKCS1 v1.5 padding [COMMENT: This is NOT "OAEP" padding]
        ' Tamanho da chave privada = 1024 bytes
        ' Formato do Hash da mensagem = SHA-1

        Dim nRet As Integer
        Dim strEncPrivateKeyFile As String
        Dim strPassword As String
        Dim keyUsage As X509.KeyUsageOptions
        Dim options As X509.Options
        Dim strCertFile As String
        Dim strDN As String
        Dim nYearsValid As Integer
        Dim nCertNum As Integer

        ' With CryptoSys PKI we need to use the encrypted private key file we created with RSA_MakeKeys, not the OpenSSL one.
        strEncPrivateKeyFile = "Pt_PrivateKey.EPK"  ' This is in encrypted form
        strPassword = "password"            ' The password for the encrypted private key
        strCertFile = "Pt_SelfSigned.cer"   ' The certificate file we are going to create
        nYearsValid = 10                    ' Make this as long as you want.
        nCertNum = &H101                    ' Pick a number. Change this if you issue another certificate with a different key.
        ' The distinguished name of both the subject and the issuer of the certificate...
        strDN = "C=PT;O=Exemplo Organização;CN=Certificado auto-assinado"
        ' Options...
        keyUsage = X509.KeyUsageOptions.DigitalSignature + X509.KeyUsageOptions.KeyCertSign + X509.KeyUsageOptions.CrlSign
        ' We want UTF-8 text and the output in PEM format...
        options = X509.Options.UTF8String + X509.Options.FormatPem

        ' Create the certificate file
        Console.WriteLine("Creating self-signed X.509 certificate serial number 0x" & Hex(nCertNum) & " for subject '" & strDN & "'")
        nRet = X509.MakeCertSelf(strCertFile, strEncPrivateKeyFile, nCertNum, nYearsValid, strDN, "", keyUsage, strPassword, options)
        Console.WriteLine("X509.MakeCertSelf returns " & nRet & " (expected 0)")

    End Sub

    Public Sub Pt_QueryCert()
        ' Query an X.509 certificate for selected information
        Dim strOutput As String
        Dim strQuery As String
        Dim strCertFile As String

        strCertFile = "Pt_SelfSigned.cer"

        Console.WriteLine("For certificate file " & strCertFile)
        strQuery = "serialNumber"
        strOutput = X509.QueryCert(strCertFile, strQuery)
        If strOutput.Length <= 0 Then Exit Sub ' catch error
        Console.WriteLine(strQuery & "=" & strOutput)

        strQuery = "subjectName"
        ' NB use of option to obtain UTF-8-encoded name in Latin-1 format
        strOutput = X509.QueryCert(strCertFile, strQuery, X509.Options.Latin1)
        If strOutput.Length <= 0 Then Exit Sub ' catch error
        Console.WriteLine(strQuery & "=" & strOutput)

        strQuery = "notAfter"
        strOutput = X509.QueryCert(strCertFile, strQuery)
        If strOutput.Length <= 0 Then Exit Sub ' catch error
        Console.WriteLine(strQuery & "=" & strOutput)

    End Sub

    Public Sub Pt_GetPublicKeyFromFileAndCert()
        ' Read the public key from both the original public key file and from the X.509 certificate we created.
        ' Display some info about the key.
        Dim strPublicKeyFile As String
        Dim strCertFile As String
        Dim strIntPublicKey As String
        strPublicKeyFile = "Pt_PublicKey.PEM"
        strCertFile = "Pt_SelfSigned.cer"

        ' NOTE:
        ' The internal key string is ephemeral and encrypted: it will be different each time you read it,
        ' although it will contain the same underlying key data..
        ' It is only intended for "internal" use by CryptoSys PKI functions like RSA_RawPublic in the same process.
        ' But the HashCode will always be the same for the same key value.

        ' Read in public key from file created by RSA_MakeKeys
        strIntPublicKey = Rsa.ReadPublicKey(strPublicKeyFile).ToString
        Console.WriteLine("Public key is  " & Rsa.KeyBits(strIntPublicKey) & " bits")
        Console.WriteLine("HashCode=0x" & Hex(Rsa.KeyHashCode(strIntPublicKey)))
        Console.WriteLine(Pt_GetPublicKeyAsXml(strIntPublicKey))
        ' Read in (the same) public key from certificate file
        strIntPublicKey = Rsa.GetPublicKeyFromCert(strCertFile).ToString
        Console.WriteLine("Public key is  " & Rsa.KeyBits(strIntPublicKey) & " bits")
        Console.WriteLine("HashCode=0x" & Hex(Rsa.KeyHashCode(strIntPublicKey)))
        Console.WriteLine(Pt_GetPublicKeyAsXml(strIntPublicKey))

    End Sub

    Public Function Pt_GetPublicKeyAsXml(ByVal strIntPublicKey As String) As String
        ' Get the public key in <RSAKeyValue> XML form from an "internal" key string
        Pt_GetPublicKeyAsXml = Rsa.ToXMLString(strIntPublicKey, 0)
    End Function




End Module