Attribute VB_Name = "TestFirmaSAT"
' $Id: TestFirmaSAT.bas $

'****************************** LICENSE ***********************************
' * Copyright (C) 2010-20 David Ireland, DI Management Services Pty Limited.
' * All rights reserved. <www.di-mgt.com.au> <www.cryptosys.net>
' * The code in this module is licensed under the terms of the MIT license.
' * For a copy, see <http://opensource.org/licenses/MIT>
' *   Last updated:
' *   $Date: 2020-08-05 23:31 $
' *   $Version: 9.2.0 $
'****************************************************************************
'
' Some tests using the FirmaSAT VB6/VBA interface.
' Requires certain files to exist in the current working directory (see RequiredFilesExist()).
' Direct calls to the DLL begin with "SAT_"; wrapper functions begin with "sat" or "tfd"

' [v2.1] Added options for SHA-1 message digest algorithm
' [v3.0] Added new functions and support for CFDI version 3.0 XML documents
' [v4.0] Added support for selloSAT signatures in the TFD node. Removed dependency on CryptoSys PKI.
' [v4.1] Added SAT_FixBOM function.
' [v5.0] Added support for SAT version 2.2 and 3.2 docs. Added GetKeyAsString function.
' [v5.1] Added WritePfx and QueryCert functions.
' [v5.1.2] Added new test files for versions 2.2 and 3.2
' [v5.2] Added tests for `nomina` supplement and SAT_FILE_NO_BOM option
' [v5.4] Added SAT_Uuid() function and SAT_TFD option for SAT_SignXML().
' [v6.0] Added support for `Retenciones` documents.
' [v7.0] Added support for `Contabilidad` and `ConVol` documents.
'        Added `BIGFILE` option to speed up the processing of large files.
' [v7.2] Added queries for key size and signature algorithm of X.509 certificates.
'        Major updates to test filenames and associated tests.
' [v7.3] Added support for new Complementos.
' [v7.4] Removed support for MD5 algorithm.
' [v8.0] Added support for CFDiv3.3. New functions Asciify() and InsertCert().
' [v8.1] Added support for Contabilidad v1.3.
' [v8.2] Added advanced xpath search features for SAT_GetXmlAttribute.
'        New function SAT_NewKeyFile. Updated test files.
' [v9.0] Added diagnostic method General.Comments(). Major changes in core XMLOK using bloom filter and regex.
' [v9.2] Improvements in handling VBA Unicode strings.

Option Explicit
Option Base 0

Public Const MIN_VERSION As Long = 90200

