using System; using System.Diagnostics; using System.IO; using System.Text; using FirmaSAT; /* * $Id: TestFirmaSat.cs $ * Last updated: * $Date: 2020-08-05 17:01 $ * $Version: 9.2.0 $ */ /* Some tests using the FirmaSAT .NET interface. * * Requires `FirmaSAT` to be installed on your system: available from <https://www.cryptosys.net/firmasat/>. * Add a reference to `diFirmaSatNet.dll` installed in `C:\Program Files (x86)\FirmaSAT`. * * Test files are in `FirmaSATtestfiles.zip`. These must be in the CWD. * * This is a Console Application written for target .NET Framework 2.0 and above. * Please report any bugs to <https://cryptosys.net/contact>. */ /******************************* 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> **************************************************************************** */ /* [2020-07] Re-organised into modular form */ /* NOTE: Requires certain files to exist in the current working directory * (by default, the same directory as the executable). * See `arrFileNames` below - these files are in `FirmaSATtestfiles.zip` */ namespace TestFirmaSAT { /// <summary> /// Test examples for FirmaSAT .NET interface /// </summary> class TestFirmaSAT { private const int MIN_FSA_VERSION = 90200; // Test files required to exist in the current working directory: private static string[] arrFileNames = new string[] { "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", }; /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main(string[] args) { // Make sure minimum required version of FirmaSAT is installed... Console.WriteLine("FirmaSAT core Version is " + General.Version()); if (General.Version() < MIN_FSA_VERSION) { Console.WriteLine("FATAL ERROR: Require FirmaSAT core version " + MIN_FSA_VERSION + " or later."); return; } // CHECK FOR REQUIRED FILES IN CURRENT WORKING DIRECTORY Console.WriteLine("Current working directory is {0}", Directory.GetCurrentDirectory()); string missingFile = "STOPPED: Required file is missing in current working directory"; foreach (string fn in arrFileNames) { if (FileIsNotPresent(fn, missingFile)) return; } /* Handle command-line arguments * [some] just do some tests (default = do all) */ bool doSome = false; for (int iarg = 0; iarg < args.Length; iarg++) { if (args[iarg] == "some") doSome = true; } //************* // DO THE TESTS //************* if (doSome) // Use "some" in the command line { // Do some tests - comment these out as required Console.WriteLine("Just doing some tests..."); //test_General(); //test_ErrorLookup(); //test_pipestring(); //test_SignAndVerify(); //test_Digest(); //test_Validate(); //test_ExtractAttribute(); //test_X509Certificate(); //test_MakeSignature(); //test_Detallista(); //test_CertValidity(); //test_CheckKeyAndCert(); //test_ReceiptVersion(); //test_Tfd(); //test_BOM(); //test_ExtractConsecutive(); //test_PrivateKey(); //test_QueryCert(); //test_SignInMemory(); //test_UUID(); //test_Retenciones(); //test_Contabilidad(); //test_ConVol(); //test_InsertCert(); //test_GetXmlAttribute_Advanced(); test_XMLasUnicodeString(); } else { // Do all the test modules (default) Console.WriteLine("Doing all tests..."); DoAllTests(); } // ****************************************** // FINALLY, DISPLAY QUICK INFO ABOUT THE CORE DLL Console.WriteLine("\nDETAILS OF CORE DLL..."); Console.WriteLine("DLL Version={0} [{1}] Lic={2} Compiled=[{3}] ", General.Version(), General.Platform(), General.LicenceType(), General.CompileTime()); Console.WriteLine("[{0}]", General.ModuleName()); //******************************************** Console.WriteLine("\nALL TESTS COMPLETED."); } static void DoAllTests() { test_General(); test_ErrorLookup(); test_pipestring(); test_SignAndVerify(); test_Digest(); test_Validate(); test_ExtractAttribute(); test_X509Certificate(); test_MakeSignature(); test_Detallista(); test_CertValidity(); test_CheckKeyAndCert(); test_ReceiptVersion(); test_Tfd(); test_BOM(); test_ConsecutiveElements(); test_PrivateKey(); test_QueryCert(); test_SignInMemory(); test_UUID(); test_Retenciones(); test_Contabilidad(); test_ConVol(); test_InsertCert(); test_GetXmlAttribute_Advanced(); test_XMLasUnicodeString(); } //******************** // THE TEST MODULES... //******************** static void test_General() { int n; char ch; string s; //**************** // GENERAL TESTS * //**************** Console.WriteLine("\nINTERROGATE THE CORE DIFIRMASAT DLL:"); n = FirmaSAT.General.Version(); Console.WriteLine("Version={0}", n); s = General.CompileTime(); Console.WriteLine("CompileTime={0}", s); s = General.ModuleName(); Console.WriteLine("ModuleName={0}", s); s = General.Platform(); Console.WriteLine("Platform={0}", s); ch = General.LicenceType(); Console.WriteLine("LicenceType={0}", ch); s = General.Comments(); Console.WriteLine("Comments={0}", s); } static void test_ErrorLookup() { string s; //***************** // ERROR LOOKUP TESTS * //********************* Console.WriteLine("\nERROR CODES:"); for (int i = 0; i < 10000; i++) { s = General.ErrorLookup(i); if (!s.Equals(String.Empty)) Console.WriteLine("{0}={1}", i, s); } } static void test_pipestring() { string s; string fname; Console.WriteLine("\nFORM THE PIPESTRING FROM AN XML FILE:"); fname = "cfdv33a-base.xml"; s = Sat.MakePipeStringFromXml(fname); Console.WriteLine("Sat.MakePipeStringFromXml('{0}')=\n{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.MakePipeStringFromXml failed"); } static void test_SignAndVerify() { int n; string fname; string newname, keyfile, password, certfile; Console.WriteLine("\nSIGN 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); Console.WriteLine("Sat.SignXml('{0}'-->'{1}') returns {2}", fname, newname, n); Debug.Assert(n == 0, "Sat.SignXml failed"); // Did we make a valid XML file? n = Sat.ValidateXml(newname); Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", newname, n); Debug.Assert(n == 0, "Sat.ValidateXml failed"); Console.WriteLine("\nVERIFY A SIGNATURE IN AN XML FILE:"); Console.WriteLine("1. One we know is good:"); fname = "cfdv33a-signed-tfd.xml"; n = Sat.VerifySignature(fname); Console.WriteLine("Sat.VerifySignature('{0}') returns {1}", fname, n); Debug.Assert(n == 0, "Sat.VerifySignature failed"); Console.WriteLine("2. One we just made, so it should be good:"); fname = newname; n = Sat.VerifySignature(fname); Console.WriteLine("Sat.VerifySignature('{0}') returns {1}", fname, n); Debug.Assert(n == 0, "Sat.VerifySignature failed"); } static void test_Digest() { string s; string fname; Console.WriteLine("\nFORM THE DIGEST OF THE PIPESTRING IN AN XML FILE:"); fname = "cfdv33a-signed-tfd.xml"; s = Sat.MakeDigestFromXml(fname); Console.WriteLine("Sat.MakeDigestFromXml('{0}')=\n{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed"); Console.WriteLine("\nEXTRACT THE DIGEST FROM THE SIGNATURE IN AN XML FILE:"); fname = "cfdv33a-signed-tfd.xml"; s = Sat.ExtractDigestFromSignature(fname); Console.WriteLine("Sat.ExtractDigestFromSignature('{0}')=\n{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.ExtractDigestFromSignature failed"); } static void test_Validate() { int n; string s; string fname; Console.WriteLine("\nTRY VALIDATING XML FILES:"); Console.WriteLine("1. A valid one:"); fname = "cfdv33a-signed-tfd.xml"; n = Sat.ValidateXml(fname); Console.WriteLine("Sat.ValidateXml('{0}') returns {1} (expecting 0)", fname, n); Debug.Assert(n == 0, "Sat.ValidateXml failed"); Console.WriteLine("2. An invalid one (missing version):"); fname = "cfdv33a-bad-nover.xml"; n = Sat.ValidateXml(fname); Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", fname, n); s = Sat.LastError(); Console.WriteLine("ErrorLookup({0})={1}", n, General.ErrorLookup(n)); Debug.Assert(n != 0, "Sat.ValidateXml should have failed"); Console.WriteLine("\nVALIDATE XML WITH STRICT AND LOOSE OPTIONS:"); Console.WriteLine("Default strict behaviour (Bad attribute..@CURP..is too long):"); fname = "V3_2_BadCurp.xml"; n = Sat.ValidateXml(fname); Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", fname, n); s = Sat.LastError(); Console.WriteLine("ErrorLookup({0})={1}", n, General.ErrorLookup(n)); Console.WriteLine("LastError={0}", s); Debug.Assert(n != 0, "Sat.ValidateXml failed to fail"); Console.WriteLine("\nUsing XmlOption.Loose:"); n = Sat.ValidateXml(fname, XmlOption.Loose); Console.WriteLine("Sat.ValidateXml('{0}', Loose) returns {1}", fname, n); Debug.Assert(n == 0, "Sat.ValidateXml failed"); } static void test_ExtractAttribute() { string s; string fname; string attributeName, elementName; Console.WriteLine("\nEXTRACT AN ATTRIBUTE FROM AN XML FILE:"); fname = "cfdv33a-signed-tfd.xml"; elementName = "Comprobante"; attributeName = "Sello"; // NB capital 'S' for v3.3 s = Sat.GetXmlAttribute(fname, attributeName, elementName); Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})=\n{1}", fname, s, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); elementName = "Comprobante"; attributeName = "Fecha"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})=\n{1}", fname, s, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("\nEXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS:"); fname = "cfdv33a-base.xml"; elementName = "cfdi:Emisor"; attributeName = "Nombre"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})={1}", fname, s, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("\nEXTRACT AN ATTRIBUTE WITH ACCENTED CHARACTERS IN ITS NAME:"); fname = "cfdv33a-nomina12.xml"; elementName = "nomina12:CompensacionSaldosAFavor"; attributeName = "Año"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})={1}", fname, s, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); } static void test_X509Certificate() { string s; string s1; string fname; Console.WriteLine("\nGET DETAILS OF X.509 CERTIFICATE:"); Console.WriteLine("1. From embedded `certificado` in XML"); fname = "cfdv33a-signed-tfd.xml"; s = Sat.GetCertNumber(fname); Console.WriteLine("Sat.GetCertNumber('{0}')={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertNumber failed"); s = Sat.GetCertExpiry(fname); Console.WriteLine("Sat.GetCertExpiry('{0}')={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed"); Console.WriteLine("2. From X.509 file"); fname = "emisor.cer"; s = Sat.GetCertNumber(fname); Console.WriteLine("Sat.GetCertNumber('{0}')={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertNumber failed"); s = Sat.GetCertExpiry(fname); Console.WriteLine("Sat.GetCertExpiry('{0}')={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed"); Console.WriteLine("\nGET CERTIFICATE AS A BASE64 STRING:"); fname = "emisor.cer"; s = Sat.GetCertAsString(fname); Console.WriteLine("Sat.GetCertAsString('{0}')=\n{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertAsString failed"); Console.WriteLine("Sat.GetCertAsString('{0}').Length={1}", fname, s.Length); // Compare against string from XML file fname = "cfdv33a-signed-tfd.xml"; s1 = Sat.GetCertAsString(fname); Console.WriteLine("Sat.GetCertAsString('{0}').Length={1}", fname, s1.Length); Debug.Assert(s1.Length > 0, "Sat.GetCertAsString failed"); Debug.Assert(String.Compare(s, s1, true) == 0, "Sat.GetCertAsString failed"); } static void test_MakeSignature() { string s; string fname; string keyfile, password; Console.WriteLine("\nMAKE A SIGNATURE FROM A BASE XML FILE:"); fname = "cfdv33a-base.xml"; keyfile = "emisor.key"; password = "12345678a"; /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */ s = Sat.MakeSignatureFromXml(fname, keyfile, password); Console.WriteLine("Sat.MakeSignatureFromXml('{0}')=\n{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml failed"); } static void test_Detallista() { int n; string s; string fname; string keyfile, password; string newname, certfile; string attributeName, elementName; Console.WriteLine("\nSIGN 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); Console.WriteLine("Sat.SignXml('{0}'-->'{1}') returns {2}", fname, newname, n); Debug.Assert(n == 0, "Sat.SignXml failed"); // Did we make a valid XML file? n = Sat.ValidateXml(newname); Console.WriteLine("Sat.ValidateXml('{0}') returns {1}", newname, n); Debug.Assert(n == 0, "Sat.ValidateXml failed"); n = Sat.VerifySignature(newname); Console.WriteLine("Sat.VerifySignature('{0}') returns {1}", newname, n); Debug.Assert(n == 0, "Sat.VerifySignature failed"); Console.WriteLine("\nEXTRACT AN ATTRIBUTE FROM A DETALLISTA XML FILE:"); fname = "cfdv33a-detallista.xml"; elementName = "detallista:detallista"; attributeName = "documentStructureVersion"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Console.WriteLine("Sat.GetXmlAttribute('{0}',{2},{3})={1}", fname, s, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Debug.Assert(String.Compare(s, "AMC8.1") == 0, "Invalid detallista.documentStructureVersion"); } static void test_CertValidity() { string s; string fname; Console.WriteLine("\nGET VALIDITY DETAILS OF X.509 CERTIFICATE:"); Console.WriteLine("1. From embedded `certificado` in XML"); fname = "cfdv33a-signed-tfd.xml"; s = Sat.GetCertExpiry(fname); Console.WriteLine("Sat.GetCertExpiry('{0}')=\t{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed"); s = Sat.GetCertStart(fname); Console.WriteLine("Sat.GetCertStart('{0}') =\t{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertStart failed"); Console.WriteLine("2. From X.509 file"); fname = "emisor.cer"; s = Sat.GetCertExpiry(fname); Console.WriteLine("Sat.GetCertExpiry('{0}')=\t{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed"); s = Sat.GetCertStart(fname); Console.WriteLine("Sat.GetCertStart('{0}') =\t{1}", fname, s); Debug.Assert(s.Length > 0, "Sat.GetCertStart failed"); } static void test_CheckKeyAndCert() { int n; string keyfile, password, certfile; Console.WriteLine("\nCHECK 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); Console.WriteLine("Sat.CheckKeyAndCert({0},{1}) = {2}", keyfile, certfile, n); Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed"); certfile = "pac.cer"; keyfile = "pac.key"; password = "12345678a"; n = Sat.CheckKeyAndCert(keyfile, password, certfile); Console.WriteLine("Sat.CheckKeyAndCert({0},{1}) = {2}", keyfile, certfile, n); Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed"); // Get embedded certificate from XML doc certfile = "cfdv33a-signed-tfd.xml"; keyfile = "emisor.key"; password = "12345678a"; n = Sat.CheckKeyAndCert(keyfile, password, certfile); Console.WriteLine("Sat.CheckKeyAndCert({0},{1}) = {2}", keyfile, certfile, n); Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed"); } static void test_ReceiptVersion() { int n; string fname; Console.WriteLine("\nFIND THE COMPROBANTE VERSION OF AN XML FILE:"); fname = "cfdv33a-base.xml."; n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion('{0}') = {1}", fname, n); Debug.Assert(n == 33, "Sat.XmlReceiptVersion failed"); // Older version... Console.WriteLine("Legacy versions still work..."); fname = "ejemplo_v32-tfd2015.xml"; n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion('{0}') = {1}", fname, n); Debug.Assert(n == 32, "Sat.XmlReceiptVersion failed"); } static void test_Tfd() { int n; string s; string fname; string newname, keyfile, password, certfile; string s1; Console.WriteLine("\nCREATE CADENA ORIGINAL DEL TIMBRE FISCAL DIGITAL (PIPESTRING FOR TFD):"); fname = "cfdv33a-signed-tfd.xml"; certfile = "pac.cer"; s = Tfd.MakePipeStringFromXml(fname); Console.WriteLine("Tfd.MakePipeStringFromXml('{0}') =\n{1}", fname, s); Console.WriteLine("\nFORM DIGEST OF PIPESTRING FOR TFD:"); s = Tfd.MakeDigestFromXml(fname); Console.WriteLine("Tfd.MakeDigestFromXml('{0}')=\n{1}", fname, s); Console.WriteLine("\nEXTRACT DIGEST FROM TFD SELLOSAT:"); // NB certFile is required for Tfd s1 = Tfd.ExtractDigestFromSignature(fname, certfile); Console.WriteLine("Tfd.ExtractDigestFromSignature('{0}')=\n{1}", fname, s1); // Check the two digests match Debug.Assert(String.Compare(s, s1, true) == 0, "Digests do not match"); Console.WriteLine("\nPRETEND WE ARE A PAC WITH A KEY ALLOWED TO SIGN THE TFD:"); // 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"; certfile = "pac.cer"; keyfile = "pac.key"; password = "12345678a"; s = Tfd.MakeSignatureFromXml(fname, keyfile, password); Console.WriteLine("Tfd.MakeSignatureFromXml('{0}')=\n{1}", fname, s); s1 = Sat.GetXmlAttribute(fname, "SelloSAT", "TimbreFiscalDigital"); // NB Capital 'S' in Sello for TFD v1.1 Console.WriteLine("Correct=\n{0}", s1); Debug.Assert(String.Compare(s, s1, true) == 0, "selloSAT values do not match"); Console.WriteLine("\nADD 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 = Tfd.AddSignedTfd(newname, fname, keyfile, password, certfile); Console.WriteLine("Tfd.AddSignedTfd('{0}') returns {1} (expected 0)\n", newname, n); Debug.Assert(0 == n, "Tfd.AddSignedTfd failed"); // Did we make a valid XML file? n = Sat.ValidateXml(newname); Console.WriteLine("Sat.ValidateXml('{0}') returns {1} (expected 0)\n", newname, n); Debug.Assert(0 == n, "Sat.ValidateXml failed"); Console.WriteLine("\nVERIFY SIGNATURE IN TFD SELLOSAT:"); n = Tfd.VerifySignature(newname, certfile); Console.WriteLine("Tfd.VerifySignature({0},{1})={2} (expected 0)", newname, certfile, n); Debug.Assert(n == 0, "Tfd.VerifySignature failed"); } static void test_BOM() { int n; string fname; string newname; Console.WriteLine("\nADD UTF-8 BOM TO EXISTING FILE:"); fname = "cfdv33a-signed-nobom.xml"; Console.WriteLine("FileHasBom('{0}')={1} ", fname, FileHasBom(fname)); newname = "cfdv33a_new-signed-with-BOM.xml"; n = Sat.FixBom(newname, fname); Console.WriteLine("Sat.FixBom({0}->{1})={2} (expected 0)", fname, newname, n); Debug.Assert(n == 0, "Sat.FixBom failed"); Console.WriteLine("FileHasBom('{0}')={1} ", newname, FileHasBom(newname)); } static void test_ConsecutiveElements() { string s; string fname; string attributeName, elementName; Console.WriteLine("\nEXTRACT ATTRIBUTES FROM CONSECUTIVE ELEMENTS:"); fname = "ejemplo_v32-tfd2015.xml"; attributeName = "descripcion"; elementName = "cfdi:Concepto"; Console.WriteLine("For file '{0}'", fname); string eName = null; for (int i = 1; i <= 10; i++) { eName = elementName + string.Format("[{0:d}]", i); Console.Write("Sat.GetXmlAttribute({0},{1})=", attributeName, eName); s = Sat.GetXmlAttribute(fname, attributeName, eName); if (Sat.XmlNoMatch() == s) { // [2020-07-06] Changed from empty string break; } Console.WriteLine("{0}", s); } Console.WriteLine(); } static void test_PrivateKey() { int n; string s; string fname; string keyfile, password, certfile; string newname; string newpassword; Console.WriteLine("\nGET PRIVATE KEY AS BASE64:"); fname = "emisor.key"; password = "12345678a"; s = Sat.GetKeyAsString(fname, password); Console.WriteLine("Sat.GetKeyAsString({0})=\n{1}\n", fname, s); Console.WriteLine("Sat.GetKeyAsString('{0}').Length={1}", fname, s.Length); Debug.Assert(s.Length > 0, "Sat.GetKeyAsString failed"); Console.WriteLine("\nWRITE 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); Console.WriteLine("Sat.WritePfxFile()->{0} returns {1}", fname, n); Debug.Assert(n == 0, "Sat.WritePfxFile failed"); Console.WriteLine("New PFX file is {0} bytes long.", FileLength(fname)); Console.WriteLine("\nSAVE KEYFILE WITH NEW PASSWORD..."); keyfile = "emisor.key"; password = "12345678a"; newname = "emisor_new.key"; newpassword = "password123"; n = Sat.NewKeyFile(newname, newpassword, keyfile, password, 0); Console.WriteLine("Sat.NewKeyFile() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.NewKeyFile failed"); Console.WriteLine("Created new key file '{0}' of length {1} bytes with password '{2}'.", newname, FileLength(newname), newpassword); Console.WriteLine("Save again in PEM format..."); newname = "emisor_new.pem"; newpassword = "password456"; n = Sat.NewKeyFile(newname, newpassword, keyfile, password, KeyFormat.PEM); Console.WriteLine("Sat.NewKeyFile(KeyFormat.PEM) returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.NewKeyFile failed"); Console.WriteLine("Created new key file '{0}' of length {1} bytes with password '{2}'.", newname, FileLength(newname), newpassword); } static void test_QueryCert() { string s; string fname; Console.WriteLine("\nGET RFC AND ORG NAME FROM CERT:"); fname = "emisor.cer"; Console.WriteLine("FILE: {0}", fname); s = Sat.QueryCert(fname, Query.rfc); Console.WriteLine("Sat.QueryCert(rfc)={0}", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(rfc) failed"); s = Sat.QueryCert(fname, Query.organizationName); Console.WriteLine("Sat.QueryCert(organizationName)='{0}'", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(organizationName) failed"); fname = "cfdv33a-signed-tfd.xml"; Console.WriteLine("FILE: {0}", fname); s = Sat.QueryCert(fname, Query.rfc); Console.WriteLine("Sat.QueryCert(rfc)={0}", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(rfc) failed"); s = Sat.QueryCert(fname, Query.organizationName); Console.WriteLine("Sat.QueryCert(organizationName)='{0}'", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(organizationName) failed"); Console.WriteLine("\nTEST OTHER QUERIES FOR CERT:"); fname = "emisor.cer"; Console.WriteLine("FILE: {0}", fname); s = Sat.QueryCert(fname, Query.notBefore); Console.WriteLine("Sat.QueryCert(notBefore)={0}", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(notBefore) failed"); s = Sat.QueryCert(fname, Query.notAfter); Console.WriteLine("Sat.QueryCert(notAfter)={0}", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(notAfter) failed"); s = Sat.QueryCert(fname, Query.serialNumber); Console.WriteLine("Sat.QueryCert(serialNumber)={0}", s); Debug.Assert(s.Length > 0, "Sat.QueryCert(serialNumber) failed"); Console.WriteLine("\nQUERY KEY SIZE OF CERTIFICATES..."); fname = "emisor1024.cer"; s = Sat.QueryCert(fname, Query.keySize); Console.WriteLine("Sat.QueryCert('{0}',keySize)={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.QueryCert(keySize) failed"); fname = "emisor.cer"; s = Sat.QueryCert(fname, Query.keySize); Console.WriteLine("Sat.QueryCert('{0}',keySize)={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.QueryCert(keySize) failed"); fname = "AC4_SAT.cer"; s = Sat.QueryCert(fname, Query.keySize); Console.WriteLine("Sat.QueryCert('{0}',keySize)={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.QueryCert(keySize) failed"); Console.WriteLine("\nQUERY SIGNATURE ALGORITHM IN CERTIFICATES..."); fname = "emisor1024.cer"; s = Sat.QueryCert(fname, Query.sigAlg); Console.WriteLine("Sat.QueryCert('{0}',sigAlg)={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.QueryCert(sigAlg) failed"); fname = "emisor.cer"; s = Sat.QueryCert(fname, Query.sigAlg); Console.WriteLine("Sat.QueryCert('{0}',sigAlg)={1}", fname, s); Debug.Assert(s.Length > 0, "Sat.QueryCert(sigAlg) failed"); } static void test_SignInMemory() { int n; string fname; string keyfile, password, certfile; byte[] xmlArr; byte[] b; Console.WriteLine("\nSIGN XML FROM BYTES TO 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 certfile = "-----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-----"; keyfile = "-----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-----"; password = "12345678a"; // Check key matches certificate n = Sat.CheckKeyAndCert(keyfile, password, certfile); Console.WriteLine("Sat.CheckKeyAndCert(STRINGS) = {0}", n); Debug.Assert(n == 0, "Sat.CheckKeyAndCert failed"); // Read in a UTF-8-encoded XML file into a byte array fname = "cfdv33a-base.xml"; xmlArr = File.ReadAllBytes(fname); Console.WriteLine("File '{0}'-->{1} UTF-8 bytes", fname, xmlArr.Length); string s1 = ""; string s2 = ""; // Sign it and receive result back as a byte array... b = Sat.SignXmlToBytes(xmlArr, keyfile, password, certfile, 0); if (b.Length == 0) { Console.WriteLine("ERROR={0}", General.LastError()); } else { // Convert this to a string s1 = System.Text.Encoding.UTF8.GetString(b); Console.WriteLine("XML-OUT=\n---\n{0}\n---\n", s1); Console.WriteLine("From bytes: XML-OUT: {0} bytes --> {1} chars", b.Length, s1.Length); } // Sign the file instead and receive result back as a byte array... b = Sat.SignXmlToBytes(fname, keyfile, password, certfile, 0); if (b.Length == 0) Console.WriteLine("ERROR={0}", General.LastError()); else { // Convert this to a string s2 = System.Text.Encoding.UTF8.GetString(b); Console.WriteLine("From file: XML-OUT: {0} bytes --> {1} chars", b.Length, s2.Length); Debug.Assert(s1.Equals(s2)); } } static void test_UUID() { string s; Console.WriteLine("\nGENERATE 3 UUIDs:"); s = Sat.Uuid(); Console.WriteLine("UUID={0}", s); s = Sat.Uuid(); Console.WriteLine("UUID={0}", s); s = Sat.Uuid(); Console.WriteLine("UUID={0}", s); } static void test_Retenciones() { int n; string s; string fname; Console.WriteLine("\nWORK WITH A `RETENCIONES` DOCUMENT:"); fname = "Ejemplo_Retenciones-base.xml"; Console.WriteLine("FILE={0}", fname); n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 1010)", n); Debug.Assert(1010 == n, "Sat.XmlReceiptVersion failed"); s = Sat.MakeDigestFromXml(fname); Console.WriteLine("Sat.MakeDigestFromXml() -> {0}", s); Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed"); // Use new [v6.0] option to find name of root element s = Sat.GetXmlAttribute(fname, "", ""); Console.WriteLine("File root element is '{0}'", s); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute('','') failed"); } static void test_Contabilidad() { int n; string s; string fname; string newname, keyfile, password, certfile; Console.WriteLine("\nWORK WITH `CONTABILIDAD` DOCUMENTS:"); fname = "AAA010101AAA201501CT-base.xml"; Console.WriteLine("CATALOGOCUENTAS FILE={0}", fname); n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 2011)", n); Debug.Assert(2011 == n, "Sat.XmlReceiptVersion failed"); Console.WriteLine("SIGN A CATALOGOCUENTAS DOCUMENT..."); newname = "AAA010101AAA201501CT_new-signed.xml"; keyfile = "emisor.key"; certfile = "emisor.cer"; password = "12345678a"; n = Sat.SignXml(newname, fname, keyfile, password, certfile); Console.WriteLine("Sat.SignXml() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.SignXml failed"); n = Sat.VerifySignature(newname); Console.WriteLine("Sat.VerifySignature() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); fname = "AAA010101AAA201501BN-base.xml"; Console.WriteLine("BALANZA FILE={0}", fname); n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 2111)", n); Debug.Assert(2111 == n, "Sat.XmlReceiptVersion failed"); Console.WriteLine("MAKE THE SIGNATURE STRING FOR BALANZA..."); s = Sat.MakeSignatureFromXml(fname, keyfile, password); Console.WriteLine("Sat.MakeSignatureFromXml() ->\n{0}", s); Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml() failed"); fname = "contab-SelloDigitalContElec-signed.xml"; Console.WriteLine("SELLODIGITALCONTELEC FILE={0}", fname); n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 2511)", n); Debug.Assert(2511 == n, "Sat.XmlReceiptVersion failed"); Console.WriteLine("VERIFY SIGNATURE FOR SELLODIGITALCONTELEC USING PAC CERTIFICATE..."); n = Sat.VerifySignature(fname, "pac1024.cer"); Console.WriteLine("Sat.VerifySignature() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); // NEW IN [v8.1] Console.WriteLine("\nSUPPORT FOR CONTABILIDAD V1.3..."); fname = "AAA010101AAA201705CT.xml"; Console.WriteLine("FILE: {0}", fname); s = Sat.GetXmlAttribute(fname, "", ""); Console.WriteLine("Doc type is '{1}'", fname, s); n = Sat.ValidateXml(fname); Console.WriteLine("Sat.ValidateXml() returns {0} (0 => OK)", n); Debug.Assert(0 == n, "Sat.ValidateXml failed"); n = Sat.VerifySignature(fname); Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion() returns {0} (expecting 2013)", n); s = Sat.DefaultDigestAlg(fname); Console.WriteLine("Default digest algorithm is '{0}'", s); } static void test_ConVol() { int n; string fname; string newname, keyfile, password, certfile; Console.WriteLine("\nWORK WITH `CONTROLESVOLUMETRICOS` DOCUMENT:"); fname = "ConVolE12345-base.xml"; Console.WriteLine("FILE={0}", fname); n = Sat.XmlReceiptVersion(fname); Console.WriteLine("Sat.XmlReceiptVersion() returns ID={0} (expecting 4011)", n); Debug.Assert(4011 == n, "Sat.XmlReceiptVersion failed"); Console.WriteLine("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"; n = Sat.SignXmlEx(newname, fname, keyfile, password, certfile, SignOptions.BigFile); Console.WriteLine("Sat.SignXmlEx(BigFile) returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.SignXmlEx(BigFile) failed"); n = Sat.VerifySignature(newname); Console.WriteLine("Sat.VerifySignature() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); } static void test_InsertCert() { int n; string s; string fname; string newname, password, certfile; string cerStr; string xmlStr; string keyStr; string newStr; byte[] b; Console.WriteLine("\nINSERT CERTIFICATE INTO XML..."); // Take an XML file without a Nocertificado... fname = "cfdv33a-base-nocertnum.xml"; Console.WriteLine("Start file '{0}'.NoCertificado=[{1}]", fname, Sat.GetXmlAttribute(fname, "NoCertificado", "cfdi:Comprobante")); Console.WriteLine("Start file '{0}'.Certificado={1} bytes", fname, Sat.GetXmlAttribute(fname, "Certificado", "cfdi:Comprobante").Length); // Insert certificate details... certfile = "emisor.cer"; newname = "cfdv33a_new-base-pluscert.xml"; n = Sat.InsertCert(newname, fname, certfile); Console.WriteLine("Sat.InsertCert() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.InsertCert failed"); fname = newname; Console.WriteLine("Inter file '{0}'.NoCertificado=[{1}]", fname, Sat.GetXmlAttribute(fname, "NoCertificado", "cfdi:Comprobante")); Console.WriteLine("Inter file '{0}'.Certificado={1} bytes", fname, Sat.GetXmlAttribute(fname, "Certificado", "cfdi:Comprobante").Length); // Sign it - no need to specify the certificate newname = "cfdv33a_new-signed-pluscert.xml"; n = Sat.SignXml(newname, fname, "emisor.key", "12345678a", null); Console.WriteLine("Sat.SignXml() returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Sat.SignXml failed"); // Verify that it worked n = Sat.VerifySignature(newname); Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); Console.WriteLine("\nINSERT CERTIFICATE INTO XML USING STRINGS ONLY..."); // Read in data to strings password = "12345678a"; xmlStr = File.ReadAllText("cfdv33a-base-nocertnum.xml"); cerStr = Sat.GetCertAsString("emisor.cer"); keyStr = Sat.GetKeyAsString("emisor.key", password, KeyOption.EncryptedPEM); Console.WriteLine("String lengths: XML={0}, CER={1}, KEY={2}", xmlStr.Length, cerStr.Length, keyStr.Length); // Show we can use just strings n = Sat.CheckKeyAndCert(keyStr, password, cerStr); Console.WriteLine("Sat.CheckKeyAndCert() returns {0} (0 => OK)", n); s = Sat.GetXmlAttribute(xmlStr, "Moneda", "cfdi:Comprobante"); Console.WriteLine("Moneda in XML=[{0}]", s); // Insert certificate details. Note (string) -> (bytes) -> (string) b = Sat.InsertCertToBytes(xmlStr, cerStr); Console.WriteLine("Sat.InsertCertToBytes() returns byte array of length {0}", b.Length); if (b.Length == 0) DisplayError(); Debug.Assert(b.Length > 0); newStr = System.Text.Encoding.UTF8.GetString(b); Console.WriteLine("XML string has length {0}", newStr.Length); // Check we have a NoCertificado attribute... s = Sat.GetXmlAttribute(newStr, "NoCertificado", "cfdi:Comprobante"); Console.WriteLine("NoCertificado=[{0}]", s); if (s.Length == 0) DisplayError(); xmlStr = newStr; // Sign it - all strings, no cert required newStr = System.Text.Encoding.UTF8.GetString(Sat.SignXmlToBytes(xmlStr, keyStr, password, null, 0)); // Verify that it worked n = Sat.VerifySignature(newStr); Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n); if (n != 0) DisplayError(n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); } static void test_GetXmlAttribute_Advanced() { string s; string fname; string attributeName, elementName; string xbase, xpath, val; int i, j; Console.WriteLine("\nXPATH EXPRESSIONS FOR XMLGETATTRIBUTE..."); fname = "A7.xml"; Console.WriteLine("FILE: {0}", fname); elementName = "/Comprobante"; attributeName = "Version"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s); elementName = "/Comprobante/Conceptos/Concepto[2]/Impuestos/Traslados/Traslado[1]"; attributeName = "Importe"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s); elementName = "/Comprobante/Conceptos/Concepto[1]/Impuestos/Retenciones/Retencion[2]"; attributeName = "Importe"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s); // Same as above but shorter elementName = "//Conceptos/Concepto[1]//Retencion[2]"; attributeName = "Importe"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s); // Test for existence Console.WriteLine("Check for existence..."); elementName = "/Comprobante/Conceptos/Concepto[3]"; attributeName = "Importe"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s); Console.WriteLine("Loop through all Concepto elements until no match..."); for (i = 1; true; i++) { elementName = "//Conceptos/Concepto" + "[" + i + "]"; s = Sat.GetXmlAttribute(fname, attributeName, elementName); Console.WriteLine("elemname='{0}' attributeName='{1}' attrValue='{2}'", elementName, attributeName, s); if (Sat.XmlNoMatch() == s) break; } Console.WriteLine("Invalid path expression..."); elementName = "/"; attributeName = "Version"; Console.WriteLine("elemname='{0}' attributeName='{1}'", elementName, attributeName); s = Sat.GetXmlAttribute(fname, attributeName, elementName); if (s.Length == 0) { Console.WriteLine("ERROR={0}", General.LastError()); } Debug.Assert(s.Length == 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("\nADVANCED XMLGETATTRIBUTE 'NOMATCH' FEATURES..."); s = Sat.XmlNoMatch(); Console.WriteLine("Sat.XmlNoMatch() returns '{0}'", s); Console.WriteLine("Set a new value to be returned..."); Sat.SetXmlNoMatch("##No coinciden##"); s = Sat.XmlNoMatch(); Console.WriteLine("Sat.XmlNoMatch() returns '{0}'", s); fname = "cfdv33a-signed-tfd.xml"; Console.WriteLine("FILE: {0}", fname); elementName = "/Comprobante/notthere"; s = Sat.GetXmlAttribute(fname, "", elementName); Console.WriteLine("Sat.GetXmlAttribute({0}) returns '{1}'", elementName, s); elementName = "/Comprobante/Impuestos"; s = Sat.GetXmlAttribute(fname, "", elementName); Console.WriteLine("Sat.GetXmlAttribute({0}) returns '{1}'", elementName, s); Console.WriteLine("\nUSE XPATH TO FIND ALL ATTRIBUTES NAMED 'IMPORTE'..."); fname = "A7.xml"; Console.WriteLine("FILE: {0}", 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"; for (i = 1; true; i++) { // FOREACH //Conceptos/Concepto[i] element output the value of Importe xpath = xbase + "[" + i + "]"; val = Sat.GetXmlAttribute(fname, attributeName, xpath); if (Sat.XmlNoMatch() == val) break; Console.WriteLine("{0}/@{1}='{2}'", xpath, attributeName, val); // FOREACH //Conceptos/Concepto[i]//Traslado[j] element output the value of Importe // Long xpath is /Comprobante/Conceptos/Concepto[i]/Impuestos/Traslados/Traslado[j] for (j = 1; true; j++) { string xpath1 = xpath + "//Traslado[" + j + "]"; val = Sat.GetXmlAttribute(fname, attributeName, xpath1); if (Sat.XmlNoMatch() == val) break; Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val); } // FOREACH //Conceptos/Concepto[i]//Retencion[j] element output the value of Importe for (j = 1; true; j++) { string xpath1 = xpath + "//Retencion[" + j + "]"; val = Sat.GetXmlAttribute(fname, attributeName, xpath1); if (Sat.XmlNoMatch() == val) break; Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val); } } // 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] for (j = 1; true; j++) { string xpath1 = xpath + "//Retencion[" + j + "]"; val = Sat.GetXmlAttribute(fname, attributeName, xpath1); if (Sat.XmlNoMatch() == val) break; Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val); } // FOREACH /Comprobante/Impuestos//Traslado[j] element output the value of Importe for (j = 1; true; j++) { string xpath1 = xpath + "//Traslado[" + j + "]"; val = Sat.GetXmlAttribute(fname, attributeName, xpath1); if (Sat.XmlNoMatch() == val) break; Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val); } } /// <summary> /// Tests passing xmlDoc parameter as a Unicode UTF-16 string. Improved in [v9.2]. /// </summary> static void test_XMLasUnicodeString() { int n; string s; string fname; string newname, password; string xmlStr; string elemName, attrName; string digest1, digest2; Console.WriteLine("\nREAD IN A FILE TO A UNICODE UTF-16 STRING THEN PASS THE STRING AS XML DOC..."); fname = "cfdv33a-nomina12B.xml"; Console.WriteLine("FILE: {0}", fname); // Read in file to a Unicode text string xmlStr = File.ReadAllText(fname, Encoding.UTF8); Console.WriteLine("xmlStr is {0} characters", xmlStr.Length); Console.WriteLine("\nGET ATTRIBUTE VALUE USING UTF-16 STRING..."); // Attribute name contains non-ASCII character 'ü', Antigüedad="P3Y2M23D" elemName = "nomina12:Receptor"; attrName = "Antigüedad"; s = Sat.GetXmlAttribute(xmlStr, attrName, elemName); Console.WriteLine("{0}/@{1}='{2}'", elemName, attrName, s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); // Attribute name contains non-ASCII character 'ñ', Año="2016" elemName = "nomina12:CompensacionSaldosAFavor"; attrName = "Año"; s = Sat.GetXmlAttribute(xmlStr, attrName, elemName); Console.WriteLine("{0}/@{1}='{2}'", elemName, attrName, s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); // Attribute value contains non-ASCII character 'í', Sindicalizado="Sí" elemName = "nomina12:Receptor"; attrName = "Sindicalizado"; s = Sat.GetXmlAttribute(xmlStr, attrName, elemName); Console.WriteLine("{0}/@{1}='{2}'", elemName, attrName, s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed"); Console.WriteLine("\nCHECK XML IS OK USING UTF-16 STRING..."); n = Sat.ValidateXml(xmlStr); Console.WriteLine("Sat.ValidateXml returns {0} (expecting 0)", n); if (n != 0) DisplayError(n); Debug.Assert(0 == n, "Sat.ValidateXml failed"); Console.WriteLine("\nMAKE PIPE STRING USING UTF-16 STRING..."); s = Sat.MakePipeStringFromXml(xmlStr); Console.WriteLine("PIPESTRING=\n{0}", s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.MakePipeStringFromXml failed"); Console.WriteLine("\nFORM MESSAGE DIGEST OF PIPE STRING USING UTF-16 STRING..."); s = Sat.MakeDigestFromXml(xmlStr); Console.WriteLine("DIGEST={0}", s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed"); Console.WriteLine("\nINSERT CERTIFICATE INTO XML USING UTF-16 STRING AS INPUT..."); newname = "cfdv33a-nomina12B-plus-cert.xml"; n = Sat.InsertCert(newname, xmlStr, "emisor.cer"); Console.WriteLine("Sat.InsertCert returns {0} (expecting 0)", n); if (n != 0) DisplayError(n); Debug.Assert(0 == n, "Sat.InsertCert failed"); Console.WriteLine("Created new file '{0}' length = {1}", newname, FileLength(newname)); // Query this new file for certificate details s = Sat.QueryCert(newname, Query.organizationName); Console.WriteLine("organizationName('{0}')='{1}'", newname, s); Console.WriteLine("\nSIGN AN XML FILE WORKING ENTIRELY IN MEMORY...:"); Console.WriteLine("Read in private key and matching certificate to memory..."); password = "12345678a"; /* CAUTION: DO NOT HARD-CODE REAL PASSWORDS! */ // Read in the private key string keyStr = Sat.GetKeyAsString("emisor.key", password, KeyOption.EncryptedPEM); Console.WriteLine("Key string is {0} characters", keyStr.Length); if (keyStr.Length == 0) DisplayError(); // And the matching cert string cerStr = Sat.GetCertAsString("emisor.cer"); Console.WriteLine("Cert string is {0} characters", cerStr.Length); if (cerStr.Length == 0) DisplayError(); Console.WriteLine("Check that the key and certificate are matched..."); n = Sat.CheckKeyAndCert(keyStr, password, cerStr); Console.WriteLine("Sat.CheckKeyAndCert() returns {0} (expecting 0)", n); if (n != 0) DisplayError(n); Debug.Assert(0 == n, "Sat.CheckKeyAndCert failed"); // xmlStr already contains the unsigned XML doc, but we need XML data in bytes. // As a test, we will force encoding of *input* string to be Latin-1 // but note the *output* will still be UTF-8. byte[] xmlArr = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(xmlStr); Console.WriteLine("Unsigned xmlArr is {0} bytes", xmlArr.Length); // Sign XML document with all parameters passed in memory byte[] xmlArrSigned = Sat.SignXmlToBytes(xmlArr, keyStr, password, cerStr, 0); Console.WriteLine("Sat.SignXmlToBytes(xmlArr) returns {0} bytes", xmlArrSigned.Length); // Convert bytes back to Unicode string // NB xmlStr now contains the *signed* document xmlStr = System.Text.Encoding.UTF8.GetString(xmlArrSigned); Console.WriteLine("xmlStr-signed is {0} characters", xmlStr.Length); Console.WriteLine("\nCHECK SIGNED XML IS OK USING UTF-16 STRING..."); n = Sat.ValidateXml(xmlStr); Console.WriteLine("Sat.ValidateXml(xmlStr-signed) returns {0} (expecting 0)", n); if (n != 0) DisplayError(n); Debug.Assert(0 == n, "Sat.ValidateXml failed"); Console.WriteLine("\nVALIDATE THE SIGNATURE USING UTF-16 STRING..."); n = Sat.VerifySignature(xmlStr); Console.WriteLine("Sat.VerifySignature(xmlStr-signed) returns {0} (expecting 0)", n); if (n != 0) DisplayError(n); Debug.Assert(0 == n, "Sat.VerifySignature failed"); Console.WriteLine("\nGET CERTIFICATE ORGNAME USING UTF-16 STRING..."); // This time we have a result... s = Sat.QueryCert(xmlStr, Query.organizationName); Console.WriteLine("organizationName={0}", s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.QueryCert failed"); Console.WriteLine("\nMAKE SIGNATURE USING UTF-16 STRING..."); s = Sat.MakeSignatureFromXml(xmlStr, keyStr, password); Console.WriteLine("Sello={0}", s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml failed"); Console.WriteLine("\nEXTRACT SIGNATURE DIGEST USING UTF-16 STRING..."); s = Sat.ExtractDigestFromSignature(xmlStr); Console.WriteLine("digest1={0}", s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.ExtractDigestFromSignature failed"); digest1 = s; Console.WriteLine("\nMAKE MESSAGE DIGEST OF PIPE STRING USING UTF-16 STRING..."); s = Sat.MakeDigestFromXml(xmlStr); Console.WriteLine("digest2={0}", s); if (s.Length == 0) DisplayError(); Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed"); digest2 = s; // The last two should match (ignore case) n = string.Compare(digest1, digest2, true); Console.WriteLine("String.Compare(digest1, digest2, ignoreCase) returns {0} (expecting 0)", n); Debug.Assert(0 == n, "Digest values do not match"); } //************************************* // LOCAL UTILITIES... //************************************* static bool FileIsNotPresent(string filePath, string message) { FileInfo fi = new FileInfo(filePath); if (!fi.Exists) { Console.WriteLine("\n{0}: {1}", message, filePath); return true; } return false; } static long FileLength(string filePath) { FileInfo fi = new FileInfo(filePath); if (!fi.Exists) { return -1; } return fi.Length; } static bool FileHasBom(string filePath) { const int kNBYTES = 3; byte[] buf = new byte[kNBYTES]; FileInfo finfo = new FileInfo(filePath); Debug.Assert(finfo.Exists, "File '" + filePath + "' does not exist."); if (finfo.Exists) { FileStream fsi = finfo.OpenRead(); BinaryReader br = new BinaryReader(fsi); buf = br.ReadBytes(kNBYTES); br.Close(); fsi.Close(); } /* BOM consists of three bytes (0xEF, 0xBB, 0xBF) */ return (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF); } /** Display error - overloaded */ static void DisplayError() { Console.WriteLine("ERROR: {0}", General.LastError()); } static void DisplayError(int errCode) { string s = General.LastError(); Console.WriteLine("ERROR {0}: {1}: {2}", errCode, General.ErrorLookup(errCode), s); } } }