#! 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()