Public Sub General_Tests()
    Dim n As Long
    Dim i As Long
    Dim j As Long
    Dim s As String
    Dim s1 As String
    Dim ch As String
    Dim fname As String
    Dim newname As String
    Dim keyfile As String
    Dim certfile As String
    Dim password As String
    Dim newpassword As String
    Dim attributeName As String
    Dim elementName As String
    Dim eName As String
    Dim hasBOM As Boolean
    Dim xmlstring As String
    Dim newstring As String
    Dim digest As String
    Dim query As String
    Dim dig1 As String
    Dim dig2 As String
    Dim certfiledata As String
    Dim keyfiledata As String
    Dim xbase As String
    Dim xpath As String
    Dim xpath1 As String
    Dim xmlbytes() As Byte
    Dim isok As Boolean
    
    ' Check if all required test files exist in the CWD
    If Not RequiredFilesExist() Then
        MsgBox "Required test file cannot be found in current working directory: " & CurDir, vbCritical
        Exit Sub
    End If
    
    Debug.Print ("INTERROGATE THE CORE DIFIRMASAT DLL:")
    n = SAT_Version()
    Debug.Print "Version=" & n
    If n < MIN_VERSION Then
        MsgBox "Require FirmaSAT v" & MIN_VERSION & " or higher", vbCritical
        Exit Sub
    End If
    ch = Chr(SAT_LicenceType())
    Debug.Print "LicenceType=" & ch
    Debug.Print "ModuleName=" & satModuleName()
    Debug.Print "CompileTime=" & satCompileTime()
    Debug.Print "Comments=" & satComments()
    
    Debug.Print (vbLf & "FORM THE PIPESTRING FROM AN XML FILE:")
    fname = "cfdv33a-base.xml"
    s = satMakePipeStringFromXml(fname)
    Debug.Print "MakePipeStringFromXml(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "SIGN AN XML FILE:")
    fname = "cfdv33a-base.xml"
    newname = "cfdv33a_new-signed.xml"
    keyfile = "emisor.key"
    password = "12345678a"    ' CAUTION: DO NOT HARD-CODE REAL PASSWORDS!
    certfile = "emisor.cer"
    n = SAT_SignXml(newname, fname, keyfile, password, certfile, 0)
    Debug.Print "SAT_SignXml('" & fname & "'-->'" & newname & "') returns " & n
    Debug.Assert (n = 0)
    ' Did we make a valid XML file?
    n = SAT_ValidateXml(newname, 0)
    Debug.Print "SAT_ValidateXml(" & newname & ") returns " & n
    Debug.Assert (n = 0)

    Debug.Print (vbLf & "VERIFY A SIGNATURE IN AN XML FILE:")
    Debug.Print ("1. One we know is good:")
    fname = "cfdv33a-signed-tfd.xml"
    n = satVerifySignature(fname)
    Debug.Print "SAT_VerifySignature(" & fname & ") returns " & n
    Debug.Assert (n = 0)

    Debug.Print ("2. One we just made, so it should be good:")
    fname = newname
    n = satVerifySignature(fname)
    Debug.Print "SAT_VerifySignature(" & fname & ") returns " & n
    Debug.Assert (n = 0)
    
    Debug.Print (vbLf & "FORM THE DIGEST OF THE PIPESTRING IN AN XML FILE:")
    fname = "cfdv33a-base.xml"
    s = satMakeDigestFromXml(fname)
    Debug.Print "MakeDigestFromXml(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)

    Debug.Print (vbLf & "EXTRACT THE DIGEST FROM THE SIGNATURE IN AN XML FILE:")
    fname = "cfdv33a-signed-tfd.xml"
    s1 = satExtractDigestFromSignature(fname)
    Debug.Print "ExtractDigestFromSignature(" & fname & ")=" & vbLf & s1
    Debug.Assert (Len(s1) > 0)

    Debug.Assert (StrComp(s1, s, vbTextCompare) = 0)
    
    Debug.Print (vbLf & "TRY VALIDATING STRUCTURE OF XML FILES:")
    Debug.Print ("1. A valid one:")
    fname = "cfdv33a-signed-tfd.xml"
    n = SAT_ValidateXml(fname, 0)
    Debug.Print "SAT_ValidateXml(" & fname & ") returns " & n
    Debug.Assert (n = 0)
    
    Debug.Print ("2. An invalid one (missing version):")
    fname = "cfdv33a-bad-nover.xml"
    n = SAT_ValidateXml(fname, 0)
    Debug.Print "SAT_ValidateXml(" & fname & ") returns " & n
    s = satLastError()
    Debug.Print "ErrorLookup(" & n & ")=" & satErrorLookup(n)
    Debug.Print "LastError=" & s

    Debug.Print (vbLf & "EXTRACT AN ATTRIBUTE FROM AN XML FILE:")
    fname = "cfdv33a-signed-tfd.xml"
    elementName = "Comprobante"
    attributeName = "Sello"  ' NB Capital letter 'S'
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print "SAT_GetXmlAttribute('" & fname & "'," & attributeName & "," & elementName & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "GET DETAILS OF X.509 CERTIFICATE:")
    Debug.Print ("1. From embedded `certificado` in XML")
    fname = "cfdv33a-signed-tfd.xml"
    s = satGetCertNumber(fname)
    Debug.Print "GetCertNumber(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    s = satGetCertExpiry(fname)
    Debug.Print "GetCertExpiry(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)

    Debug.Print ("2. From X.509 certificate file")
    fname = "emisor.cer"
    s = satGetCertNumber(fname)
    Debug.Print "GetCertNumber(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    s = satGetCertExpiry(fname)
    Debug.Print "GetCertExpiry(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "GET CERTIFICATE AS A BASE64 STRING:")
    fname = "emisor.cer"
    s = satGetCertAsString(fname)
    Debug.Print "GetCertAsString(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    Debug.Print "Len(GetCertAsString(" & fname & "))=" & Len(s)
    ' Compare against string from XML file
    fname = "cfdv33a-signed-tfd.xml"
    s1 = satGetCertAsString(fname)
    Debug.Print "Len(GetCertAsString(" & fname & "))=" & Len(s1)
    Debug.Assert (Len(s1) > 0)
    Debug.Assert (StrComp(s, s1, vbTextCompare) = 0)

    Debug.Print (vbLf & "MAKE A SIGNATURE FROM A BASE XML FILE:")
    fname = "cfdv33a-base.xml"
    keyfile = "emisor.key"
    password = "12345678a"    ' CAUTION: DO NOT HARD-CODE REAL PASSWORDS!
    s = satMakeSignatureFromXml(fname, keyfile, password)
    Debug.Print "MakeSignatureFromXml(" & fname & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)

    Debug.Print (vbLf & "SIGN A DETALLISTA XML FILE:")
    fname = "cfdv33a-detallista.xml"
    newname = "detallista_new-signed.xml"
    keyfile = "emisor.key"
    password = "12345678a"
    ' CAUTION: DO NOT HARD-CODE REAL PASSWORDS!
    certfile = "emisor.cer"
    n = SAT_SignXml(newname, fname, keyfile, password, certfile, 0)
    Debug.Print "SAT_SignXml('" & fname & "'-->'" & newname & "') returns " & n
    Debug.Assert (n = 0)
    ' Did we make a valid XML file?
    n = SAT_ValidateXml(newname, 0)
    Debug.Print "SAT_ValidateXml(" & newname & ") returns " & n
    Debug.Assert (n = 0)
    n = satVerifySignature(newname)
    Debug.Print "SAT_VerifySignature(" & newname & ") returns " & n
    Debug.Assert (n = 0)
    
    Debug.Print (vbLf & "EXTRACT AN ATTRIBUTE FROM A DETALLISTA XML FILE:")
    fname = "cfdv33a-detallista.xml"
    elementName = "detallista:detallista"
    attributeName = "documentStructureVersion"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print "SAT_GetXmlAttribute('" & fname & "'," & attributeName & "," & elementName & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    Debug.Assert (StrComp(s, "AMC8.1") = 0)
        
    Debug.Print (vbLf & "EXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS:")
    fname = "cfdv33a-base.xml"
    elementName = "cfdi:Emisor"
    attributeName = "Nombre"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print "SAT_GetXmlAttribute('" & fname & "'," & attributeName & "," & elementName & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "EXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS IN ITS NAME:")
    fname = "cfdv33a-nomina12.xml"
    elementName = "nomina12:CompensacionSaldosAFavor"
    attributeName = "Año"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print "SAT_GetXmlAttribute('" & fname & "'," & attributeName & "," & elementName & ")=" & vbLf & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "CHECK PRIVATE KEY MATCHES PUBLIC KEY IN CERTIFICATE:")
    certfile = "emisor.cer"
    keyfile = "emisor.key"
    password = "12345678a"   ' CAUTION: DO NOT HARD-CODE REAL PASSWORDS!
    n = SAT_CheckKeyAndCert(keyfile, password, certfile, 0)
    Debug.Print "SAT_CheckKeyAndCert(" & keyfile & "," & certfile & ")=" & n
    Debug.Assert (n = 0)
    
    certfile = "pac.cer"
    keyfile = "pac.key"
    password = "12345678a"
    n = SAT_CheckKeyAndCert(keyfile, password, certfile, 0)
    Debug.Print "SAT_CheckKeyAndCert(" & keyfile & "," & certfile & ")=" & n
    Debug.Assert (n = 0)

    ' Get embedded certificate from XML doc
    certfile = "cfdv33a-signed-tfd.xml"
    keyfile = "emisor.key"
    password = "12345678a"
    n = SAT_CheckKeyAndCert(keyfile, password, certfile, 0)
    Debug.Print "SAT_CheckKeyAndCert(" & keyfile & "," & certfile & ")=" & n
    Debug.Assert (n = 0)
    
    
    Debug.Print (vbLf & "GET RECEIPT (COMPROBANTE) VERSION NUMBER FROM XML FILE:")
    fname = "cfdv33a-base.xml"
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion(" & fname & ")=" & n
    Debug.Assert (33 = n)
    
    ' Older version...
    fname = "ejemplo_v32-tfd2015.xml"
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion(" & fname & ")=" & n
    Debug.Assert (32 = n)
    

    Debug.Print (vbLf & "CREATE CADENA ORIGINAL DEL TIMBRE FISCAL DIGITAL (PIPESTRING FOR TFD):")
    fname = "cfdv33a-signed-tfd.xml"
    s = tfdMakePipeStringFromXml(fname)
    Debug.Print "tfdMakePipeStringFromXml(" & fname & ")=" & vbCrLf & s
    
    Debug.Print (vbLf & "FORM DIGEST OF PIPESTRING FOR TFD:")
    fname = "cfdv33a-signed-tfd.xml"
    s = tfdMakeDigestFromXml(fname)
    Debug.Print "tfdMakeDigestFromXml(" & fname & ")=" & vbCrLf & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "EXTRACT DIGEST FROM TFD SELLOSAT:")
    certfile = "pac.cer"
    s1 = tfdExtractDigestFromSignature(fname, certfile)
    Debug.Print "tfdExtractDigestFromSignature(" & fname & ")=" & vbCrLf & s1
    Debug.Assert (Len(s1) > 0)
    
    Debug.Assert (StrComp(s1, s, vbTextCompare) = 0)
    
    Debug.Print (vbLf & "PRETEND WE ARE A PAC WITH A KEY ALLOWED TO SIGN THE TFD:")
    ' so create a TFD signature string we could paste into the `selloSAT' node
    fname = "cfdv33a-signed-tfd.xml"
    keyfile = "pac.key"
    password = "12345678a"
    s = tfdMakeSignatureFromXml(fname, keyfile, password)
    Debug.Print "tfdMakeSignatureFromXml(" & fname & ", " & keyfile & ")=" & vbCrLf & s
    Debug.Assert (Len(s) > 0)
    ' Get the correct string from the TFD node
    ' NB Capital 'S' for Sello in TFD v1.1
    s1 = satGetXmlAttribute(fname, "SelloSAT", "TimbreFiscalDigital")
    Debug.Print "Correct=" & vbCrLf & s1
    Debug.Assert (StrComp(s1, s, vbTextCompare) = 0)
    
    Debug.Print (vbLf & "VERIFY SIGNATURE IN TFD SELLOSAT:")
    fname = "cfdv33a-signed-tfd.xml"
    certfile = "pac.cer"
    n = tfdVerifySignature(fname, certfile)
    Debug.Print "tfdVerifySignature(" & fname & ", " & certfile & ")=" & n & " (expected 0)"
    Debug.Assert (n = 0)
    
    Debug.Print (vbLf & "ADD A TFD ELEMENT TO A SIGNED CFDI DOCUMENT USING PAC KEY:")
    fname = "cfdv33a-signed.xml"
    newname = "cfdv33a_new-tfd.xml"
    certfile = "pac.cer"
    keyfile = "pac.key"
    password = "12345678a"
    n = SAT_SignXml(newname, fname, keyfile, password, certfile, SAT_TFD)
    Debug.Assert (n = 0)
    ' Did we make a valid XML file?
    n = SAT_ValidateXml(newname, 0)
    Debug.Print "SAT_ValidateXml(" & newname & ")=" & n
    Debug.Assert (n = 0)
    ' Does it have a valid selloSAT?
    n = tfdVerifySignature(newname, certfile)
    Debug.Print "tfdVerifySignature(" & newname & ")=" & n
    Debug.Assert (n = 0)
    
    
    Debug.Print (vbLf & "ADD UTF-8 BOM TO EXISTING FILE:")
    fname = "cfdv33a-signed-nobom.xml"
    newname = "cfdv33a_new-signed-with-BOM"
    n = SAT_FixBOM(newname, fname, 0)
    Debug.Print "SAT_FixBOM(" & fname & "->" & newname & ")=" & n & " (expected 0)"
    Debug.Assert (n = 0)
    
    Debug.Print (vbLf & "EXTRACT ATTRIBUTES FROM CONSECUTIVE ELEMENTS:")
    fname = "ejemplo_v32-tfd2015.xml"
    attributeName = "descripcion"
    elementName = "cfdi:Concepto"
    For i = 1 To 100
        eName = elementName & "[" & i & "]"
        s = satGetXmlAttribute(fname, attributeName, eName)
        Debug.Print "satGetXmlAttribute(" & attributeName & ", " & eName & ")='" & s & "'"
        If Len(s) = 0 Then
            Exit For
        End If
    Next
           
    Debug.Print (vbLf & "VALIDATE XML WITH STRICT AND LOOSE OPTIONS:")
    fname = "V3_2_BadCurp.xml"
    Debug.Print "Default strict behaviour (badly formed CURP attribute)"
    n = SAT_ValidateXml(fname, 0)
    Debug.Print "SAT_ValidateXml('" & fname & "') returns " & n
    s = satLastError()
    Debug.Print "ErrorLookup(" & n & ")=" & satErrorLookup(n)
    Debug.Print "LastError=" & s
    Debug.Assert (n <> 0)
    
    Debug.Print "Using LOOSE option:"
    n = SAT_ValidateXml(fname, SAT_XML_LOOSE)
    Debug.Print "SAT_ValidateXml('" & fname & "', LOOSE) returns " & n
    Debug.Assert (n = 0)
    
    Debug.Print (vbLf & "GET PRIVATE KEY AS BASE64:")
    fname = "emisor.key"
    s = satGetKeyAsString(fname, "12345678a")
    Debug.Print "GetCertAsString(" & fname & ")=" & vbLf & s
    Debug.Print "Len(satGetKeyAsString(" & fname & "))=" & Len(s)
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "WRITE PFX FROM PRIVATE KEY AND CERT:")
    certfile = "emisor.cer"
    keyfile = "emisor.key"
    password = "12345678a"
    fname = "archivo_new-pfx.txt"
    newpassword = "clavedesalida"
    n = SAT_WritePfxFile(fname, newpassword, keyfile, password, certfile, 0)
    Debug.Print "Sat.WritePfxFile()->" & fname & " returns " & n
    Debug.Assert (n = 0)
    Debug.Print "New PFX file is " & FileLen(fname) & " bytes long."
    
    Debug.Print (vbLf & "GET RFC AND ORG NAME FROM CERT:")
    ' From X.509 certificate file
    fname = "emisor.cer"
    Debug.Print "FILE: " & fname
    s = satQueryCert(fname, "rfc")
    Debug.Print "satQueryCert(rfc)="; s
    Debug.Assert (Len(s) > 0)
    s = satQueryCert(fname, "organizationName")
    Debug.Print "satQueryCert(organizationName)='" & s & "'"
    Debug.Assert (Len(s) > 0)
    ' From embedded `certificado` in XML
    fname = "cfdv33a-signed-tfd.xml"
    Debug.Print "FILE: " & fname
    s = satQueryCert(fname, "rfc")
    Debug.Print "satQueryCert(rfc)=" & s
    Debug.Assert (Len(s) > 0)
    s = satQueryCert(fname, "organizationName")
    Debug.Print "satQueryCert(organizationName)='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "TEST OTHER QUERIES FOR CERT:")
    fname = "emisor.cer"
    Debug.Print "FILE: " & fname
    s = satQueryCert(fname, "notBefore")
    Debug.Print "satQueryCert(notBefore)=" & s
    Debug.Assert (Len(s) > 0)
    s = satQueryCert(fname, "notAfter")
    Debug.Print "satQueryCert(notAfter)=" & s
    Debug.Assert (Len(s) > 0)
    s = satQueryCert(fname, "serialNumber")
    Debug.Print "satQueryCert(serialNumber)=" & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print (vbLf & "READ ENCRYPTED PRIVATE KEY FILE AS PEM STRING:")
    keyfile = "emisor.key"
    s = satGetKeyAsPEMString(keyfile, "12345678a")
    Debug.Print "Key file '" & keyfile & "' in PEM form:"
    Debug.Print s
    
    Debug.Print (vbLf & "SIGN XML TO STRING:")
    ' Read an XML file into a string
    ' [v9.2] CHANGE - always use satAsciify to read in a file to a VBA Unicode string
    fname = "cfdv33a-base.xml"
    xmlstring = satAsciify(fname)
    Debug.Print "String from file '" & fname & "' has " & Len(xmlstring) & " bytes"
    
    ' We can pass key file and certificate as "PEM" strings.
    ' The "BEGIN/END" encapsulation is optional for a certificate,
    ' but is required for the encrypted private key.
    ' These strings are from `emisor-pem.cer` and `emisor-pem.key`, respectively
    certfiledata = _
