#! python3 # -*- coding: utf8 -*- """Some tests for `firmasat.py` the Python interface to CryptoSys FirmaSAT.""" # test_firmasat.py: version 10.50.0 # $Date: 2023-04-18 03:17:00 $ # ************************** LICENSE ***************************************** # Copyright (C) 2016-23 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> # **************************************************************************** from firmasat import * # @UnusedWildImport import os import sys import pytest import shutil import random from glob import iglob _MIN_FSA_VERSION = 105028 # Show some info about the core CryptoSys DLL print("INFO ABOUT CORE DLL...") print("FSA version =", Gen.version()) print("module_name =", Gen.module_name()) print("compile_time =", Gen.compile_time()) print("platform =", Gen.core_platform()) print("licence_type =", Gen.licence_type()) print("comments =", Gen.comments()) # Show some system values print("SOME SYSTEM VALUES...") print("sys.getdefaultencoding()=", sys.getdefaultencoding()) print("sys.getfilesystemencoding()=", sys.getfilesystemencoding()) print("sys.platform()=", sys.platform) print("cwd =", os.getcwd()) if Gen.version() < _MIN_FSA_VERSION: raise Exception(('Require PKI version ', str(_MIN_FSA_VERSION) + ' or greater')) # GLOBAL VARS # Remember CWD where we started start_dir = os.getcwd() # Temp directory to use as CWD for tests - set by `setup_temp_dir()` ourtmp_dir = "" # Flag to delete tmp directory when finished - used in `reset_start_dir()` # Change with command-line argument `nodelete` - see `main()` delete_tmp_dir = True # JIGGERY-POKERY FOR A TEMP WORKING DIRECTORY # start_dir/ # test_firmasat.py # this module # work/ # this _must_ exist # <all required test files> # tmp.XXXXXXXX/ # created by `setup_temp_dir()` # <copy of all required test files> # <files created by tests> def setup_temp_dir(): """Set up a fresh temp directory to work in.""" global ourtmp_dir # `work` should be a sub-directory of the cwd and must exist work_dir = os.path.join(start_dir, "work") print("\nExpecting to find work dir:", work_dir) assert os.path.isdir(work_dir) # It should contain all the required test files # Create a temp sub-directory in `work` -- random 8 hex digits r1 = random.randrange(0, (1 << 16)) r2 = random.randrange(0, (1 << 16)) ourtmp_dir = os.path.join( work_dir, "tmp." + format(r1, '04X') + format(r2, '04X')) os.mkdir(ourtmp_dir) assert (os.path.isdir(ourtmp_dir)) # Copy the required temp files for f in iglob(os.path.join(work_dir, "*.*")): if (os.path.isfile(f) and not f.endswith('.zip')): shutil.copy(f, ourtmp_dir) # Set CWD to be inside temp os.chdir(ourtmp_dir) print("Working in new temp directory:", os.getcwd()) def reset_start_dir(): """Set CWD back to where we started and delete temp dir.""" if not os.path.isdir(start_dir): return if (ourtmp_dir == start_dir): return os.chdir(start_dir) print("") # print "CWD:", os.getcwd() # Remove the temp direcory if (delete_tmp_dir and 'tmp.' in ourtmp_dir): print("Removing temp directory:", ourtmp_dir) shutil.rmtree(ourtmp_dir, ignore_errors=True) else: print("Temp directory '%s' is left in place." % (ourtmp_dir)) # MORE JIGGERY_POKERY FOR py.test # Thanks to Brian Okken for the base code # <http://pythontesting.net/framework/pytest/pytest-session-scoped-fixtures/> @pytest.fixture(scope="module", autouse=True) def divider_module(request): print(("\n --- module %s() start ---" % request.module.__name__)) setup_temp_dir() def fin(): print(("\n --- module %s() done ---" % request.module.__name__)) reset_start_dir() request.addfinalizer(fin) @pytest.fixture(scope="function", autouse=True) def divider_function(request): print(("\n --- function %s() start ---" % request.function.__name__)) os.chdir(ourtmp_dir) def fin(): print(("\n --- function %s() done ---" % request.function.__name__)) os.chdir(start_dir) request.addfinalizer(fin) # FILE-RELATED UTILITIES def read_binary_file(fname): with open(fname, "rb") as f: return bytearray(f.read()) def read_text_file(fname): with open(fname, "rb") as f: return f.read().decode() def write_file(fname, data): with open(fname, "wb") as f: f.write(data) def _print_file(fname): """Print contents of text file""" s = read_text_file(fname) print(s) def _dump_file(fname): """Print contents of text file with filename header and rulers""" s = read_text_file(fname) ndash = (24 if len(s) > 24 else len(s)) # hack print("FILE:", fname) print("-" * ndash) print(s) print("-" * ndash) def _file_has_bom(fname): """Returns True if file has a UTF-8 BOM or False if not.""" buf = read_binary_file(fname) if (len(buf) < 3): return False # BOM consists of three bytes (0xEF, 0xBB, 0xBF) return (buf[0] == 0xEF and buf[1] == 0xBB and buf[2] == 0xBF) # ERROR def disp_error(n): """Display details of last error.""" s = Err.last_error() print("ERROR %d: %s: %s" % (n, Err.error_lookup(n), "\n" + s if s else "")) ################### # THE TESTS PROPER ################### def test_version(): print("VERSION:", Gen.version()) assert Gen.version() >= _MIN_FSA_VERSION def test_error_lookup(): print("\nLOOKUP SOME ERROR CODES...") for n in range(10): s = Err.error_lookup(n) print("error_lookup(" + str(n) + ")=" + s) assert (len(s) > 0) def test_make_digest(): print("\nFORM MESSAGE DIGESTS...") fname = 'cfdv40-ejemplo.xml' print("FILE:", fname) dig = Sello.make_digest(fname) print("DEFAULT:", dig) dig = Sello.make_digest(fname, HashAlg.SHA1) print("SHA-1 :", dig) dig = Sello.make_digest('cfdv40-ejemplo.xml') print("dig :", dig) def test_make_pipestring_and_sig(): print("\nCREATE PIPE STRING...") fname = 'cfdv40-ejemplo.xml' print("FILE:", fname) s = Sello.make_pipestring(fname) print(s) keyfile = 'emisor.key' passwd = '12345678a' sig = Sello.make_sig(fname, keyfile, passwd) print("SIG:", sig) def test_query_cert(): print("\nQUERY X.509 CERT...") fname = 'cfdv40-ejemplo-signed-tfd.xml' print("FILE:", fname) query = 'serialNumber' s = Pkix.query_cert(fname, query) print("Pkix.query_cert(" + query + ")=[" + s + "]") query = 'keySize' s = Pkix.query_cert(fname, query) print("Pkix.query_cert(" + query + ")=[" + s + "]") query = 'organizationName' s = Pkix.query_cert(fname, query) print("Pkix.query_cert(" + query + ")=[" + s + "]") fname = "AC4_SAT.cer" print("FILE:", fname) query = 'serialNumber' s = Pkix.query_cert(fname, query) print("Pkix.query_cert(" + query + ")=[" + s + "]") query = 'keySize' s = Pkix.query_cert(fname, query) print("Pkix.query_cert(" + query + ")=[" + s + "]") try: _ = Pkix.query_cert(fname, "BADQUERY") except Error as e: print("(Expected) Error:", e) else: raise Exception("Test should have failed.") n = Pkix.query_cert('AC4_SAT.cer', 'keySize') print("keySize =", n) n = Pkix.query_cert('AC4_SAT.cer', Pkix.Query.KEYSIZE) print("Query.KEYSIZE =", n) def test_validate_xml(): print("\nVALIDATE XML SYNTAX...") # A valid XML file n = Xmlu.validate_xml('cfdv40-ejemplo.xml') assert (0 == n) fname = 'cfdv40-ejemplo.xml' n = Xmlu.validate_xml(fname) print("validate_xml('%s') returns %d (expected zero => OK)" % (fname, n)) print("EXPECTING ERRORS...") # XML with a non-conforming attribute fname = "cfdv40-iedu-badcurp.xml" n = Xmlu.validate_xml(fname) print("validate_xml('%s') returns %d (expected nonzero error)" % (fname, n)) if (n != 0): disp_error(n) assert (n != 0) # print "But..." n = Xmlu.validate_xml(fname, loose=True) print("validate_xml('%s', loose) returns %d (expected zero => OK)" % (fname, n)) assert (0 == n) # Not an XML file fname = "emisor.cer" n = Xmlu.validate_xml(fname) print("validate_xml('%s') returns %d (expected nonzero error)" % (fname, n)) if (n != 0): disp_error(n) assert (n != 0) print("...END OF EXPECTED ERRORS.") def test_get_attribute(): print("\nGET ATTRIBUTE FROM XML...") fname = 'cfdv40-ejemplo.xml' s = Xmlu.get_attribute(fname, "Nombre", "cfdi:Emisor") print("Xmlu.get_attribute() returns %s" % (s)) # Get i'th element until no match found for i in range(1, 100): elem = f"Concepto[{i}]" a = Xmlu.get_attribute(fname, "Descripcion", elem) print(f"{elem}={a}") if (a == Xmlu.xml_no_match()): break def test_receipt_id(): print("\nGET COMPROBANTE VERSION OR ID NUMBER...") n = Xmlu.receipt_version('cfdv40-ejemplo.xml') assert (40 == n) # Other types of files for (fname, exp) in [ ("cfdv40-ejemplo.xml", 40), ("Ejemplo_Retenciones-base.xml", 1010), ("AAA010101AAA201501CT-base.xml", 2011), ("AAA010101AAA201501BN-base.xml", 2111), ("ConVolE12345-signed2015.xml", 4011), ]: print("FILE:", fname) n = Xmlu.receipt_version(fname) print(" receipt_version() returns %d (expected %d)" % (n, exp)) assert (n == exp) # Show root element of document print(" ROOT=%s" % (Xmlu.get_attribute(fname, "", ""))) def test_uuid(): print("\nTEST RANDOM UUID...") for dummy in range(5): s = Pkix.uuid() print(s) def test_key_string(): print("\nTEST KEY AS STRING...") s = Pkix.get_key_as_string('emisor.key', '12345678a') assert (len(s) > 0) s = Pkix.get_key_as_string( 'emisor.key', '12345678a', Pkix.KeyOpt.ENCRYPTED_PEM) assert (len(s) > 0) fname = "emisor.key" pwd = '12345678a' # CAUTION: do not hardcode passwords s = Pkix.get_key_as_string(fname, pwd) print("Pkix.get_key_as_string('%s'):\n%s" % (fname, s)) s = Pkix.get_key_as_string(fname, pwd, Pkix.KeyOpt.ENCRYPTED_PEM) print("Pkix.get_key_as_string('%s', ENCRYPTED_PEM):\n%s" % (fname, s)) def test_cert_string(): print("\nTEST CERT AS STRING...") s = Pkix.get_cert_as_string('emisor.cer') assert (len(s) > 0) s = Pkix.get_cert_as_string('cfdv40-ejemplo-signed-tfd.xml') assert (len(s) > 0) fname = "emisor.cer" s = Pkix.get_cert_as_string(fname) print("Pkix.get_cert_as_string('%s'):\n%s" % (fname, s)) fname = 'cfdv40-ejemplo-signed-tfd.xml' s = Pkix.get_cert_as_string(fname) print("Pkix.get_cert_as_string('%s'):\n%s" % (fname, s)) # We can query this string directly as though it were an X.509 file print("Query the string directly...") rfc = Pkix.query_cert(s, Pkix.Query.RFC) print("RFC =", rfc) assert (len(rfc) > 0) alg = Pkix.query_cert(s, Pkix.Query.SIGALG) print("alg =", alg) assert (len(alg) > 0) def test_write_pfx(): print("\nWRITE PFX FILE...") keyFile = "emisor.key" certFile = "emisor.cer" pfxFile = "emisor.pfx" n = Pkix.write_pfx_file(pfxFile, "password1", keyFile, '12345678a', certFile) assert (0 == n) print("Created new file '%s'" % (pfxFile)) contents = read_text_file(pfxFile) print("Contents:\n" + contents) def test_check_key_cert(): print("\nCHECK KEY MATCHES CERT...") # Key and cert match keyFile = "emisor.key" certFile = "emisor.cer" n = Pkix.check_key_and_cert(keyFile, '12345678a', certFile) print("Pkix.check_key_and_cert() returns %d (expecting 0 => OK)" % (n)) print("EXPECTING ERRORS...") # Key and cert DO NOT match keyFile = "emisor.key" certFile = "pac.cer" n = Pkix.check_key_and_cert(keyFile, '12345678a', certFile) print("Pkix.check_key_and_cert() returns %d" % (n)) disp_error(n) # password is wrong keyFile = "emisor.key" certFile = "pac.cer" n = Pkix.check_key_and_cert(keyFile, 'wrong password', certFile) print("Pkix.check_key_and_cert() returns %d" % (n)) disp_error(n) # Key file is invalid keyFile = "cfdv40-ejemplo.xml" certFile = "pac.cer" n = Pkix.check_key_and_cert(keyFile, '12345678a', certFile) print("Pkix.check_key_and_cert() returns %d" % (n)) disp_error(n) print("...END OF EXPECTED ERRORS.") def test_sign_xml(): print("\nSIGN XML FILE...") basefile = "cfdv40-ejemplo.xml" newfile = "cfdv40-ejemplo-new-signed.xml" n = Sello.sign_xml(newfile, basefile, "emisor.key", '12345678a', "emisor.cer") print("Sello.sign_xml() returns %d (expecting 0 => OK)" % (n)) print("Created new XML file '%s'" % (newfile)) def test_extract_digest(): print("\nEXTRACT DIGEST FROM SELLO...") fname = "cfdv40-ejemplo-signed-tfd.xml" extr_dig = Sello.extract_digest_from_sig(fname) print(extr_dig) made_dig = Sello.make_digest(fname) print(made_dig) def test_verify_sig(): print("\nVERIFY SIGNATURE IN XML FILE...") n = Sello.verify_sig('cfdv40-ejemplo-signed-tfd.xml') print("Sello.verify_sig() returns %s" % (n)) # Override certificado included in XML with (correct) cert file n = Sello.verify_sig('cfdv40-ejemplo-signed-tfd.xml', 'emisor.cer') print("Sello.verify_sig() returns %s" % (n)) print("ERRORS EXPECTED...") # Override with wrong certificate file n = Sello.verify_sig('cfdv40-ejemplo-signed-tfd.xml', 'pac.cer') print("Sello.verify_sig() returns %s" % (n)) disp_error(n) # Sello has been changed n = Sello.verify_sig('cfdv40-ejemplo-badsign.xml') print("Sello.verify_sig() returns %s" % (n)) disp_error(n) # File does not exist n = Sello.verify_sig('missing.xml') print("Sello.verify_sig() returns %s" % (n)) disp_error(n) # Not an XML file n = Sello.verify_sig('pac.cer') print("Sello.verify_sig() returns %s" % (n)) disp_error(n) print("...END OF EXPECTED ERRORS.") def test_fix_bom(): print("\nFIX BOM IN XML FILE...") infile = "cfdv40-ejemplo.xml" outfile = "cfdv40-ejemplo-nobom.xml" print("Create a signed XML file with no BOM...") n = Sello.sign_xml(outfile, infile, "emisor.key", '12345678a', "emisor.cer", signopts=Sello.SignOpts.NOBOM) print("Sello.sign_xml() returns %d (expecting 0 => OK)" % (n)) print("Created new XML file '%s'" % (outfile)) has_bom = _file_has_bom(outfile) print("File %s a UTF-8 BOM" % ("DOES NOT have" if not has_bom else "HAS")) print("Now add a BOM to it...") infile = outfile outfile = "cfdv40-ejemplo-withbom.xml" n = Xmlu.fix_bom(outfile, infile) print("Xmlu.fix_bom() returns %d (expecting 0 => OK)" % (n)) print("Created new XML file '%s'" % (outfile)) has_bom = _file_has_bom(outfile) print("File %s a UTF-8 BOM" % ("DOES NOT have" if not has_bom else "HAS")) def test_sign_xml_to_buf(): print("\nSIGN XML FILE TO BUFFER...") basefile = "cfdv40-ejemplo.xml" print("bytes <-- file") xmlsigned = Sello.sign_xml_file_to_buf( basefile, "emisor.key", '12345678a', "emisor.cer") print("Signed XML:\n" + xmlsigned.decode()[0:60] + '\n ...[snip]... \n' + xmlsigned.decode()[-60:]) # Write to disk so we can examine later write_file("frombuf1.xml", xmlsigned) # Read in file to a byte array, then sign to a buffer xmlbase = read_binary_file(basefile) print("Read in %d bytes from file" % (len(xmlbase))) print("bytes <-- bytes") xmlsigned = Sello.sign_xml_data_to_buf( xmlbase, "emisor.key", '12345678a', "emisor.cer", signopts=Sello.SignOpts.USEEMPTYELEMENTS) print("Signed XML:\n" + xmlsigned.decode()[0:60] + '\n ...[snip]... \n' + xmlsigned.decode()[-60:]) write_file("frombuf2.xml", xmlsigned) # 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. # CAUTION: no white space between """ and ----- certdata = """-----BEGIN CERTIFICATE----- MIIF+TCCA+GgAwIBAgIUMzAwMDEwMDAwMDAzMDAwMjM3MDgwDQYJKoZIhvcNAQELBQAwggFmMSAwHgY DVQQDDBdBLkMuIDIgZGUgcHJ1ZWJhcyg0MDk2KTEvMC0GA1UECgwmU2VydmljaW8gZGUgQWRtaW5pc3 RyYWNpw7NuIFRyaWJ1dGFyaWExODA2BgNVBAsML0FkbWluaXN0cmFjacOzbiBkZSBTZWd1cmlkYWQgZ GUgbGEgSW5mb3JtYWNpw7NuMSkwJwYJKoZIhvcNAQkBFhphc2lzbmV0QHBydWViYXMuc2F0LmdvYi5t eDEmMCQGA1UECQwdQXYuIEhpZGFsZ28gNzcsIENvbC4gR3VlcnJlcm8xDjAMBgNVBBEMBTA2MzAwMQs wCQYDVQQGEwJNWDEZMBcGA1UECAwQRGlzdHJpdG8gRmVkZXJhbDESMBAGA1UEBwwJQ295b2Fjw6FuMR UwEwYDVQQtEwxTQVQ5NzA3MDFOTjMxITAfBgkqhkiG9w0BCQIMElJlc3BvbnNhYmxlOiBBQ0RNQTAeF w0xNzA1MTgwMzU0NTZaFw0yMTA1MTgwMzU0NTZaMIHlMSkwJwYDVQQDEyBBQ0NFTSBTRVJWSUNJT1Mg RU1QUkVTQVJJQUxFUyBTQzEpMCcGA1UEKRMgQUNDRU0gU0VSVklDSU9TIEVNUFJFU0FSSUFMRVMgU0M xKTAnBgNVBAoTIEFDQ0VNIFNFUlZJQ0lPUyBFTVBSRVNBUklBTEVTIFNDMSUwIwYDVQQtExxBQUEwMT AxMDFBQUEgLyBIRUdUNzYxMDAzNFMyMR4wHAYDVQQFExUgLyBIRUdUNzYxMDAzTURGUk5OMDkxGzAZB gNVBAsUEkNTRDAxX0FBQTAxMDEwMUFBQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJdU csHIEIgwivvAantGnYVIO3+7yTdD1tkKopbL+tKSjRFo1ErPdGJxP3gxT5O+ACIDQXN+HS9uMWDYnaU RalSIF9COFCdh/OH2Pn+UmkN4culr2DanKztVIO8idXM6c9aHn5hOo7hDxXMC3uOuGV3FS4ObkxTV+9 NsvOAV2lMe27SHrSB0DhuLurUbZwXm+/r4dtz3b2uLgBc+Diy95PG+MIu7oNKM89aBNGcjTJw+9k+Wz JiPd3ZpQgIedYBD+8QWxlYCgxhnta3k9ylgXKYXCYk0k0qauvBJ1jSRVf5BjjIUbOstaQp59nkgHh45 c9gnwJRV618NW0fMeDzuKR0CAwEAAaMdMBswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwDQYJKoZ IhvcNAQELBQADggIBABKj0DCNL1lh44y+OcWFrT2icnKF7WySOVihx0oR+HPrWKBMXxo9KtrodnB1tg Ix8f+Xjqyphhbw+juDSeDrb99PhC4+E6JeXOkdQcJt50Kyodl9URpCVWNWjUb3F/ypa8oTcff/eMftQ ZT7MQ1Lqht+xm3QhVoxTIASce0jjsnBTGD2JQ4uT3oCem8bmoMXV/fk9aJ3v0+ZIL42MpY4POGUa/iT aawklKRAL1Xj9IdIR06RK68RS6xrGk6jwbDTEKxJpmZ3SPLtlsmPUTO1kraTPIo9FCmU/zZkWGpd8ZE AAFw+ZfI+bdXBfvdDwaM2iMGTQZTTEgU5KKTIvkAnHo9O45SqSJwqV9NLfPAxCo5eRR2OGibd9jhHe8 1zUsp5GdE1mZiSqJU82H3cu6BiE+D3YbZeZnjrNSxBgKTIf8w+KNYPM4aWnuUMl0mLgtOxTUXi9MKnU ccq3GZLA7bx7Zn211yPRqEjSAqybUMVIOho6aqzkfc3WLZ6LnGU+hyHuZUfPwbnClb7oFFz1PlvGOpN DsUb0qP42QCGBiTUseGugAzqOP6EYpVPC73gFourmdBQgfayaEvi3xjNanFkPlW1XEYNrYJB4yNjphF rvWwTY86vL2o8gZN0Utmc5fnoBTfM9r2zVKmEi6FUeJ1iaDaVNv47te9iS1ai4V4vBY8r -----END CERTIFICATE----""" keydata = """-----BEGIN ENCRYPTED PRIVATE KEY----- MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI5qDMtGWYa2wCAggA MBQGCCqGSIb3DQMHBAhFAqj+c0f8JASCBMhNUpNUp57vMu8L3LHBKRBTFl0VE3oq BIEKBHFYYz063iiS0Y3tPW3cplLTSqG25MdbIQcHCxwmPVYNdetHUjqjeR+TklWg tnMbLqvdMmmRxAFuHXznHFIa4U+YNedhFm7sdR2DsGFijm3vIpUbvpILtpTrhog/ EHAvZXV6+F86cYc9+LUg3d0DRwJc+sWmk+2xOoXvOvvpnnQqfhQxkSknfITmc+HA WgHbKLK2q6e2RixjpWn0sA9LslYD0ZDn5uhrce+QEfK97asraFfiteqXf2Ll8B54 Ku/er+O2JEu62vVDFumwMtZOuHKH4NbjOmMzKIwRTKp/1jp6OTGYSKIRiTDXnTET JwgItHahf7UAoM/qnkJa17Ood4hiCYopMyCXdhyMDJoFhWRanQODaiocb7XpMm1S EpTtHZeKgEVWSc/obYgSgs4iY497UR2MUVZQSCBdRXCgs5g1c31cCwAZ6r41KMoL OBVLtRXoT0mc0D6ovlwYuJhqYvuwjdNkWJS7qwXuy8b2ux4t027NGUXmgtb9XQDm 8yJrdTtm0CktWPKe7i2tQtBC2tAjduGAlBrzY+whySRN8KUJQbYKhOBaLXgEPI93 wi/SKHJO13WvfqqjKqrqJwB3tvhjz5E1uDKmDFoivdS76uq+k/xpmF5OWBmypWNV iw7kgvmH1OeTBKYkUHIL85skL6pdycGnTk3g0AmG9xtPYu6pdSqUv+N8QmTdmmdu 85fDEN0fk2t2BRPANsbIqxopVfj5qIwm+8TbZDdNj8OssxrC5sRy5yDBjV4J+x25 3yaILn7wgUR6Yj6GaHUUF4GISmFZ/PTbnVPDd424w6hGV8NKtUHXq5ms2kJXo6XG iGqjbdePM53QhdSrxTM5Dt76RcAInky6w5s/7gvT/w7tdbVA/SPhp4xgaT8Crmjb k3upcSqNI0HuROBxOs0gRRAWXScUZJ0Vd1V0F+C5cG2R1CtGTYeRmIAwLwcWf6Dj Y1Q+TOe/W3eTatOo+gIozjYDCk5ZNfeQzq4p1ApN6+gzS8kNxtvKOYJogjV74RK/ Xl7u7oLv4SZT7Nl1YRpScW1ouIcNNTP0AC+j2OFZ3YueN8CcmvXbgSW8pYRooTxn Ffo9sdOL624uwRyb2DwwLO0Vo3aBIEIf8sm9sqocXmwh9sxFPEbTXPCuMSao8Qjy BOlsCem2589NVZs0h0ipGwdbatcjkgf+hzRoYBdlvHtKHJ8gL/A/Ap8z0+TK5NaV WUA+zXOZRZ66NYfs18DEbJKjwOcnnsLcfAMYoSn697148sL4JBv8IOmM6QXfxCl/ 0yU0d5/876L5jOL56lfH0eBk8s2nioAl3yRBl2wlihWi39sA0bsdHFKYEX+LqPBB CAdxZAvXCCJcdEdxOXSgEiFAmW9+IXFT/WJeGcZ4OmCd3Qf0fxGqFXA/9hIUumWd e6s0wN8LjXuFZQaMDaaVIGXKguP3OijsfBF0PYzI+L6CfUi2BLaYNJTlbQxbncmW 2PKeDiypgt3ZY1PKV66o5OAJEAkV3vf9cRwXE5T8GwZHA+wx2rWC98hkH15xfI9q EsYulVdcXWzCF58HFQjUoDon0e/QMukS0eNgq9ipmoKAWKyy7+TQw7Xx3MmqkGlL HGM= -----END ENCRYPTED PRIVATE KEY-----; """ # Note that all parameters are passed as strings here: no files involved xmlsigned = Sello.sign_xml_data_to_buf( xmlbase, keydata, '12345678a', certdata) print("Signed XML:\n" + xmlsigned.decode()[0:60] + '\n ...[snip]... \n' + xmlsigned.decode()[-60:]) print("type(xmlsigned)=", type(xmlsigned)) write_file("frombuf3.xml", xmlsigned) def test_tfd(): print("\nOPERATIONS ON THE TIMBRE FISCAL DIGITAL (TFD)...") print("\nCREATE CADENA ORIGINAL DEL TIMBRE FISCAL DIGITAL (PIPESTRING FOR TFD):") fname = "cfdv40-ejemplo-signed-tfd.xml" s = Tfd.make_pipestring(fname) print(s) # Form the digest from the element nodes in the XML doc s = Tfd.make_digest(fname) print(s) # Extract the digest from the signature value using the PAC's cert certfile = "pac.cer" s1 = Tfd.extract_digest_from_sig(fname, certfile) print(s1) # Should be the same, but ignore case when comparing assert (s1.lower() == s.lower()) print("\nPRETEND WE ARE A PAC WITH A KEY ALLOWED TO SIGN THE TFD:") # Create a TFD signature string we could paste into the `SelloSAT` node fname = "cfdv40-ejemplo-signed-tfd.xml" certfile = "pac.cer" keyfile = "pac.key" password = "12345678a" s = Tfd.make_sig(fname, keyfile, password) print(s) # Compare with actual `SelloSAT` in doc s1 = Xmlu.get_attribute(fname, "SelloSAT", "TimbreFiscalDigital") print(s1) assert s == s1 print("\nVERIFY SIGNATURE IN TFD SELLOSAT:") n = Tfd.verify_sig(fname, certfile) print("Tfd.verifySignature() returns %d (expected 0)" % (n)) assert 0 == n print("\nADD A TFD ELEMENT TO A SIGNED CFDI DOCUMENT USING PAC KEY:") # Base file is signed but has no TFD element fname = "cfdv40-ejemplo-signed.xml" newname = "cfdv40-ejemplo_signed-new-tfd.xml" # We have the PAC's private key and cert to do the signing certfile = "pac.cer" keyfile = "pac.key" password = "12345678a" n = Tfd.add_signed_tfd(newname, fname, keyfile, password, certfile) print("Tfd.add_signed_tfd('%s'-->'%s') returns %d" % (fname, newname, n)) assert 0 == n # Did we make a valid XML file? n = Xmlu.validate_xml(newname) print("Xmlu.validate_xml() returned %d" % (n)) assert 0 == n # Does it have a valid selloSAT in the TFD? n = Tfd.verify_sig(newname, certfile) print("Tfd.verify_sig() returned %d" % (n)) assert 0 == n # Show the pipe string. NB different each time # -- timestamped using the system clock and a fresh UUID is generated s = Tfd.make_pipestring(newname) print(s) def test_asciify(): print("\nASCIIFY AN XML DOCUMENT...") xmlstr = "<a c='México'/>" print(f"OLD=[{xmlstr}]") s = Xmlu.asciify(xmlstr) print(f"NEW=[{s}]") # Now do a file fname = "cfdv40-ejemplo.xml" print(f"FILE: {fname}") s = Xmlu.asciify(fname) # Check it's still valid XML assert (Xmlu.validate_xml(s) == 0) # Check it's digest value is unchanged print("SHA-256(original) =" + Sello.make_digest(fname)) print("SHA-256(asciified)=" + Sello.make_digest(s)) assert (Sello.make_digest(fname) == Sello.make_digest(s)) def test_insertcert(): print("\nINSERT CERTIFICATE AND NUMBER INTO CFDI...") fname = "cfdv40-ejemplo-nocertnum.xml" certfile = "emisor.cer" print(f"FILE: {fname}") print("Expecting error...") n = Xmlu.validate_xml(fname) print(f"Xmlu.validate_xml returns {n}:\n {Err.format_error_message(n)}") certnum = Xmlu.get_attribute(fname, "NoCertificado", "Comprobante") print(f"NoCertificado={certnum}") # Now insert Certificado and NoCertificado newfile = "cfdv40-ejemplo-with-added-cert.xml" n = Sello.insert_cert(newfile, fname, certfile) print(f"Sello.insert_cert returns {n}") certnum = Xmlu.get_attribute(newfile, "NoCertificado", "Comprobante") print(f"NoCertificado(FILE)={certnum}") # Repeat but output to a new string, not a file xmlstr = Sello.insert_cert_to_string(fname, certfile) certnum = Xmlu.get_attribute(xmlstr, "NoCertificado", "Comprobante") print(f"NoCertificado(STR)={certnum}") # Check against serial number in actual certificate serialnum = Pkix.query_cert(certfile, 'serialNumber') print(f"serialNumber={serialnum}") assert serialnum == certnum def test_newkeyfile(): print("\nSAVE KEYFILE WITH A NEW PASSWORD...") keyfile = "emisor.key" certfile = "emisor.cer" oldpassword = "12345678a" print(f"Keyfile={keyfile}") n = Pkix.check_key_and_cert(keyfile, oldpassword, certfile) assert (0 == n) newkeyfile = "newkeyfile.key" newpassword = "password123" n = Pkix.new_key_file(newkeyfile, newpassword, keyfile, oldpassword) print(f"Pkix.new_key_file returns {n}") print(f"NewKeyfile={newkeyfile}") # Check this new key file still works n = Pkix.check_key_and_cert(newkeyfile, newpassword, certfile) assert (0 == n) # Again but saving in PEM format newkeyfile = "newkeyfile.pem" newpassword = "password123456" n = Pkix.new_key_file(newkeyfile, newpassword, keyfile, oldpassword, Pkix.KeyFormat.PEM) print(f"Pkix.new_key_file(PEM) returns {n}") print(f"NewKeyfile={newkeyfile}") # Check this new key file still works n = Pkix.check_key_and_cert(newkeyfile, newpassword, certfile) assert (0 == n) # Read in what should be a text file s = read_text_file(newkeyfile) print(s[:60] + "\n...\n" + s[-37:]) def main(): do_all = True # CHANGE TO SUIT for arg in sys.argv: global delete_tmp_dir if (arg == 'nodelete'): delete_tmp_dir = False elif (arg == 'some'): do_all = False setup_temp_dir() # DO THE TESTS - EITHER SOME OR ALL if (do_all): print("DOING ALL TESTS...\n") test_version() test_error_lookup() test_make_digest() test_make_pipestring_and_sig() test_query_cert() test_uuid() test_validate_xml() test_receipt_id() test_cert_string() test_key_string() test_write_pfx() test_check_key_cert() test_sign_xml() test_fix_bom() test_sign_xml_to_buf() test_get_attribute() test_verify_sig() test_extract_digest() test_tfd() test_asciify() test_insertcert() test_newkeyfile() else: # just do some tests: comment out as necessary print("ONLY DOING SOME TESTS...\n") test_version() # test_error_lookup() # test_make_digest() # test_make_pipestring_and_sig() # test_query_cert() # test_uuid() # test_validate_xml() # test_receipt_id() # test_cert_string() # test_key_string() # test_write_pfx() # test_check_key_cert() # test_sign_xml() # test_fix_bom() # test_sign_xml_to_buf() # test_get_attribute() # test_verify_sig() # test_extract_digest() # test_tfd() # test_asciify() # test_insertcert() test_newkeyfile() reset_start_dir() print("ALL DONE.") if __name__ == "__main__": main()