Imports System.Text
Imports CryptoSysPKI

' $Id: MexicoSAT.vb $
' $Date: 2009-09-03 17:09 $
' $Author: dai $

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

' *************************** COPYRIGHT NOTICE ******************************
' This code was originally written by David Ireland and is copyright
' (C) 2005-9 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 **************************

Module MexicoSAT

    Sub Main()
        Call Mex_CreateSignature()
        Call Mex_ExtractDigestFromSignature()
        Call Mex_CreateDigestFromString()
        Call Mex_CheckKeyAndCertMatch()
        Call Mex_Convert_Latin1_To_UTF8()
        Call Mex_ValidateCert()
        Call Mex_CertToBase64String()
        Call Mex_SAT_SerialNumber()
        Call Mex_QueryCertString()
    End Sub

    Public Sub Mex_CreateSignature()
        '@Proc: Mex_CreateSignature
        '@Lang: VB.NET
        '@ Creates a signature (sello) value in base64 format given the piped-string input and the private key
        Dim strDataFile As String
        Dim strKeyFile As String
        Dim strPassword As String
        Dim sbPrivateKey As New StringBuilder
        Dim abDigest() As Byte
        Dim abBlock() As Byte
        Dim nBlockLen As Integer
        ''Dim nLen As Integer
        ''Dim nRet As Integer
        Dim strBase64 As String

        ' INPUT: File containing piped-string formed from XML doc in UTF-8 format (NOTE: no Unicode markers in the file),
        '        private key file and its secret password(!)
        strDataFile = "Muestra-v2_PipedString-UTF8.txt"
        strKeyFile = "aaa010101aaa_CSD_01.key"
        ' Test password - CAUTION: DO NOT hardcode production passwords!
        strPassword = "a0123456789"

        ' 1. Form the message digest hash of the piped-string directly from the file

        abDigest = Hash.BytesFromFile(strDataFile, HashAlgorithm.Md5)
        If abDigest.Length <= 0 Then
            Console.WriteLine("ERROR: Failed to create hash of file.")
            Exit Sub
        End If
        ' Display in hex
        Console.WriteLine("Digest=" & Cnv.ToHex(abDigest))

        ' 2. Sign the message digest using the private key

        ' 2.1 Read in private key from encrypted .key file
        sbPrivateKey = Rsa.ReadEncPrivateKey(strKeyFile, strPassword)
        If sbPrivateKey.Length = 0 Then
            Console.WriteLine("ERROR: Failed to read private key")
            Exit Sub
        End If
        ' -- show we got something
        Console.WriteLine("Private key is " & Rsa.KeyBits(sbPrivateKey.ToString) & " bits long")

        ' 2.2 Encode the digest ready for signing with `Encoded Message for Signature' block using PKCS#1 v1.5 method
        nBlockLen = Rsa.KeyBytes(sbPrivateKey.ToString)
        abBlock = Rsa.EncodeDigestForSignature(nBlockLen, abDigest, HashAlgorithm.Md5)
        If abBlock.Length = 0 Then
            Console.WriteLine("ERROR with Rsa.EncodeDigestForSignature")
            Exit Sub
        End If
        Console.WriteLine("INPUT BLOCK= " & Cnv.ToHex(abBlock))

        ' 2.3 Sign using the RSA private key
        abBlock = Rsa.RawPrivate(abBlock, sbPrivateKey.ToString)
        ' Display in hex
        Console.WriteLine("OUTPUT BLOCK=" & Cnv.ToHex(abBlock))

        ' 2.4 Clean up
        Wipe.String(sbPrivateKey)

        ' 3. Convert to base64 and output result
        strBase64 = System.Convert.ToBase64String(abBlock)
        Console.WriteLine("SIGNATURE VALUE=" & strBase64)

    End Sub

    Public Function Mex_ExtractDigestFromSignature() As String
        '@Proc: Mex_ExtractDigestFromSignature
        '@Lang: VB6
        '@ Extracts the message digest from a signature (sello) string using the X.509 certificate (certificado) value
        Dim sbPublicKey As StringBuilder
        Dim strSello As String
        Dim strCertificado As String
        Dim abDigest() As Byte
        Dim abData() As Byte
        Dim nSigLen As Integer
        Dim strDigestHex As String

        Mex_ExtractDigestFromSignature = ""
        ' INPUT: Base64 strings extracted from the XML file (Ref: Muestra_v2_signed2.xml)
        strSello = "UlUSwGNEicfigV6i4RhTy0eb2RYWFYyFatJFcM/u5Wlkb5XRxXiCizTGw5Yxz9oZNk8msAgO4C5Gevjh+S2TJPZueYhaQeZlo6k0rE3CQexkOGVRpHkvAoAgOM5kGKzYe24DKZbTgjNL+ai+tbhEHmRAFcpv2rDpehbL3w6BnYU="
        strCertificado = "MIIDhDCCAmygAwIBAgIUMTAwMDEyMDAwMDAwMDAwMjI1MTcwDQYJKoZIhvcNAQEFBQAwgcMxGTAXBgNVBAcTEENpdWRhZCBkZSBNZXhpY28xFTATBgNVBAgTDE1leGljbywgRC5GLjELMAkGA1UEBhMCTVgxGjAYBgNVBAMTEUFDIGRlIFBydWViYXMgU0FUMTYwNAYDVQQLFC1BZG1pbmlzdHJhY2nzbiBkZSBTZWd1cmlkYWQgZGUgbGEgSW5" & _
            "mb3JtYWNp824xLjAsBgNVBAoUJVNlcnZpY2lvIGRlIEFkbWluaXN0cmFjafNuIFRyaWJ1dGFyaWEwHhcNMDgwODIxMTUyMjA4WhcNMTAwODIxMTUyMjA4WjCBmDElMCMGA1UELRMcQUFBMDEwMTAxQUFBIC8gQUFBQTAxMDEwMUFBQTEeMBwGA1UEBRMVIC8gQUFBQTAxMDEwMUhERlJYWDAxMRIwEAYDVQQKEwlNYXRyaXogU0ExEzARBgNVBA" & _
            "sTClVuaWRhZCAxMCAxEjAQBgNVBAMTCU1hdHJpeiBTQTESMBAGA1UEKRMJTWF0cml6IFNBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpmiW1q9gyzCFtMcbaFDJexk2IpLoTdNXg4ToGRZ/f+hIjmj3N6ODWX1ARNFGYocEHf113GpW5Oe/mj6UqhBpiH4JRTNR4Udb8myJTArIlODynVHuIUuyhKo7gbMbDdXjilTAYY2XWQuQ7aDtWw" & _
            "ntUmNg4vAC/F3OtRz3+y9wM5QIDAQABox0wGzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQUFAAOCAQEAafyD4gMsOvq7E3raPntmQlJTxpWwNySqskE7fe23HVL9UKFCUlWWx/W8gluxIX9S19y17iWnGbtmbNddHxG5PznPsy/a8PlwNHjDW0FOpia2LsvDrNcdPiJhzL/1OVagkenffFf8bLEetF3ktxZ7ifcH1yxV" & _
            "xpZ7PS/pe8YIOpWRuMmTV4ypGdsw9TW3HVP5IJ/canuQGPTb3LQ8ojihW2dHnC6ojaWW4GHFSZAPhQJ/DaH/UgFjaQke/RBtoAketfROdG+1qYeA1q/is04O4AXNmMByGp7ZnvGNrO9LDBvs3eKN4ZYcQyjxFEbr1X/xUqHCRF1VEkkC5jJQ1ktC4g=="

        ' 1. Read in Public key from X.509 certificate string directly
        sbPublicKey = Rsa.GetPublicKeyFromCert(strCertificado)
        If sbPublicKey.Length = 0 Then
            Console.WriteLine("ERROR: failed to read Certificado string")
            Exit Function
        End If
        ' --Show we got something useful
        Console.WriteLine("Public key is " & Rsa.KeyBits(sbPublicKey.ToString) & " bits long")

        ' 2. Convert base64 signature value to byte array
        abData = System.Convert.FromBase64String(strSello)
        nSigLen = abData.Length
        ' 2a. Check lengths match
        Console.WriteLine("Signature bytes=" & nSigLen)
        Console.WriteLine("Key bytes =" & Rsa.KeyBytes(sbPublicKey.ToString))
        If nSigLen <> Rsa.KeyBytes(sbPublicKey.ToString) Then
            Console.WriteLine("ERROR: key length does not match signature")
            Exit Function
        End If

        ' 3.Decrypt using RSA public key
        abData = Rsa.RawPublic(abData, sbPublicKey.ToString)
        Console.WriteLine("RSA_RawPublic returns " & abData.Length & " bytes (expected >0)")
        If abData.Length = 0 Then
            Console.WriteLine("ERROR: failed to decrypt RSA signature.")
            Exit Function
        End If
        ' Display result in hex
        Console.WriteLine("Decrypted signature=" & vbCrLf & Cnv.ToHex(abData))

        ' 4. Decode to extract the original message digest
        abDigest = Rsa.DecodeDigestForSignature(abData)
        ' 5. Convert to hex format
        strDigestHex = Cnv.ToHex(abDigest)

        ' OUTPUT: Digest in hex format
        Console.WriteLine("MD5 digest as hex: " & strDigestHex)
        Mex_ExtractDigestFromSignature = LCase(Cnv.ToHex(abDigest))

    End Function

    Public Function Mex_CreateDigestFromString() As String
        '@Proc: Mex_CreateDigestFromString
        '@Lang: VB.NET
        '@ Creates the MD5 digest of the input string after converting to UTF-8 encoding
        Dim strData As String
        Dim abDataUTF8 As Byte()
        Dim strDigest As String

        ' INPUT: Our original string data in "Latin-1" encoding
        strData = "||2.0|A|1|2009-08-16T16:30:00|1|2009|ingreso|Una sola exhibición|350.00|5.25|397.25|ISP900909Q88|Industrias del Sur Poniente, S.A. de C.V.|Alvaro Obregón|37|3|Col. Roma Norte|México|Cuauhtémoc|Distrito Federal|México|06700|Pino Suarez|23|Centro|Monterrey|Monterrey|Nuevo Léon|México|95460|CAUR390312S87|Rosa María Calderón Uriegas|Topochico|52|Jardines del Valle|Monterrey|Monterrey|Nuevo León|México|95465|10|Caja|Vasos decorados|20.00|200|1|pieza|Charola metálica|150.00|150|IVA|15.00|52.50||"
        Console.WriteLine("INPUT=" & strData)

        ' 1. Convert to UTF-8
        ' -- In VB.NET, it is easier to convert directly to a byte array
        abDataUTF8 = System.Text.Encoding.UTF8.GetBytes(strData)

        ' 2. Create the message digest hash
        strDigest = Hash.HexFromBytes(abDataUTF8, HashAlgorithm.Md5)

        ' OUTPUT: Display digest in hex format
        Console.WriteLine("Digest=" & strDigest)
        Mex_CreateDigestFromString = strDigest

    End Function

    Public Sub Mex_CheckKeyAndCertMatch()
        '@Proc: Mex_Create_Digest
        '@Lang: VB.NET
        '@ Checks that the keys in the private key and certificate match
        Dim strCertFile As String
        Dim strKeyFile As String
        Dim sbPassword As New StringBuilder
        Dim sbPublicKey As New StringBuilder
        Dim sbPrivateKey As New StringBuilder
        Dim nRet As Integer

        ' INPUT: filenames for certificate and private key files
        strCertFile = "aaa010101aaa_CSD_01.cer"
        strKeyFile = "aaa010101aaa_CSD_01.key"
        ' Test password - CAUTION: DO NOT hardcode production passwords!
        sbPassword.Append("a0123456789")

        ' 1. Read in private key from encrypted .key file
        sbPrivateKey = Rsa.ReadEncPrivateKey(strKeyFile, sbPassword.ToString)
        If sbPrivateKey.Length > 0 Then
            Console.WriteLine("Private key is " & Rsa.KeyBits(sbPrivateKey.ToString) & " bits")
        Else
            Console.WriteLine("ERROR: Cannot read private key file.")
            Exit Sub
        End If

        ' 2. Clean up password as we are done with it
        Wipe.String(sbPassword)

        ' 3. Read in public key from certificate
        sbPublicKey = Rsa.GetPublicKeyFromCert(strCertFile)
        If sbPublicKey.Length > 0 Then
            Console.WriteLine("Public key is " & Rsa.KeyBits(sbPublicKey.ToString) & " bits")
        Else
            Console.WriteLine("ERROR: Cannot read certificate file.")
            Exit Sub
        End If

        ' 4. See if the two key strings match
        nRet = Rsa.KeyMatch(sbPrivateKey.ToString, sbPublicKey.ToString)
        If nRet = 0 Then
            Console.WriteLine("OK, key strings match.")
        Else
            Console.WriteLine("FAILED: key strings do not match.")
        End If

        ' 5. Clean up private key string
        Wipe.String(sbPrivateKey)

    End Sub

    Public Sub Mex_Convert_Latin1_To_UTF8()
        '@Proc: Mex_Convert_Latin1_To_UTF8
        '@Lang: VB.NET $
        '@ Checks if a string is valid UTF-8 and converts between Latin-1 and UTF-8 encodings
        Dim strData As String
        Dim strDataUTF8 As String
        Dim nRet As Integer

        ' Our original string data is in "Latin-1" encoding
        strData = "Asociación Mexicana de Estándares para el Comercio Electrónico A.C.|México|"
        Console.WriteLine("INPUT:   " & strData)

        ' Is it valid UTF-8?
        nRet = Cnv.CheckUTF8(strData)
        Console.WriteLine("CNV.CheckUTF8 returns " & nRet & " (0 => Not valid UTF-8)")

        ' So convert to UTF-8: use the standard method in .NET
        strDataUTF8 = System.Text.Encoding.Default.GetString(System.Text.Encoding.UTF8.GetBytes(strData))
        ' Which may not display correctly ...!
        Console.WriteLine("UTF-8:   " & strDataUTF8)

        nRet = Cnv.CheckUTF8(strDataUTF8)
        Console.WriteLine("CNV.CheckUTF8 returns " & nRet & " (1,2,3 => Valid UTF-8)")
    End Sub

    Public Sub Mex_ValidateCert()
        '@Proc: Mex_ValidateCert
        '@Lang: VB.NET
        '@ Checks that a given X.509 certificate really was issued by the issuer and has not expired
        Dim strCert As String
        Dim strIssuerCert As String
        Dim nRet As Integer
        Dim fIsValid As Boolean

        ' INPUT: Filenames of certificate to be checked and issuer's certificate
        strCert = "aaa010101aaa_CSD_01.cer"
        strIssuerCert = "AC_SAT2048.cer"

        ' 1. Was this cert signed by the purported issuer?
        nRet = X509.VerifyCert(strCert, strIssuerCert)
        Console.WriteLine("X509_VerifyCert returns " & nRet)
        If nRet < 0 Then
            Console.WriteLine("ERROR: Validation failed")
        ElseIf nRet > 0 Then
            Console.WriteLine("ERROR: " & General.ErrorLookup(nRet))
        Else
            Console.WriteLine("OK, cert was signed by issuer.")
        End If

        ' 2. Is this cert still valid now?
        fIsValid = X509.CertIsValidNow(strCert)
        Console.WriteLine("X509_CertIsValidNow returns " & fIsValid)
        If Not fIsValid Then
            Console.WriteLine("ERROR: cert has expired")
        Else
            Console.WriteLine("OK, cert is still valid now.")
        End If

    End Sub

    Public Sub Mex_CertToBase64String()
        '@Proc: Mex_CertToBase64String
        '@Lang: VB.NET
        '@ Converts an X.509 certificate file into a base64 string suitable for Certificado field in XML,
        '  and shows how this string form can be treated just like the .cer file.
        Dim strCertString As String
        Dim strCertFile As String
        Dim strThumb1 As String
        Dim strThumb2 As String

        strCertFile = "aaa010101aaa_CSD_01.cer"

        ' Read in certificate file's data to a string
        strCertString = X509.ReadStringFromFile(strCertFile)
        Console.WriteLine("For certificate '" & strCertFile & "':")
        Console.WriteLine(strCertString)

        ' Check that the two versions of the certificate are identical by computing their SHA-1 thumbprints
        strThumb1 = X509.CertThumb(strCertFile, HashAlgorithm.Sha1)
        strThumb2 = X509.CertThumb(strCertString, HashAlgorithm.Sha1)
        Console.WriteLine("SHA-1(file)  =" & strThumb1)
        Console.WriteLine("SHA-1(string)=" & strThumb2)
        If strThumb1 = strThumb2 Then
            Console.WriteLine("Certificates are identical")
        Else
            Console.WriteLine("ERROR: certificates do not match")
        End If

    End Sub

    Public Sub Mex_SAT_SerialNumber()
        '@Proc: Mex_SAT_SerialNumber
        '@Lang: VB.NET
        '@ Extracts the serial number from a SAT-issued X.509 certificate and displays in base64 format
        Dim strCertFile As String
        Dim strSerialNumber As String
        Dim strSerialSAT As String

        ' Extract the certificate's serial number
        strCertFile = "AAA010101AAAsd.cer"
        strSerialNumber = X509.CertSerialNumber(strCertFile)
        Console.WriteLine("X.509 Serial Number=0x" & strSerialNumber)
        ' Decode from hex-encoded integer to string of ASCII digits
        strSerialSAT = System.Text.Encoding.Default.GetString(Cnv.FromHex(strSerialNumber))
        Console.WriteLine("Decoded SAT Format ='" & strSerialSAT & "'")
    End Sub

    Public Sub Mex_QueryCertString()
        '@Proc: Mex_QueryCertString
        '@Lang: VB.NET
        '@ Extracts various details from a certificate string
        Dim strCertificado As String
        Dim strOutput As String
        Dim strQuery As String

        ' INPUT: Certificado string frm XML file. This is the same as in the file aaa010101aaa_CSD_01.cer.
        strCertificado = "MIIDhDCCAmygAwIBAgIUMTAwMDEyMDAwMDAwMDAwMjI1MTcwDQYJKoZIhvcNAQEFBQAwgcMxGTAXBgNVBAcTEENpdWRhZCBkZSBNZXhpY28xFTATBgNVBAgTDE1leGljbywgRC5GLjELMAkGA1UEBhMCTVgxGjAYBgNVBAMTEUFDIGRlIFBydWViYXMgU0FUMTYwNAYDVQQLFC1BZG1pbmlzdHJhY2nzbiBkZSBTZWd1cmlkYWQgZGUgbGEgSW5" & _
            "mb3JtYWNp824xLjAsBgNVBAoUJVNlcnZpY2lvIGRlIEFkbWluaXN0cmFjafNuIFRyaWJ1dGFyaWEwHhcNMDgwODIxMTUyMjA4WhcNMTAwODIxMTUyMjA4WjCBmDElMCMGA1UELRMcQUFBMDEwMTAxQUFBIC8gQUFBQTAxMDEwMUFBQTEeMBwGA1UEBRMVIC8gQUFBQTAxMDEwMUhERlJYWDAxMRIwEAYDVQQKEwlNYXRyaXogU0ExEzARBgNVBA" & _
            "sTClVuaWRhZCAxMCAxEjAQBgNVBAMTCU1hdHJpeiBTQTESMBAGA1UEKRMJTWF0cml6IFNBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDpmiW1q9gyzCFtMcbaFDJexk2IpLoTdNXg4ToGRZ/f+hIjmj3N6ODWX1ARNFGYocEHf113GpW5Oe/mj6UqhBpiH4JRTNR4Udb8myJTArIlODynVHuIUuyhKo7gbMbDdXjilTAYY2XWQuQ7aDtWw" & _
            "ntUmNg4vAC/F3OtRz3+y9wM5QIDAQABox0wGzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQUFAAOCAQEAafyD4gMsOvq7E3raPntmQlJTxpWwNySqskE7fe23HVL9UKFCUlWWx/W8gluxIX9S19y17iWnGbtmbNddHxG5PznPsy/a8PlwNHjDW0FOpia2LsvDrNcdPiJhzL/1OVagkenffFf8bLEetF3ktxZ7ifcH1yxV" & _
            "xpZ7PS/pe8YIOpWRuMmTV4ypGdsw9TW3HVP5IJ/canuQGPTb3LQ8ojihW2dHnC6ojaWW4GHFSZAPhQJ/DaH/UgFjaQke/RBtoAketfROdG+1qYeA1q/is04O4AXNmMByGp7ZnvGNrO9LDBvs3eKN4ZYcQyjxFEbr1X/xUqHCRF1VEkkC5jJQ1ktC4g=="

        ' 1. Get the Issuer's distinguished name
        strOutput = X509.CertIssuerName(strCertificado, ";")
        Console.WriteLine("ISSUER= [" & strOutput & "]")

        ' 2. Get the Subject's distinguished name
        strOutput = X509.CertSubjectName(strCertificado, ";")
        Console.WriteLine("SUBJECT=[" & strOutput & "]")

        ' 3. Get the Serial Number
        strOutput = X509.CertSerialNumber(strCertificado)
        Console.WriteLine("X.509 Serial Number=0x" & strOutput)

        ' 4. Get the expiry date
        strOutput = X509.CertExpiresOn(strCertificado)
        Console.WriteLine("Expires on: " & strOutput)

        ' 5. Get the signature algorithm
        strQuery = "signatureAlgorithm"
        strOutput = X509.QueryCert(strCertificado, strQuery)
        Console.WriteLine(strQuery & "=" & strOutput)

    End Sub

End Module