"-----BEGIN CERTIFICATE-----" & _
"MIIF+TCCA+GgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwggFmMSAwHgYDVQQDDBdBLkMuIDIgZGUgcHJ1ZWJhcyg0MDk2KTEvMC0GA1UECgwmU2VydmljaW8gZGUgQWRtaW5pc3RyYWNpw7NuIFRyaWJ1dGFyaWExODA2BgNVBAsML0FkbWluaXN0cmFjacOzbiBkZSBTZWd1cmlkYWQgZGU" & _
"gbGEgSW5mb3JtYWNpw7NuMSkwJwYJKoZIhvcNAQkBFhphc2lzbmV0QHBydWViYXMuc2F0LmdvYi5teDEmMCQGA1UECQwdQXYuIEhpZGFsZ28gNzcsIENvbC4gR3VlcnJlcm8xDjAMBgNVBBEMBTA2MzAwMQswCQYDVQQGEwJNWDEZMBcGA1UECAwQRGlzdHJpdG8gRmVkZXJhbDESMBAGA1UEBwwJQ295b2Fjw6FuMRUwEw" & _
"YDVQQtEwxTQVQ5NzA3MDFOTjMxITAfBgkqhkiG9w0BCQIMElJlc3BvbnNhYmxlOiBBQ0RNQTAeFw0xNzA1MTgwMzU0NTZaFw0yMTA1MTgwMzU0NTZaMIHlMSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1MgRU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0MxKTAnB" & _
"gNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMSUwIwYDVQQtExxBQUEwMTAxMDFBQUEgLyBIRUdUNzYxMDAzNFMyMR4wHAYDVQQFExUgLyBIRUdUNzYxMDAzTURGUk5OMDkxGzAZBgNVBAsUEkNTRDAxX0FBQTAxMDEwMUFBQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdUcsHIEIgw" & _
"ivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaURalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+WzJiPd3ZpQgI" & _
"edYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45c9gnwJRV618NW0fMeDzuKR0CAwEAAaMdMBswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwDQYJKoZIhvcNAQELBQADggIBABKj0DCNL1lh44y+OcWFrT2icnKF7WySOVihx0oR+HPrWKBMXxo9KtrodnB1tgIx8f+Xjqyphh" & _
"bw+juDSeDrb99PhC4+E6JeXOkdQcJt50Kyodl9URpCVWNWjUb3F/ypa8oTcff/eMftQZT7MQ1Lqht+xm3QhVoxTIASce0jjsnBTGD2JQ4uT3oCem8bmoMXV/fk9aJ3v0+ZIL42MpY4POGUa/iTaawklKRAL1Xj9IdIR06RK68RS6xrGk6jwbDTEKxJpmZ3SPLtlsmPUTO1kraTPIo9FCmU/zZkWGpd8ZEAAFw+ZfI+bdXBf" & _
"vdDwaM2iMGTQZTTEgU5KKTIvkAnHo9O45SqSJwqV9NLfPAxCo5eRR2OGibd9jhHe81zUsp5GdE1mZiSqJU82H3cu6BiE+D3YbZeZnjrNSxBgKTIf8w+KNYPM4aWnuUMl0mLgtOxTUXi9MKnUccq3GZLA7bx7Zn211yPRqEjSAqybUMVIOho6aqzkfc3WLZ6LnGU+hyHuZUfPwbnClb7oFFz1PlvGOpNDsUb0qP42QCGBiTU" & _
"seGugAzqOP6EYpVPC73gFourmdBQgfayaEvi3xjNanFkPlW1XEYNrYJB4yNjphFrvWwTY86vL2o8gZN0Utmc5fnoBTfM9r2zVKmEi6FUeJ1iaDaVNv47te9iS1ai4V4vBY8r" & _
"-----END CERTIFICATE-----"
    keyfiledata = _
