//=================================
using Microsoft.Data.SqlClient;
using Org.BouncyCastle.X509;
using System.Data;
using System.Globalization;
using System.Reflection.Metadata;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography;
using System.Xml.Xsl;
string XMLS = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xsl:stylesheet xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
xmlns:xs=""http://www.w3.org/2001/XMLSchema""
xmlns=""urn:oasis:names:specification:ubl:schema:xsd:Invoice-2""
xmlns:cac=""urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2""
xmlns:cbc=""urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2""
xmlns:ext=""urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2""
exclude-result-prefixes=""xs""
version=""2.0"">
<xsl:output omit-xml-declaration=""yes"" indent=""no"" encoding=""utf-8""/>
<xsl:template match=""node() | @*"">
<xsl:copy>
<xsl:apply-templates select=""node() | @*""/>
</xsl:copy>
</xsl:template>
<xsl:template match=""//*[local-name()='Invoice']//*[local-name()='UBLExtensions']""></xsl:template>
<xsl:template match=""//*[local-name()='AdditionalDocumentReference'][cbc:ID[normalize-space(text()) = 'QR']]""></xsl:template>
<xsl:template match=""//*[local-name()='Invoice']//*[local-name()='Signature']""></xsl:template>
</xsl:stylesheet>";
static string GetBase64InvoiceHash(string eInvoiceXml)
{
using MemoryStream stream = new(Encoding.UTF8.GetBytes(eInvoiceXml));
XmlDsigC14NTransform transform1 = new(false);
transform1.LoadInput(stream);
MemoryStream output = transform1.GetOutput() as MemoryStream;
byte[] hashBytes = HashSha256(Encoding.UTF8.GetString(output.ToArray()));
return Convert.ToBase64String(hashBytes);
}
static byte[] HashSha256(string rawData)
{
return SHA256.HashData(Encoding.UTF8.GetBytes(rawData));
}
static string ApplyXSLT(string xml, string xsltFileContent, bool indent)
{
StringBuilder output = new();
XmlWriterSettings settings = new()
{
OmitXmlDeclaration = true,
Encoding = Encoding.UTF8,
ConformanceLevel = ConformanceLevel.Auto,
Indent = indent
};
using (XmlWriter writer = XmlWriter.Create(output, settings))
{
XmlReader stylesheet = XmlReader.Create(new StringReader(xsltFileContent));
XmlReader input = XmlReader.Create(new StringReader(xml));
input.Read();
XslCompiledTransform transform1 = new();
transform1.Load(stylesheet);
transform1.Transform(input, writer);
}
return output.ToString();
}
static XmlDocument PrettyXml(XmlDocument inputXml)
{
static string GetBase64InvoiceHash(string eInvoiceXml)
{
using MemoryStream stream = new(Encoding.UTF8.GetBytes(eInvoiceXml));
XmlDsigC14NTransform transform1 = new(false);
transform1.LoadInput(stream);
MemoryStream output = transform1.GetOutput() as MemoryStream;
byte[] hashBytes = HashSha256(Encoding.UTF8.GetString(output.ToArray()));
return Convert.ToBase64String(hashBytes);
}
static byte[] HashSha256(string rawData)
{
return SHA256.HashData(Encoding.UTF8.GetBytes(rawData));
}
XmlDocument formattedXml = new XmlDocument() { PreserveWhitespace = true };
using (MemoryStream memoryStream = new MemoryStream())
{
using (StreamWriter streamWriter = new StreamWriter(memoryStream, new UTF8Encoding(false))) // false to exclude BOM
{
XmlWriterSettings settings = new XmlWriterSettings()
{
Indent = true,
IndentChars = " ",
OmitXmlDeclaration = false,
Encoding = Encoding.UTF8,
};
using (XmlWriter xmlWriter = XmlWriter.Create(streamWriter, settings))
{
inputXml.Save(xmlWriter);
}
}
// Get the UTF-8 encoded string from the MemoryStream
string utf8Xml = Encoding.UTF8.GetString(memoryStream.ToArray()).Trim();
// Load the UTF-8 XML string into the new XmlDocument
formattedXml.LoadXml(utf8Xml);
}
return formattedXml;
}
static string GetDateTime(string Date, string Time)
{
if (Time.EndsWith("Z"))
{
DateTime dateTime = DateTime.ParseExact(Date + "T" + Time, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal);
TimeZoneInfo destinationTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Arab Standard Time");
return TimeZoneInfo.ConvertTimeFromUtc(dateTime, destinationTimeZone).ToString("yyyy-MM-ddTHH:mm:ss");
}
else
{
return DateTime.ParseExact(Date + "T" + Time, "yyyy-MM-ddTHH:mm:ss", null).ToString("yyyy-MM-ddTHH:mm:ss");
}
}
static string GetNodeInnerText(XmlDocument doc, string xPath)
{
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(doc.NameTable);
xmlNamespaceManager.AddNamespace("cac", "urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2");
xmlNamespaceManager.AddNamespace("cbc", "urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2");
xmlNamespaceManager.AddNamespace("ext", "urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2");
XmlNode xmlNode = doc.SelectSingleNode(xPath, xmlNamespaceManager);
if (xmlNode == null)
{
return "";
}
return xmlNode.InnerText;
}
static SortedDictionary<int, byte[]> GetInvoiceDetails(XmlDocument Invoice)
{
string XpathSellerName = "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PartyLegalEntity']//*[local-name() = 'RegistrationName']";
string XpathVatRegisteration = "/*[local-name() = 'Invoice']/*[local-name() = 'AccountingSupplierParty']/*[local-name() = 'Party']/*[local-name() = 'PartyTaxScheme']/*[local-name() = 'CompanyID']";
string XpathInvoiceTotal = "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'TaxInclusiveAmount']";
string XpathPayableAmount = "/*[local-name() = 'Invoice']/*[local-name() = 'LegalMonetaryTotal']/*[local-name() = 'PayableAmount']";
string XpathVatTotal = "/*[local-name() = 'Invoice']/*[local-name() = 'TaxTotal']/*[local-name() = 'TaxAmount']";
string CERTIFICATE_XPATH = "/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'KeyInfo']/*[local-name() = 'X509Data']/*[local-name() = 'X509Certificate']";
string XpathSignature = "/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignatureValue']";
string XpathQR = "/*[local-name() = 'Invoice']/*[local-name() = 'AdditionalDocumentReference' and *[local-name()='ID' and .='QR']]/*[local-name() = 'Attachment']/*[local-name() = 'EmbeddedDocumentBinaryObject']";
string XpathHash = "/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignedInfo']/*[local-name() = 'Reference' and @Id='invoiceSignedData']/*[local-name() = 'DigestValue']";
string XpathUuid = "//*[local-name()='Invoice']//*[local-name()='UUID']";
string XpathPih = "/*[local-name() = 'Invoice']/*[local-name() = 'AdditionalDocumentReference' and *[local-name()='ID' and .='PIH']]/*[local-name() = 'Attachment']/*[local-name() = 'EmbeddedDocumentBinaryObject']";
string ISSUE_DATE_XPATH = "/*[local-name() = 'Invoice']/*[local-name() = 'IssueDate']";
string ISSUE_TIME_XPATH = "/*[local-name() = 'Invoice']/*[local-name() = 'IssueTime']";
string ZatcaDataInvoice = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<xsl:stylesheet xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
xmlns:xs=""http://www.w3.org/2001/XMLSchema""
xmlns=""urn:oasis:names:specification:ubl:schema:xsd:Invoice-2""
xmlns:cac=""urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2""
xmlns:cbc=""urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2""
xmlns:ext=""urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2""
exclude-result-prefixes=""xs""
version=""2.0"">
<xsl:output omit-xml-declaration=""yes"" indent=""no"" encoding=""utf-8""/>
<xsl:template match=""node() | @*"">
<xsl:copy>
<xsl:apply-templates select=""node() | @*""/>
</xsl:copy>
</xsl:template>
<xsl:template match=""//*[local-name()='Invoice']//*[local-name()='UBLExtensions']""></xsl:template>
<xsl:template match=""//*[local-name()='AdditionalDocumentReference'][cbc:ID[normalize-space(text()) = 'QR']]""></xsl:template>
<xsl:template match=""//*[local-name()='Invoice']//*[local-name()='Signature']""></xsl:template>
</xsl:stylesheet>";
return new SortedDictionary<int, byte[]>
{
{
1,
Encoding.UTF8.GetBytes(GetNodeInnerText(Invoice,XpathSellerName))
},
{
2,
Encoding.UTF8.GetBytes(GetNodeInnerText(Invoice,XpathVatRegisteration))
},
{
3,
Encoding.UTF8.GetBytes( GetDateTime(GetNodeInnerText(Invoice,ISSUE_DATE_XPATH),GetNodeInnerText(Invoice,ISSUE_TIME_XPATH)))
},
{
4,
Encoding.UTF8.GetBytes(GetNodeInnerText(Invoice,XpathPayableAmount))
},
{
5,
Encoding.UTF8.GetBytes(GetNodeInnerText(Invoice,XpathVatTotal))
},
{
6,
Encoding.UTF8.GetBytes(GetNodeInnerText(Invoice,XpathHash))
},
{
7,
Encoding.UTF8.GetBytes(GetNodeInnerText(Invoice,XpathSignature))
}
};
}
static void WriteLength(MemoryStream stream, int? length)
{
if (!length.HasValue)
{
stream.WriteByte(0x80);
}
else
{
int? nullable = length;
int num2 = 0;
if (!(nullable.GetValueOrDefault() < num2 & nullable.HasValue))
{
nullable = length;
long? nullable2 = nullable.HasValue ? new long?(nullable.GetValueOrDefault()) : null;
long num3 = 0xffff_ffff;
if (!(nullable2.GetValueOrDefault() > num3 & nullable2.HasValue))
{
nullable = length;
num2 = 0x7f;
if (nullable.GetValueOrDefault() <= num2 & nullable.HasValue)
{
stream.WriteByte((byte)length.Value);
}
else
{
byte num;
nullable = length;
num2 = 0xff;
if (nullable.GetValueOrDefault() <= num2 & nullable.HasValue)
{
num = 1;
}
else
{
nullable = length;
num2 = 0xffff;
if (nullable.GetValueOrDefault() <= num2 & nullable.HasValue)
{
num = 2;
}
else
{
nullable = length;
num2 = 0xff_ffff;
if (nullable.GetValueOrDefault() <= num2 & nullable.HasValue)
{
num = 3;
}
else
{
nullable = length;
nullable2 = nullable.HasValue ? new long?(nullable.GetValueOrDefault()) : null;
num3 = 0xffff_ffff;
if (!(nullable2.GetValueOrDefault() <= num3 & nullable2.HasValue))
{
throw new Exception($"[Error] Length value too big: {length}");
}
num = 4;
}
}
}
stream.WriteByte((byte)(num | 0x80));
for (int i = num - 1; i >= 0; i--)
{
nullable = length;
num2 = 8 * i;
int? nullable4 = nullable.HasValue ? new int?(nullable.GetValueOrDefault() >> (num2 & 0x1f)) : null;
byte num5 = (byte)nullable4.Value;
stream.WriteByte(num5);
}
}
return;
}
}
throw new Exception($"[Error] Invalid length value: {length}");
}
}
static void WriteTag(MemoryStream stream, uint tag)
{
bool flag = true;
for (int i = 3; i >= 0; i--)
{
byte num2 = (byte)(tag >> 8 * i);
if (!(num2 == 0 & flag) || i <= 0)
{
if (flag)
{
if (i != 0)
{
if ((num2 & 0x1f) != 0x1f)
{
throw new Exception("[Error] Invalid tag value: first octet indicates no subsequent octets, but subsequent octets found");
}
}
else if ((num2 & 0x1f) == 0x1f)
{
throw new Exception("[Error] Invalid tag value: first octet indicates subsequent octets, but no subsequent octets found");
}
}
else if (i == 0)
{
if ((num2 & 0x80) == 0x80)
{
throw new Exception("[Error] Invalid tag value: last octet indicates subsequent octets");
}
}
else if ((num2 & 0x80) != 0x80)
{
throw new Exception("[Error] Invalid tag value: non-last octet indicates no subsequent octets");
}
stream.WriteByte(num2);
flag = false;
}
}
}
static byte[] WriteTlv(uint tag, byte[] value)
{
if (value == null)
{
throw new Exception("[Error] Please provide a value!");
}
using MemoryStream stream = new();
WriteTag(stream, tag);
int num = value != null ? value.Length : 0;
WriteLength(stream, new int?(num));
stream.Write(value, 0, num);
return stream.ToArray();
}
static void TryGetPublicKeySByteArray(string certificate, out byte[] publicKey, out byte[] certificateSignature)
{
Org.BouncyCastle.X509.X509Certificate certificate2 = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(new X509Certificate2(Convert.FromBase64String(certificate)));
publicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(certificate2.GetPublicKey()).GetEncoded();
certificateSignature = certificate2.GetSignature();
}
static string GenerateQRCodeFromValues(SortedDictionary<int, byte[]> invoiceDetails)
{
List<byte> data = [];
foreach (var item in invoiceDetails)
{
data.AddRange(WriteTlv((uint)item.Key, item.Value));
var t = Convert.ToBase64String(item.Value);
}
return Convert.ToBase64String(data.ToArray());
}
static string GenerateQRCode(XmlDocument invoiceObject)
{
string CERTIFICATE_XPATH = "/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'KeyInfo']/*[local-name() = 'X509Data']/*[local-name() = 'X509Certificate']";
SortedDictionary<int, byte[]> invoiceDetails = GetInvoiceDetails(invoiceObject);
var stringCertificate = GetNodeInnerText(invoiceObject, CERTIFICATE_XPATH);
TryGetPublicKeySByteArray(stringCertificate, out byte[] publicKey, out byte[] certificateSignature);
invoiceDetails.Add(8, publicKey);
invoiceDetails.Add(9, certificateSignature);
return GenerateQRCodeFromValues(invoiceDetails);
}
static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
static string Base64Decode(string encodedString)
{
byte[] data = Convert.FromBase64String(encodedString);
string decodedString = System.Text.Encoding.UTF8.GetString(data);
return decodedString;
}
static void LogWriter(string logMessage)
{
LogWrite(logMessage);
}
static void LogWrite(string logMessage)
{
string m_exePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
try
{
using (StreamWriter w = File.AppendText(m_exePath + "\\" + "log.txt"))
{
Log(logMessage, w);
}
}
catch (Exception ex)
{
}
}
static void Log(string logMessage, TextWriter txtWriter)
{
try
{
txtWriter.Write("\r\nLog Entry : ");
txtWriter.WriteLine("{0} {1}", DateTime.Now.ToLongTimeString(),
DateTime.Now.ToLongDateString());
txtWriter.WriteLine(" :");
txtWriter.WriteLine(" :{0}", logMessage);
txtWriter.WriteLine("-------------------------------");
}
catch (Exception ex)
{
LogWriter(ex.Message);
}
}
while (true)
{
Thread.Sleep(2000);
DatabaseHandler.DataBaseCommands dataBaseCommands = new DatabaseHandler.DataBaseCommands();
DataTable dataTables = dataBaseCommands.GetDataTable("SelectQRGeneratorFromCMD");
if (dataTables.Rows.Count != 0)
{
foreach (DataRow item in dataTables.Rows)
{
try
{
XmlDocument xmlDocument = new XmlDocument();
string invoice = Base64Decode(Convert.ToString(item["InvoiceBase64"]));
invoice = invoice.Replace(" />", "/>");
xmlDocument.LoadXml(invoice);
xmlDocument = PrettyXml(xmlDocument);
// string CleanInvoice = ApplyXSLT(xmlDocument.InnerXml, XMLS, true);
// string Hash = GetBase64InvoiceHash(CleanInvoice);
Zatca.EInvoice.SDK.EInvoiceSigner eInvoiceSigner = new Zatca.EInvoice.SDK.EInvoiceSigner();
var t = eInvoiceSigner.SignDocument(xmlDocument, Base64Decode(Convert.ToString(item["BinaryToken"])), Base64Decode(Convert.ToString(item["PrivetKey"])));
if (!t.IsValid)
{
Console.WriteLine("Error in Sign Process" + System.Environment.NewLine + "***********************");
LogWriter("Error in Sign Process" + System.Environment.NewLine + "***********************");
}
xmlDocument.LoadXml(t.SignedEInvoice.InnerXml);
xmlDocument = PrettyXml(xmlDocument);
string qRResult = GenerateQRCode(xmlDocument);
string ReplaceString = GetNodeInnerText(xmlDocument, @"//cac:AdditionalDocumentReference[3]/cac:Attachment[1]/cbc:EmbeddedDocumentBinaryObject[1]");
string FixedXML = xmlDocument.InnerXml;
FixedXML = FixedXML.Replace(ReplaceString.Trim(), qRResult);
xmlDocument.LoadXml(FixedXML);
xmlDocument = PrettyXml(xmlDocument);
Zatca.EInvoice.SDK.EInvoiceHashGenerator eInvoiceHashGenerator = new Zatca.EInvoice.SDK.EInvoiceHashGenerator();
var x = eInvoiceHashGenerator.GenerateEInvoiceHashing(xmlDocument);
ReplaceString = GetNodeInnerText(xmlDocument, @"/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignedInfo']/*[local-name() = 'Reference' and @Id='invoiceSignedData']/*[local-name() = 'DigestValue']");
FixedXML = xmlDocument.InnerXml;
FixedXML = FixedXML.Replace(ReplaceString.Trim(), x.Hash);
xmlDocument.LoadXml(FixedXML);
xmlDocument = PrettyXml(xmlDocument);
dataBaseCommands = new DatabaseHandler.DataBaseCommands();
string Invoice = xmlDocument.InnerXml.Replace(" />", "/>");
xmlDocument.LoadXml(Invoice);
xmlDocument = PrettyXml(xmlDocument);
string ttt = Convert.ToBase64String(Encoding.UTF8.GetBytes(xmlDocument.InnerXml));
//File.WriteAllText(@"D:\Ayman123321.txt", Convert.ToBase64String(Encoding.UTF8.GetBytes(xmlDocument.InnerXml)));
//File.WriteAllText(@"D:\Ayman123321Hash.txt", "Hash= " + GetNodeInnerText(xmlDocument, @"/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignedInfo']/*[local-name() = 'Reference' and @Id='invoiceSignedData']/*[local-name() = 'DigestValue']"));
//File.WriteAllText(@"D:\AymanXML.txt", xmlDocument.InnerXml);
dataBaseCommands.ExcuteSPDateBase("UpdateQRGeneratorFromCMD", new SqlParameter[] { new SqlParameter("@QR", qRResult), new SqlParameter("@NewInvoice64", Convert.ToBase64String(Encoding.UTF8.GetBytes(xmlDocument.InnerXml))), new SqlParameter("@ID", Convert.ToString(item["ID"])) });
Console.WriteLine("Time= " + DateTime.Now + System.Environment.NewLine + "Hash= " + GetNodeInnerText(xmlDocument, @"/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignedInfo']/*[local-name() = 'Reference' and @Id='invoiceSignedData']/*[local-name() = 'DigestValue']") + System.Environment.NewLine + "QR= " + qRResult + System.Environment.NewLine + "ID= " + Convert.ToString(item["ID"]) + System.Environment.NewLine + "***********************");
LogWriter("Time= " + DateTime.Now + System.Environment.NewLine + "Hash= " + GetNodeInnerText(xmlDocument, @"/*[local-name() = 'Invoice']/*[local-name() = 'UBLExtensions']/*[local-name() = 'UBLExtension']/*[local-name() = 'ExtensionContent']/*[local-name() = 'UBLDocumentSignatures']/*[local-name() = 'SignatureInformation']/*[local-name() = 'Signature']/*[local-name() = 'SignedInfo']/*[local-name() = 'Reference' and @Id='invoiceSignedData']/*[local-name() = 'DigestValue']") + System.Environment.NewLine + "QR= " + qRResult + System.Environment.NewLine + "ID= " + Convert.ToString(item["ID"]) + System.Environment.NewLine + "***********************");
}
catch (Exception ex)
{
LogWriter(ex.Message);
}
}
}
}
Just Fix Xpath Links To match Your XML This Code i Use to Sign and Get QR