"-----BEGIN ENCRYPTED PRIVATE KEY-----" & _
"MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI5qDMtGWYa2wCAggAMBQGCCqGSIb3DQMHBAhFAqj+c0f8JASCBMhNUpNUp57vMu8L3LHBKRBTFl0VE3oqBIEKBHFYYz063iiS0Y3tPW3cplLTSqG25MdbIQcHCxwmPVYNdetHUjqjeR+TklWgtnMbLqvdMmmRxAFuHXznHFIa4U+YNedhFm7sdR2DsGFijm3" & _
"vIpUbvpILtpTrhog/EHAvZXV6+F86cYc9+LUg3d0DRwJc+sWmk+2xOoXvOvvpnnQqfhQxkSknfITmc+HAWgHbKLK2q6e2RixjpWn0sA9LslYD0ZDn5uhrce+QEfK97asraFfiteqXf2Ll8B54Ku/er+O2JEu62vVDFumwMtZOuHKH4NbjOmMzKIwRTKp/1jp6OTGYSKIRiTDXnTETJwgItHahf7UAoM/qnkJa17Ood4hiCY" & _
"opMyCXdhyMDJoFhWRanQODaiocb7XpMm1SEpTtHZeKgEVWSc/obYgSgs4iY497UR2MUVZQSCBdRXCgs5g1c31cCwAZ6r41KMoLOBVLtRXoT0mc0D6ovlwYuJhqYvuwjdNkWJS7qwXuy8b2ux4t027NGUXmgtb9XQDm8yJrdTtm0CktWPKe7i2tQtBC2tAjduGAlBrzY+whySRN8KUJQbYKhOBaLXgEPI93wi/SKHJO13Wvf" & _
"qqjKqrqJwB3tvhjz5E1uDKmDFoivdS76uq+k/xpmF5OWBmypWNViw7kgvmH1OeTBKYkUHIL85skL6pdycGnTk3g0AmG9xtPYu6pdSqUv+N8QmTdmmdu85fDEN0fk2t2BRPANsbIqxopVfj5qIwm+8TbZDdNj8OssxrC5sRy5yDBjV4J+x253yaILn7wgUR6Yj6GaHUUF4GISmFZ/PTbnVPDd424w6hGV8NKtUHXq5ms2kJX" & _
"o6XGiGqjbdePM53QhdSrxTM5Dt76RcAInky6w5s/7gvT/w7tdbVA/SPhp4xgaT8Crmjbk3upcSqNI0HuROBxOs0gRRAWXScUZJ0Vd1V0F+C5cG2R1CtGTYeRmIAwLwcWf6DjY1Q+TOe/W3eTatOo+gIozjYDCk5ZNfeQzq4p1ApN6+gzS8kNxtvKOYJogjV74RK/Xl7u7oLv4SZT7Nl1YRpScW1ouIcNNTP0AC+j2OFZ3Yu" & _
"eN8CcmvXbgSW8pYRooTxnFfo9sdOL624uwRyb2DwwLO0Vo3aBIEIf8sm9sqocXmwh9sxFPEbTXPCuMSao8QjyBOlsCem2589NVZs0h0ipGwdbatcjkgf+hzRoYBdlvHtKHJ8gL/A/Ap8z0+TK5NaVWUA+zXOZRZ66NYfs18DEbJKjwOcnnsLcfAMYoSn697148sL4JBv8IOmM6QXfxCl/0yU0d5/876L5jOL56lfH0eBk8s" & _
"2nioAl3yRBl2wlihWi39sA0bsdHFKYEX+LqPBBCAdxZAvXCCJcdEdxOXSgEiFAmW9+IXFT/WJeGcZ4OmCd3Qf0fxGqFXA/9hIUumWde6s0wN8LjXuFZQaMDaaVIGXKguP3OijsfBF0PYzI+L6CfUi2BLaYNJTlbQxbncmW2PKeDiypgt3ZY1PKV66o5OAJEAkV3vf9cRwXE5T8GwZHA+wx2rWC98hkH15xfI9qEsYulVdcX" & _
"WzCF58HFQjUoDon0e/QMukS0eNgq9ipmoKAWKyy7+TQw7Xx3MmqkGlLHGM=" & _
"-----END ENCRYPTED PRIVATE KEY-----"
    password = "12345678a"
    
    ' Check key and certificate are matched
    n = SAT_CheckKeyAndCert(keyfiledata, password, certfiledata, 0)
    Debug.Print "SAT_CheckKeyAndCert(STRINGS)=" & n
    Debug.Assert (n = 0)
    
    ' Create a new string containing signed XML (UTF-8-encoded)
    ' [OLD METHOD NOW DEPRECATED] - see satSignXmlToBytes() below
    newstring = satSignXmlToString(xmlstring, keyfiledata, password, certfiledata, 0)
    Debug.Print "Signed XML string has " & Len(newstring) & " bytes:"
    ' Note this string contains UTF-8-encoded characters including a BOM which print "funny"
    Debug.Print Left(newstring, 120) & "..." & vbCrLf & "..." & vbCrLf & Right(newstring, 320)
    ' Save string as a file (UTF-8 encoded)
    Call WriteFileFromString("fromstring_new.xml", newstring)
    
    Debug.Print (vbLf & "PASS XML STRING TO OTHER SAT FUNCTIONS:")
    ' We can pass this string as an `szXmlFile` parameter to other SAT_ functions
    n = SAT_ValidateXml(newstring, 0)
    Debug.Print "SAT_ValidateXml(string) returns " & n
    Debug.Assert (n = 0)
    n = SAT_XmlReceiptVersion(newstring, 0)
    Debug.Print "SAT_XmlReceiptVersion(string) returns " & n
    
    Debug.Print (vbLf & "SIGN XML USING EMPTY-ELEMENT TAGS:")
    newstring = satSignXmlToString(xmlstring, keyfile, password, certfile, SAT_XML_EMPTYELEMTAG)
    Debug.Print "Signed XML string has " & Len(newstring) & " bytes:"
    Debug.Print Left(newstring, 120) & "..." & vbCrLf & "..." & vbCrLf & Right(newstring, 262)
    n = SAT_ValidateXml(newstring, 0)
    Debug.Print "SAT_ValidateXml(string) returns " & n
    Debug.Assert (n = 0)
    
    Call WriteFileFromString("fromstring_new-emptyelems.xml", newstring)
    
    Debug.Print (vbLf & "GENERATE 3 UUIDs:")
    s = satUuid()
    Debug.Print "UUID=" & s
    s = satUuid()
    Debug.Print "UUID=" & s
    s = satUuid()
    Debug.Print "UUID=" & s
    
    ' New in [v6.0]
    Debug.Print vbLf & "WORK WITH A `RETENCIONES` DOCUMENT:"
    fname = "Ejemplo_Retenciones-base.xml"
    Debug.Print "FILE=" & fname
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion() returns " & n & " (expecting 1010)"
    Debug.Assert (1010 = n)
    s = satMakeDigestFromXml(fname)
    Debug.Print "satMakeDigestFromXml -> " & s
    Debug.Assert (Len(s))
    ' Use new [v6.0] arguments to find name of root element
    s = satGetXmlAttribute(fname, "", "")
    Debug.Print "Document root element is '" & s & "'"
    Debug.Assert (Len(s))

    ' New in [v7.0]
    
    Debug.Print vbLf & "WORK WITH `CONTABILIDAD` DOCUMENTS:"
    fname = "AAA010101AAA201501CT-base.xml"
    Debug.Print "CATALOGOCUENTAS FILE=" & fname
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion() returns " & n & " (expecting 2011)"
    Debug.Assert (2011 = n)

    Debug.Print "SIGN A CATALOGOCUENTAS DOCUMENT...:"
    newname = "AAA010101AAA201501CT_new.xml"
    keyfile = "emisor.key"
    certfile = "emisor.cer"
    password = "12345678a"   ' CAUTION: DO NOT HARD-CODE REAL PASSWORDS!
    n = SAT_SignXml(newname, fname, keyfile, password, certfile, 0)
    Debug.Print "SAT_SignXml() returns " & n & " (expecting 0)"
    Debug.Assert (0 = n)
    n = SAT_VerifySignature(newname, "", 0)
    Debug.Print "SAT_VerifySignature() returns " & n & " (expecting 0)"
    Debug.Assert (0 = n)

    fname = "AAA010101AAA201501BN-base.xml"
    Debug.Print "BALANZA FILE=" & fname
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion() returns " & n & " (expecting 2111)"
    Debug.Assert (2111 = n)

    Debug.Print "MAKE THE SIGNATURE STRING FOR BALANZA...:"
    s = satMakeSignatureFromXml(fname, keyfile, password)
    Debug.Print "satMakeSignatureFromXml -> " & vbLf & s
    Debug.Assert (Len(s) > 0)
 
    fname = "contab-SelloDigitalContElec-signed.xml"
    Debug.Print "SELLODIGITALCONTELEC FILE=" & fname
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion() returns " & n & " (expecting 2511)"
    Debug.Assert (2511 = n)

    Debug.Print "VERIFY SIGNATURE FOR SELLODIGITALCONTELEC USING PAC CERTIFICATE...:"
    n = SAT_VerifySignature(fname, "pac1024.cer", 0)
    Debug.Print "SAT_VerifySignature() returns " & n & " (expecting 0)"
    Debug.Assert (0 = n)

    Debug.Print vbLf & "WORK WITH `CONTROLESVOLUMETRICOS` DOCUMENT:"
    fname = "ConVolE12345-base.xml"
    Debug.Print "FILE=" & fname
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion() returns " & n & " (expecting 4011)"
    Debug.Assert (4011 = n)


    Debug.Print "SIGN A CONVOL DOCUMENT WITH BIGFILE FLAG...:"
    newname = "ConVolE12345_new-signed.xml"
    ' Use key and cert provided for ConVol tests
    keyfile = "CSD_E12345CV_ACP020530MP5.key"
    certfile = "CSD_E12345CV_ACP020530MP5.cer"
    password = "12345678a"   ' CAUTION: DO NOT HARD-CODE REAL PASSWORDS!
    n = SAT_SignXml(newname, fname, keyfile, password, certfile, SAT_FILE_BIGFILE)
    Debug.Print "SAT_SignXml(BIGFILE) returns " & n & " (expecting 0)"
    Debug.Assert (0 = n)
    n = SAT_VerifySignature(newname, "", 0)
    Debug.Print "SAT_VerifySignature() returns " & n & " (expecting 0)"
    Debug.Assert (0 = n)
    
    
    Debug.Print vbLf & "QUERY KEY SIZE OF CERTIFICATES..."
    query = "keySize"
    
    fname = "emisor1024.cer"
    s = satQueryCert(fname, query)
    Debug.Print "satQueryCert('" & fname & "'," & query & ")=" & s
    Debug.Assert (Len(s) > 0)
    fname = "emisor.cer"
    s = satQueryCert(fname, query)
    Debug.Print "satQueryCert('" & fname & "'," & query & ")=" & s
    Debug.Assert (Len(s) > 0)
    fname = "AC4_SAT.cer"
    s = satQueryCert(fname, query)
    Debug.Print "satQueryCert('" & fname & "'," & query & ")=" & s
    Debug.Assert (Len(s) > 0)

    Debug.Print vbLf & "QUERY SIGNATURE ALGORITHM IN CERTIFICATES..."
    query = "sigAlg"
    
    fname = "emisor1024.cer"
    s = satQueryCert(fname, query)
    Debug.Print "satQueryCert('" & fname & "'," & query & ")=" & s
    Debug.Assert (Len(s) > 0)
    fname = "emisor.cer"
    s = satQueryCert(fname, query)
    Debug.Print "satQueryCert('" & fname & "'," & query & ")=" & s
    Debug.Assert (Len(s) > 0)
    
    Debug.Print vbLf & "READ IN XML DOC AS 'ASCIIFIED' STRING..."
    ' "Esta es una demostración" -> "Esta es una demostraci&#xF3;n"
    fname = "cfdv33a-base.xml"
    xmlstring = satAsciify(fname)
    Debug.Print "ASCIIFIED XML:"
    Debug.Print xmlstring
    
    ' Extract an attribute from the XML string
    s = satGetXmlAttribute(xmlstring, "Nombre", "cfdi:Emisor")
    Debug.Print "cfdi:Emisor/@Nombre=[" & s & "]"
    
    ' Compute digest from XML string
    dig1 = satMakeDigestFromXml(xmlstring)
    Debug.Print "DIG(str) =" & dig1
    '  -- this should match the digest of the original UTF-8-encoded file
    dig2 = satMakeDigestFromXml(fname)
    Debug.Print "DIG(file)=" & dig2
    Debug.Assert (StrComp(dig1, dig2, vbTextCompare) = 0)

    Debug.Print vbLf & "INSERT CERTIFICATE DETAILS INTO XML..."
    fname = "cfdv33a-base-nocertnum.xml"
    newname = "cfdv33a-base_new-pluscert.xml"
    certfile = "emisor.cer"
    n = SAT_InsertCert(newname, fname, certfile, 0)
    Debug.Print "SAT_InsertCert() returns " & n & " (expecting 0)"
    Debug.Assert (0 = n)
    ' Check noCertificado just inserted
    ' Original should be empty
    s = satGetXmlAttribute(fname, "NoCertificado", "cfdi:Comprobante")
    Debug.Print "Old NoCertificado=[" & s & "]"
    s = satGetXmlAttribute(newname, "NoCertificado", "cfdi:Comprobante")
    Debug.Print "New NoCertificado=[" & s & "]"
    Debug.Assert (Len(s) > 0)
    
    Debug.Print vbLf & "INSERT CERTIFICATE DETAILS INTO XML AS STRING..."
    newstring = satInsertCertToString(fname, certfile)
    Debug.Assert (Len(newstring) > 0)
    ' Extract new attribute from XML as string
    s = satGetXmlAttribute(newstring, "NoCertificado", "cfdi:Comprobante")
    Debug.Assert (Len(s) > 0)
    Debug.Print "New noCertificado=[" & s & "]"
      
    Debug.Print vbLf & "SUPPORT FOR CONTABILIDAD V1.3..."
    fname = "AAA010101AAA201705CT.xml"
    Debug.Print "FILE: " & fname
    s = satGetXmlAttribute(fname, "", "")
    Debug.Print "Doc type is '" & s & "'"
    n = SAT_ValidateXml(fname, 0)
    Debug.Print "Sat_ValidateXml() returns " & n & " (0 => OK)"
    Debug.Assert (0 = n)
    n = SAT_VerifySignature(fname, "", 0)
    Debug.Print "SAT_VerifySignature() returns " & n & " (0 => OK)"
    Debug.Assert (0 = n)
    n = SAT_XmlReceiptVersion(fname, 0)
    Debug.Print "SAT_XmlReceiptVersion() returns " & n & " (expecting 2013)"
    ' Get default digest algorithm for this document type (a hack!)
    n = SAT_XmlReceiptVersion(fname, SAT_GEN_DIGALG)
    Debug.Print "Default digest algorithm is SHA-" & n
    
    Debug.Print vbLf & "SAVE KEYFILE WITH NEW PASSWORD..."
    keyfile = "emisor.key"
    password = "12345678a"
    newname = "emisor_new.key"
    newpassword = "password123"
    n = SAT_NewKeyFile(newname, newpassword, keyfile, password, "", 0)
    Debug.Print "SAT_NewKeyFile() returns " & n & " (0 => OK)"
    Debug.Assert (0 = n)
    Debug.Print "Created new key file of length " & FileLen(newname) & " bytes with password '" & newpassword & "'"
    
    Debug.Print "Save again in PEM format..."
    newname = "emisor_new.pem"
    newpassword = "password456"
    n = SAT_NewKeyFile(newname, newpassword, keyfile, password, "", SAT_FORMAT_PEM)
    Debug.Print "SAT_NewKeyFile() returns " & n & " (0 => OK)"
    Debug.Assert (0 = n)
    Debug.Print "Created new key file of length " & FileLen(newname) & " bytes with password '" & newpassword & "'"
    
    Debug.Print "Check new key still matches old certificate..."
    n = SAT_CheckKeyAndCert(newname, newpassword, certfile, 0)
    Debug.Print "SAT_CheckKeyAndCert() returns " & n & " (0 => OK)"
    Debug.Assert (0 = n)
    
    Debug.Print vbLf & "XPATH EXPRESSIONS FOR XML-GET-ATTRIBUTE..."
    fname = "A7.xml"
    Debug.Print "FILE: " & fname
    elementName = "/Comprobante"
    attributeName = "Version"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    elementName = "/Comprobante/Conceptos/Concepto[2]/Impuestos/Traslados/Traslado[1]"
    attributeName = "Importe"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    elementName = "/Comprobante/Conceptos/Concepto[1]/Impuestos/Retenciones/Retencion[2]"
    attributeName = "Importe"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    ' Same as above but shorter
    elementName = "//Conceptos/Concepto[1]//Retencion[2]"
    attributeName = "Importe"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    ' Test for element's existence (and fail)
    elementName = "/Comprobante/Conceptos/Concepto[3]"
    attributeName = ""
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    ' Expecting empty string and LastError to include "!NO MATCH!"
    If (Len(s) = 0) Then
        Debug.Print satLastError()
    End If
    Debug.Assert (Len(s) = 0 And InStr(1, satLastError(), "!NO MATCH!") > 0)
    
    Debug.Print vbLf & "USE XPATH TO FIND ALL ATTRIBUTES NAMED 'IMPORTE'..."
    fname = "A7.xml"
    Debug.Print "FILE: " & fname
    ' Output all attributes named "Importe" in a <Traslado> or <Retencion> element.
    attributeName = "Importe"

    ' First look at each <Concepto> in the <Conceptos> element.
    ' (We can use either "/Comprobante/Conceptos" or "//Conceptos")
    xbase = "//Conceptos/Concepto"
    i = 1
    Do While True
        ' FOREACH //Conceptos/Concepto[i] element output the value of Importe
        xpath = xbase & "[" & i & "]"
        s = satGetXmlAttribute(fname, attributeName, xpath)
        If Len(s) = 0 Then
            Exit Do
        End If
        Debug.Print xpath & "/@" & attributeName & "='" & s & "'"
            ' FOREACH //Conceptos/Concepto[i]//Traslado[j] element output the value of Importe
            ' Long xpath is /Comprobante/Conceptos/Concepto[i]/Impuestos/Traslados/Traslado[j]
            j = 1
            Do While True
                xpath1 = xpath & "//Traslado[" & j & "]"
                s = satGetXmlAttribute(fname, attributeName, xpath1)
                If Len(s) = 0 Then
                    Exit Do
                End If
                Debug.Print xpath1 & "/@" & attributeName & "='" & s & "'"
                j = j + 1
            Loop

            ' FOREACH //Conceptos/Concepto[i]//Retencion[j] element output the value of Importe
            j = 1
            Do While True
                xpath1 = xpath & "//Retencion[" & j & "]"
                s = satGetXmlAttribute(fname, attributeName, xpath1)
                If Len(s) = 0 Then
                    Exit Do
                End If
                Debug.Print xpath1 & "/@" & attributeName & "='" & s & "'"
                j = j + 1
            Loop
        i = i + 1
    Loop
    
    ' Now look in the /Comprobante/Impuestos element.
    ' NB we cannot use "//Impuestos" here
    xpath = "/Comprobante/Impuestos"
    ' FOREACH /Comprobante/Impuestos//Retencion[j] element output the value of Importe
    ' Long xpath is /Comprobante/Impuestos/Retenciones/Retencion[j]
    j = 1
    Do While True
        xpath1 = xpath & "//Retencion[" & j & "]"
        s = satGetXmlAttribute(fname, attributeName, xpath1)
        If Len(s) = 0 Then
            Exit Do
        End If
        Debug.Print xpath1 & "/@" & attributeName & "='" & s & "'"
        j = j + 1
    Loop
    ' FOREACH /Comprobante/Impuestos//Traslado[j] element output the value of Importe
    j = 1
    Do While True
        xpath1 = xpath & "//Traslado[" & j & "]"
        s = satGetXmlAttribute(fname, attributeName, xpath1)
        If Len(s) = 0 Then
            Exit Do
        End If
        Debug.Print xpath1 & "/@" & attributeName & "='" & s & "'"
        j = j + 1
    Loop
    
    
    ' Improvements in [v9.2]
    '
    Debug.Print vbLf & "FIND ATTRIBUTES WITH ACCENTED CHARACTERS"
    fname = "cfdv33a-nomina12B.xml"
    Debug.Print "FILE: " & fname
    ' Attribute name contains non-ASCII character 'ü', Antigüedad="P3Y2M23D"
    elementName = "nomina12:Receptor"
    attributeName = "Antigüedad"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    ' Attribute name contains non-ASCII character character 'ñ', Año="2016"
    elementName = "nomina12:CompensacionSaldosAFavor"
    attributeName = "Año"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    ' Attribute value contains non-ASCII character 'í', Sindicalizado="Sí"
    elementName = "nomina12:Receptor"
    attributeName = "Sindicalizado"
    s = satGetXmlAttribute(fname, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    Debug.Print vbLf & "READ IN A FILE TO A VBA UNICODE STRING THEN PASS THE STRING AS XML DOC..."
    ' Use SAT_Asciify to solve the problem of reading UTF-8-encoded file into a VBA String type
    Debug.Print "FILE: " & fname & " (" & FileLen(fname) & " bytes)"
    xmlstring = satAsciify(fname)
    Debug.Print "xmlstring contains " & Len(xmlstring) & " characters"
    
    Debug.Print "Repeat GetXmlAttribute tests above using XML string as input..."
    ' Attribute name contains non-ASCII character 'ü', Antigüedad="P3Y2M23D"
    elementName = "nomina12:Receptor"
    attributeName = "Antigüedad"
    s = satGetXmlAttribute(xmlstring, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    ' Attribute name contains non-ASCII character character 'ñ', Año="2016"
    elementName = "nomina12:CompensacionSaldosAFavor"
    attributeName = "Año"
    s = satGetXmlAttribute(xmlstring, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    ' Attribute value contains non-ASCII character 'í', Sindicalizado="Sí"
    elementName = "nomina12:Receptor"
    attributeName = "Sindicalizado"
    s = satGetXmlAttribute(xmlstring, attributeName, elementName)
    Debug.Print elementName & "/@" & attributeName & "='" & s & "'"
    Debug.Assert (Len(s) > 0)
    
    Debug.Print vbLf & "CHECK XML IS OK USING UNICODE STRING AS INPUT..."
    n = SAT_ValidateXml(xmlstring, 0)
    Debug.Print "SAT_ValidateXml() returns " & n & " (0 => OK)"
    Debug.Assert (0 = n)
    
    Debug.Print vbLf & "FORM MESSAGE DIGEST OF PIPE STRING USING UNICODE STRING AS INPUT..."
    dig1 = satMakeDigestFromXml(xmlstring)
    Debug.Print "DIGEST(xmlstring)=" & dig1
    Debug.Assert (Len(dig1) > 0)
    
    Debug.Print vbLf & "CHECK MESSAGE DIGEST IS THE SAME WHEN USING THE FILE AS INPUT..."
    dig2 = satMakeDigestFromXml(fname)
    Debug.Print "DIGEST(file)=" & dig2
    Debug.Assert (Len(dig1) > 0)
    ' These should match
    Debug.Assert dig1 = dig2
    
    Debug.Print vbLf & "SIGN XML ENTIRELY IN MEMORY..."
    ' [v9.2] Better to output to a byte array than a string
    ' i.e. satSignXmlToBytes() is preferred to satSignXmlToString()
    keyfile = "emisor.key"
    certfile = "emisor.cer"
    password = "12345678a"
    ' Read in key and cert data to strings
    keyfiledata = satGetKeyAsPEMString(keyfile, password)
    Debug.Assert (Len(keyfiledata) > 0)
    certfiledata = satGetCertAsString(certfile)
    Debug.Assert (Len(certfiledata) > 0)
    
    ' Dim xmlbytes() As Byte
    xmlbytes = satSignXmlToBytes(xmlstring, keyfiledata, password, certfiledata, 0)
    Debug.Print "Signed XML byte array has " & UBound(xmlbytes) + 1 & " bytes"
    Debug.Assert UBound(xmlbytes) >= 0
    newname = "frombytes_new.xml"
    isok = WriteFileFromBytes(newname, xmlbytes)
    Debug.Assert isok
    Debug.Print "Created new signed file: " & newname
    
    Debug.Print vbLf & "MAKE SURE WHAT WE CREATED IS OK..."
    fname = newname
    n = satVerifySignature(fname)
    Debug.Print "SAT_VerifySignature(" & fname & ") returns " & n & " (expecting 0)"
    Debug.Assert n = 0
    Debug.Print "...and we got the same digest value as above..."
    dig2 = satExtractDigestFromSignature(fname)
    Debug.Print "satExtractDigestFromSignature() returns " & dig2
    ' Compare digest values (caution upper/lower case)
    Debug.Assert StrComp(dig1, dig2, vbTextCompare) = 0

    
    
    ' ********************************************
    ' FINALLY, SHOW CURRENT VERSION FOR CORE DLL...
    Debug.Print vbCrLf & "FirmaSAT Version=" & SAT_Version() & " [" & satCompileTime & "]"
    

' Change "#If False" to "#If True" to activate this
#If False Then
    Debug.Print (vbLf & "DISPLAY ALL POSSIBLE ERROR MESSAGES:");
    For i = 0 To 10000
        s = satErrorLookup(i)
       If Len(s) > 0 Then
            Debug.Print i & "=" & s
        End If
    Next i
#End If

    Debug.Print vbCrLf & "ALL DONE."

End Sub

' *********************
' UTILITIES USED HERE *
' *********************

Public Function DispError(nErrCode As Long) As String
' Return string containing error message
    Dim strLast As String
    If 0 = nErrCode Then
        Return
    End If
    DispError = "Error code " & nErrCode & ": " & satErrorLookup(nErrCode)
    strLast = satLastError()
    If Len(strLast) > 0 Then
        DispError = DispError & ": " & strLast
    End If
    
End Function

Public Function RequiredFilesExist() As Boolean
' Check for required files in current working directory
    Dim arrFiles As Variant
    Dim vnt As Variant
    
    ' Updated [2020-08-05]
    arrFiles = Array( _
        "A7.xml", "AAA010101AAA201501BN-base.xml", "AAA010101AAA201501CT-base.xml", "AAA010101AAA201705CT.xml", "AC4_SAT.cer", _
        "cfdv33a-base.xml", "cfdv33a-base-nocertnum.xml", "cfdv33a-bad-nover.xml", "cfdv33a-cce11-min.xml", "cfdv33a-cce11.xml", _
        "cfdv33a-detallista-min.xml", "cfdv33a-detallista.xml", "cfdv33a-min.xml", "cfdv33a-nomina12.xml", "cfdv33a-nomina12B.xml", _
        "cfdv33a-pagos10-min.xml", "cfdv33a-pagos10.xml", "cfdv33a-signed-tfd.xml", "cfdv33a-signed.xml", _
        "contab-SelloDigitalContElec-signed.xml", "ConVolE12345-base.xml", "ConVolE12345-signed2015.xml", _
        "CSD_E12345CV_ACP020530MP5.cer", "CSD_E12345CV_ACP020530MP5.key", _
        "Ejemplo_Retenciones-base.xml", "Ejemplo_Retenciones-signed-tfd.xml", _
        "ejemplo_v32-tfd2015.xml", "emisor-pem.cer", "emisor-pem.key", "emisor.cer", "emisor.key", _
        "emisor1024.cer", "emisor1024.key", "pac.cer", "pac.key", "pac1024.cer", "pac1024.key", _
        "V3_2_BadCurp.xml" _
    )
    For Each vnt In arrFiles
        If Not IsNormalFile(CStr(vnt)) Then
            Debug.Print "**ERROR: Cannot find file " & vnt
            Exit Function
        End If
    Next
    ' If we got here, all is OK
    RequiredFilesExist = True

End Function

Public Function IsNormalFile(sFileName As String) As Boolean
    Dim sDir As String
    If Len(sFileName) = 0 Then
        IsNormalFile = False
        Exit Function
    End If
    sDir = Dir(sFileName, vbNormal)
    IsNormalFile = (Len(sDir) > 0)
End Function

Public Sub Our_Assert(bState As Boolean, Optional strMsg As String)
    If bState = False Then
        If vbYes = MsgBox("ASSERT ERROR: " & strMsg & vbCrLf & "Stop the program?", vbCritical + vbYesNo, "ASSERT ERROR") Then
            Stop
        End If
    End If
End Sub

Public Function FileHasBOM(sFilePath As String) As Boolean
' Returns true if file has a UTF-8 byte order mark (BOM)
    Dim abIn() As Byte
    Dim hFile As Integer
    
    ' Check if file exists
    If Len(Dir(sFilePath)) = 0 Or FileLen(sFilePath) < 3 Then
        Exit Function
    End If
    hFile = FreeFile
    Open sFilePath For Binary Access Read As #hFile
    abIn = InputB(3, #hFile)
    Close #hFile
    ' BOM consists of three bytes (0xEF, 0xBB, 0xBF)
    FileHasBOM = (abIn(0) = &HEF And abIn(1) = &HBB And abIn(2) = &HBF)
    
End Function

Public Function ReadFileIntoString(sFilePath As String) As String
' Reads file (if it exists) into a string.
    Dim strIn As String
    Dim hFile As Integer
    
    ' Check if file exists
    If Len(Dir(sFilePath)) = 0 Then
        Exit Function
    End If
    hFile = FreeFile
    Open sFilePath For Binary Access Read As #hFile
    strIn = Input(LOF(hFile), #hFile)
    Close #hFile
    ReadFileIntoString = strIn
    
End Function

Public Function WriteFileFromString(sFilePath As String, strIn As String) As Boolean
' Creates a file from a string. Clobbers any existing file.
On Error GoTo OnError
    Dim hFile As Integer
    
    If Len(Dir(sFilePath)) > 0 Then
        Kill sFilePath
    End If
    hFile = FreeFile
    Open sFilePath For Binary Access Write As #hFile
    Put #hFile, , strIn
    Close #hFile
    WriteFileFromString = True
Done:
    Exit Function
OnError:
    Resume Done
    
End Function

Public Function WriteFileFromBytes(sFilePath As String, abData() As Byte) As Boolean
' Creates a file from a string. Clobbers any existing file.
On Error GoTo OnError
    Dim hFile As Integer
    
    If Len(Dir(sFilePath)) > 0 Then
        Kill sFilePath
    End If
    hFile = FreeFile
    Open sFilePath For Binary Access Write As #hFile
    Put #hFile, , abData
    Close #hFile
    WriteFileFromBytes = True
Done:
    Exit Function
OnError:
    Resume Done
    
End Function