/*  $Id: MakeEnvio.cs $ 
 *   Last updated:
 *   $Date: 2021-02-05 00:51:00 $
 *   $Version: 1.2.0 $
 */

/******************************* LICENSE ***********************************
 * Copyright (C) 2020-21 David Ireland, DI Management Services Pty Limited.
 * All rights reserved. <https://di-mgt.com.au> <https://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>
****************************************************************************
*/

/* Changelog:
 * v1.2.0: 
 * + Fixed problem with <DD> element and &apos;/&quot; entities
 * + Changed checks using .IndexOf from >0 to >=0.
 * v1.1.0: (skipped version number to align with VerifyDoc.cs).
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Diagnostics;

/*
 * DI Management CryptoSys Libraries available from <https://cryptosys.net/>.
 * EITHER add references to
 * `diCrSysPKINet.dll` (installed by default in `C:\Program Files (x86)\CryptoSysPKI\DotNet`)
 * `diSc14nNet.dll` (installed by default in `C:\Program Files (x86)\Sc14n\DotNet`)
 * `diXmlsqNet.dll` (installed by default in `C:\Program Files (x86)\xmlsq\DotNet`)
 * OR add the C# source code files `CryptoSysPKI.cs`, `diSc14nNet.cs` and `diXmlsqNet.cs` 
 * directly to your project.
 */
using Pki = CryptoSysPKI;
using Sc14n;
using Xmlsq;


namespace DIManagement.MakeEnvio
{
    class Program
    {
        static void Main(string[] args)
        {
            string envioDoc;

            // Setup to display debugging info in console in Debug mode
            // (Should be ignored in final Release mode)
            TextWriterTraceListener[] listeners = new TextWriterTraceListener[] {
                new TextWriterTraceListener(Console.Out)
            };
            Debug.Listeners.AddRange(listeners);

            // Debug to check libraries are available and where
            Debug.WriteLine("PKI Version=" + Pki.General.Version() + " " + Pki.General.ModuleName() + " [" + Pki.General.CompileTime() + "]");
            Debug.WriteLine("SC14N Version=" + Sc14n.Gen.Version() + " " + Sc14n.Gen.ModuleName() + " [" + Sc14n.Gen.CompileTime() + "]");
            Debug.WriteLine("XMLSQ Version=" + Xmlsq.Gen.Version() + " " + Xmlsq.Gen.ModuleName() + " [" + Xmlsq.Gen.CompileTime() + "]");
            Debug.Assert(Pki.General.Version() >= 120200, "PKI version must be 12.4.0 or above");
            Debug.Assert(Sc14n.Gen.Version() >= 20100, "SC14N version must be 2.1.0 or above");

            // Go do the business 
            envioDoc = MakeEnvio.ProcessEnvioDoc("output-enviodte.xml", // Output XML file to create
                "user.cer", // User's signing certificate
                "user.pfx", // User's private key file
                "password", // Password for private key file
                "caf33.key", // Private key for CAF
                "template-enviodte.xml", // Template for outer document, completed as necessary
                // (params) variable-length list of base DTE documents to be signed...
                "dte-33-1.xml", "dte-33-2.xml");

            // Returns either the name of the file just created or an error message beginning with "**ERROR"
            if (envioDoc.IndexOf("**ERROR") >= 0) {
                Console.WriteLine(envioDoc);
            } else {
                Console.WriteLine("Created file '{0}'", envioDoc);
            }

            // Go do the business - part 2
            envioDoc = MakeEnvio.ProcessEnvioDoc("output-boleta.xml", // Output XML file to create
                "user.cer", // User's signing certificate
                "user.pfx", // User's private key file
                "password", // Password for private key file
                "caf39.key", // Private key for CAF
                "template-boleta.xml", // Template for outer document, completed as necessary
                // (params) variable-length list of base DTE documents to be signed...
                "dte-b1.xml", "dte-b2.xml");

            // Returns either the name of the file just created or an error message beginning with "**ERROR"
            if (envioDoc.IndexOf("**ERROR") >= 0) {
                Console.WriteLine(envioDoc);
            } else {
                Console.WriteLine("Created file '{0}'", envioDoc);
            }

        }
    }
    class MakeEnvio
    {

        // GLOBAL DEFAULT ALGORITHMS
        // These match the algorithms hard-coded into the Signature template
        // (change one, remember to change the other)
        const Pki.HashAlgorithm hashAlg = Pki.HashAlgorithm.Sha1;
        const Pki.SigAlgorithm sigAlg = Pki.SigAlgorithm.Rsa_Sha1;
        const Sc14n.DigAlg digAlg = Sc14n.DigAlg.Sha1;
        const Sc14n.TranMethod tranMethod = Sc14n.TranMethod.Inclusive;

        // Compulsory placeholder expected in documents to be signed.
        const string SIGPLACE = "<Signature>@!SIGNATURE!@</Signature>";


        /// <summary>
        /// Create signed Envio document.
        /// </summary>
        /// <param name="outputxmlFile">Name of output file to create.</param>
        /// <param name="certFile">Signer's X.509 certificate file (.cer).</param>
        /// <param name="keyFile">Signer's private key file (.pfx)</param>
        /// <param name="password">Password for private key file.</param>
        /// <param name="cafKeyFile">CAF private key file.</param>
        /// <param name="outerTemplate">XML template for outer Envio document.</param>
        /// <param name="dtedocs">List of DTE XML documents to be signed (comma-separated list of filenames)</param>
        /// <returns>Name of output file created if successful, or an error message that begins with "**ERROR:"</returns>
        public static string ProcessEnvioDoc(string outputxmlFile, string certFile, string keyFile, string password, string cafKeyFile, string outerTemplate, params string[] dtedocs)
        {
            string strMsg;
            StringBuilder sbCafKey;
            StringBuilder sbPriKey;
            string pubkey;
            string certStr = "";
            List<string> dtelist = new List<string>();
            string xmlsetdte;
            string sig;
            byte[] xmldata;
            string signingtime;
            // Compulsory placeholder expected in outer template.
            const string SETOFDTEPLACE = "<DTE>@!SET-OF-DTE!@</DTE>";

            // Read in outer template
            xmlsetdte = File.ReadAllText(outerTemplate);

            // Check for required placeholders
            if (!xmlsetdte.Contains(SIGPLACE)) {
                strMsg = String.Format("**ERROR: Placeholder '{0}' is missing", SIGPLACE);
                return strMsg;
            }
            if (!xmlsetdte.Contains(SETOFDTEPLACE)) {
                strMsg = String.Format("**ERROR: Placeholder '{0}' is missing", SETOFDTEPLACE);
                return strMsg;
            }
         
            // If provided, read in certificate file to a one-line base64 string
            if (!String.IsNullOrEmpty(certFile)) {
                certStr = Pki.X509.ReadStringFromFile(certFile);
                if (certStr.Length == 0) {
                    strMsg = String.Format("**ERROR: Failed to read X.509 certificate in '{0}'", certFile);
                    return strMsg;
                }
            }

            // Read in CAF private key (no password)
            sbCafKey = Pki.Rsa.ReadPrivateKey(cafKeyFile, "");
            if (sbCafKey.Length == 0) {
                strMsg = String.Format("**ERROR: Failed to read CAF private key in '{0}'", cafKeyFile);
                return strMsg;
            }
            Debug.WriteLine("CAF key has " + Pki.Rsa.KeyBits(sbCafKey.ToString()) + " bits");

            // Read in private key from PFX file
            sbPriKey = Pki.Rsa.ReadPrivateKey(keyFile, password);
            if (sbPriKey.Length == 0) {
                strMsg = String.Format("**ERROR: Failed to read private key in '{0}'", keyFile);
                return strMsg;
            }
            Debug.WriteLine("Private key has " + Pki.Rsa.KeyBits(sbPriKey.ToString()) + " bits");

            // Check private key matches certificate
            if (!String.IsNullOrEmpty(certFile)) {
                pubkey = Pki.Rsa.ReadPublicKey(certStr).ToString();
                if (Pki.Rsa.KeyMatch(sbPriKey.ToString(), pubkey) != 0) {
                    strMsg = String.Format("**ERROR: private key and certificate do not match.");
                    return strMsg;
                }
            }

            // Set signing time in <xs:datetime> form
            signingtime = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss");
            Debug.WriteLine(signingtime);

            // Iterate through list of DTE documents
            for (int i = 0; i < dtedocs.Length; i++) {
                string s = ProcessDte(dtedocs[i], certStr, sbPriKey, sbCafKey, signingtime);
                // Catch error - result contains "**ERROR"
                if (s.IndexOf("**ERROR") >= 0) {
                    return s;
                }
                // Store signed DTE document to be used later.
                dtelist.Add(s);
            }

            Debug.WriteLine("\nProcessing outer document...");
            xmlsetdte = xmlsetdte.Replace("@!TIMESTAMP!@", signingtime);
            xmlsetdte = xmlsetdte.Replace("@!NUM-DTE!@", dtelist.Count.ToString());

            // Insert DTE docs into Envio outer XML
            string toinsert = String.Join("\n", dtelist);
            xmlsetdte = xmlsetdte.Replace("<DTE>@!SET-OF-DTE!@</DTE>", toinsert);

            // Extract ID of SetDTE for Signature reference
            string docid = Xmlsq.Query.GetText(xmlsetdte, "//SetDTE/@ID");
            Debug.WriteLine("SetDTE ID=" + docid);

            // Convert XML string to bytes: NB explicit Latin-1 encoding.
            xmldata = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(xmlsetdte);

            // Compute the signature over the element SetDTE
            sig = MakeSignature(xmldata, docid, "SetDTE", sbPriKey, certStr);
            xmlsetdte = xmlsetdte.Replace("<Signature>@!SIGNATURE!@</Signature>", sig);

            // Final check for uncompleted '@!..!@'
            int nret = xmlsetdte.IndexOf("@!");
            if (nret >= 0) {
                Debug.WriteLine("WARNING: uncompleted '@!..!@' items");
            }

            // Write out the final output file
            File.WriteAllText(outputxmlFile, xmlsetdte, Encoding.GetEncoding("iso-8859-1"));

            // Clean up private keys
            Pki.Wipe.String(sbCafKey);
            Pki.Wipe.String(sbPriKey);

            return outputxmlFile;
        }

        /// <summary>
        /// Process an individual DTE document.
        /// </summary>
        /// <param name="xmlFile">Base DTE file to be processed with placeholders of form "@!...!@".</param>
        /// <param name="certStr">User's X.509 certificate as a base64 string.</param>
        /// <param name="sbPriKey">User's private signing key in Pki internal string form.</param>
        /// <param name="sbCafKey">User's CAF private key in Pki internal string form.</param>
        /// <param name="signingtime">Signing time as string in xs:dateTime form.</param>
        /// <returns>Signed DTE document as a string, or an error message that begins with "**ERROR:"</returns>
        static string ProcessDte(string xmlFile, string certStr, StringBuilder sbPriKey, StringBuilder sbCafKey, string signingtime)
        {
            string xmlStr, ddelem;
            byte[] b, xmlData;
            string strMsg;
            string dig;
            string frmtSig;
            string docid;
            int n;
            string sig;
            string filefullpath, filestem, filedir, newxmlfile;
            string fecha;
            // Compulsory placeholder expected in DTE base document.
            const string FRMTSIGPLACE = "@!FRMT-SIG!@";

            // NOTE: we use standard Unicode strings for string manipulation and regex,
            // and byte arrays for crypto and C14N operations.

            Debug.WriteLine("\nProcessing file: " + xmlFile);

            // Read in the base XML file as bytes
            xmlData = File.ReadAllBytes(xmlFile);
            if (xmlData.Length == 0) {
                strMsg = String.Format("**ERROR: failed to read XML file '{0}'", xmlFile);
                return strMsg;
            }

            // Convert XML byte input to a Unicode string for regex/replace editing
            // Try to guess encoding: expecting either Latin-1 or UTF-8 (or just plain US-ASCII, a subset of UTF-8)
            n = Pki.Cnv.CheckUTF8(xmlData);  // Returns 0 if invalid UTF-8 or >0 if valid UTF-8
            Debug.WriteLine("CheckUTF8 returns " + n);
            if (0 == n) {
                // Not valid UTF-8, so probably Latin-1
                xmlStr = System.Text.Encoding.GetEncoding("iso-8859-1").GetString(xmlData);
                ShowNonAscii(xmlStr);
            } else {
                xmlStr = System.Text.Encoding.UTF8.GetString(xmlData);
                ShowNonAscii(xmlStr);
            }

            //Console.WriteLine(HeadTail(s, 100));

            // Check for required placeholders
            if (!xmlStr.Contains(SIGPLACE)) {
                strMsg = String.Format("**ERROR: Placeholder '{0}' is missing in file '{1}'", SIGPLACE, xmlFile);
                return strMsg;
            }
            if (!xmlStr.Contains(FRMTSIGPLACE)) {
                strMsg = String.Format("**ERROR: Placeholder '{0}' is missing in file '{1}'", FRMTSIGPLACE, xmlFile);
                return strMsg;
            }

            // Set signing time in document placeholder
            Debug.WriteLine(signingtime);
            xmlStr = xmlStr.Replace("@!TIMESTAMP!@", signingtime);

            // And set the Fecha Emision Contable del DTE (AAAA-MM-DD)
            fecha = signingtime.Substring(0, 10);   // Truncate timestamp to <xs:date> form
            xmlStr = xmlStr.Replace("@!FECHA!@", fecha);

            // [2021-02-05] FIX
            // We can use Xmlsq to extract the flattened DD element - NO!
            // PROBLEM: Xpath converts XML entities &apos; and &qout; to literal characters, e.g. " and '
            //ddelem = Xmlsq.Query.FullQuery(xmlStr, "//DD", Query.Opts.Raw | Query.Opts.Trim);
            //if (ddelem.Length == 0) {
            //    strMsg = String.Format("**ERROR: Cannot find DD element in DTE document");
            //    return strMsg;
            //}
            // SOLUTION: Use regex instead to extract the <DD> element.
            Match match = Regex.Match(xmlStr, @"(<DD.*?</DD>)", RegexOptions.Singleline);
            if (!match.Success) {
                strMsg = String.Format("**ERROR: Cannot find DD element in DTE document");
                return strMsg;
            }
            ddelem = match.Value;
            // Now flatten it - this is the input for the FRMT signature
            ddelem = Regex.Replace(ddelem, @">\s+<", @"><");

            Console.WriteLine("\n--\n" + ddelem + "\n--\n");

            // Convert to bytes in Latin-1 encoding
            b = System.Text.Encoding.GetEncoding("iso-8859-1").GetBytes(ddelem);
            // Compute SHA-1 digest in base64
            dig = Pki.Cnv.ToBase64(Pki.Hash.BytesFromBytes(b, hashAlg));
            Debug.WriteLine("SHA1(<DD>)=" + dig);

            // Compute FRMT signature over the flattened, Latin-1-encoded <DD> element
            frmtSig = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(dig), sbCafKey.ToString(), "", sigAlg);
            Debug.WriteLine("SIG(<DD>)=" + frmtSig);

            // Insert this signature in placeholder @!FRMT-SIG!@
            xmlStr = xmlStr.Replace("@!FRMT-SIG!@", frmtSig);

            // Copy updated XML string to array of bytes, this time in UTF-8 encoding
            xmlData = System.Text.Encoding.UTF8.GetBytes(xmlStr);

            // Do a test C14N calc on this data to see if any XML problems
            dig = Sc14n.C14n.ToDigest(xmlData, "", 0, 0, 0);
            //Debug.WriteLine("Test digest on composed XML=" + dig);
            if (dig.Length == 0) {
                strMsg = String.Format("**ERROR: XML problem: '{0}'", Sc14n.Err.LastError());
                return strMsg;
            }

            // Extract ID of Documento for Signature reference
            docid = Xmlsq.Query.GetText(xmlStr, "//Documento/@ID");
            Debug.WriteLine("Documento ID=" + docid);

            // Compute the signature over the element <Documento>
            sig = MakeSignature(xmlData, docid, "Documento", sbPriKey, certStr);

            //Debug.WriteLine("---\n" + sig + "\n---");

            // Insert the completed Signature into the parent DTE document
            xmlStr = xmlStr.Replace("<Signature>@!SIGNATURE!@</Signature>", sig);

            // Final check for uncompleted '@!..!@'
            int nret = xmlStr.IndexOf("@!");
            if (nret >= 0) {
                Debug.WriteLine("WARNING: uncompleted '@!..!@' items");
            }

            // Debugging...
            ShowNonAscii(xmlStr);

            // Output XML should now be complete
            // Convert to UTF-8 bytes for XML check
            xmlData = System.Text.Encoding.UTF8.GetBytes(xmlStr);
            // Final check
            dig = Sc14n.C14n.ToDigest(xmlData, "", 0, 0, 0);
            if (dig.Length == 0) {
                strMsg = String.Format("**ERROR: XML internal problem: '{0}'", Sc14n.Err.LastError());
                return strMsg;
            }

            // Save this DTE doc as a file for checking.
            // We'll add some extra XML stuff so we can check this individual file on the verifier site
            // https://www.aleksey.com/xmlsec/xmldsig-verifier.html
            // To verify: open the -signed.xml document in a text editor (we recommend Notepad++ which automatically detects encoding) 
            // then copy-and-paste the entire document into the input field on the web site.
            // NOTE: you can verify these individual signed DTE documents, but it won't work for the outer Envio document (because namespaces).

            // Compose output filename
            filefullpath = Path.GetFullPath(xmlFile);
            filestem = Path.GetFileNameWithoutExtension(filefullpath);
            filedir = Path.GetDirectoryName(filefullpath);
            newxmlfile = Path.Combine(filedir, filestem + "-signed");
            newxmlfile = Path.ChangeExtension(newxmlfile, ".xml");
            //Debug.WriteLine("newxmlfile=" + newxmlfile);

            // Add header to XML document and save
            string header = 
@"<?xml version='1.0' encoding='ISO-8859-1'?>
<!DOCTYPE DTE [
<!ATTLIST Documento ID ID #IMPLIED>
]>";

            string temps = header + "\n" + xmlStr;
            File.WriteAllText(newxmlfile, temps, Encoding.GetEncoding("iso-8859-1"));

            Debug.WriteLine(String.Format("Saved temp DTE file '{0}'", newxmlfile));
            
            // Return the XML doc as a string (without the extra DOCTYPE header)
            return xmlStr;
        }

        /// <summary>
        /// Create the Signature element to be inserted in <c><Signature>@!SIGNATURE!@</Signature></c>placeholder.
        /// </summary>
        /// <param name="xmldata">XML data in byte array including element-to-be-signed</param>
        /// <param name="docid">ID of element to be signed</param>
        /// <param name="elemName">Name of element to be signed</param>
        /// <param name="sbPriKey">RSA private signing key as internal string</param>
        /// <param name="certStr">User's signing certificate as a base64 string</param>
        /// <returns>String containing the completed Signature element</returns>
        /// <remarks>Example element-to-be-signed: <c>Documento ID="Ejemplo_F101T39"</c>
        /// => <c>Reference URI="#Ejemplo_F101T39"</c>
        /// </remarks>
        static string MakeSignature(byte[] xmldata, string docid, string elemName, StringBuilder sbPriKey, string certStr)
        {
            string sig, sigval, digval;
            byte[] b;
            string xs;
            int nsigs;
            string siref;

            // Get Signature template
            sig = SignatureTemplate();

            // 1. Insert all required values in the SignedInfo element

            // 1.1 Insert docid in the Signature element @!DOCID!@
            sig = sig.Replace("@!DOCID!@", docid);

            // Compute digest value for C14N of element-to-be-signed
            digval = Sc14n.C14n.ToDigest(xmldata, elemName, Tran.SubsetByTag, digAlg, tranMethod);
            Debug.WriteLine("DigestValue=" + digval);
            Debug.Assert(digval.Length > 0, "Failed to compute C14N digest value");

            // 1.2 Insert DigestValue in the Signature element @!DIGVAL!@
            sig = sig.Replace("@!DIGVAL!@", digval);

            // Compute digest of SignedInfo
            // Need to include entire document with all parent namespaces to propagate down to SignedInfo
            // Make a temp string of entire document so we can insert the partially-made Signature element into it
            string doc = System.Text.Encoding.UTF8.GetString(xmldata);
            doc = doc.Replace("<Signature>@!SIGNATURE!@</Signature>", sig);
            // Count number of Signature elements in doc
            nsigs = Xmlsq.Query.Count(doc, "//Signature");
            siref = String.Format("SignedInfo[{0}]", nsigs);
            // Now back to a temp byte array to perform C14N on SignedInfo
            b = System.Text.Encoding.UTF8.GetBytes(doc);
            digval = Sc14n.C14n.ToDigest(b, siref, Sc14n.Tran.SubsetByTag, digAlg, tranMethod);
            Debug.WriteLine(String.Format("Digest({0})={1}", siref, digval));
            Debug.Assert(digval.Length > 0, "Failed to find digest of SignedInfo");
            //Debug.WriteLine(System.Text.Encoding.Default.GetString(Sc14n.C14n.ToBytes(b, "SignedInfo", Tran.SubsetByTag)));

            // Compute signature value and insert in Signature string
            sigval = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(digval), sbPriKey.ToString(), "", sigAlg);
            Debug.WriteLine(String.Format("Signature={0}", sigval));
            Debug.Assert(sigval.Length > 0, "Failed to compute signature");

            sig = sig.Replace("@!SIGVAL!@", "\n" + Wrap(sigval) + "\n");

            // Insert all remaining values into the Signature string
            // (NB these do not affect the SignatureValue)

            // Extract and substitute RSAKeyValue components
            xs = Pki.Rsa.KeyValue(sbPriKey.ToString(), "Modulus");
            sig = sig.Replace("@!RSA-MOD!@", "\n" + Wrap(xs) + "\n");
            xs = Pki.Rsa.KeyValue(sbPriKey.ToString(), "Exponent");
            sig = sig.Replace("@!RSA-EXP!@", xs);

            // Insert the cert string after line wrapping 
            xs = "\n" + Wrap(certStr) + "\n";
            sig = sig.Replace("@!CERTIFICATE!@", xs);

            // Return the Signature string
            return sig;
        }

        ///////////////////////////
        // UTILITIES
        ///////////////////////////

        /// <summary>
        /// Flatten an XML document string.
        /// </summary>
        /// <param name="s">String containing XML document</param>
        /// <returns>Flattened document as a string.</returns>
        static string FlattenXML(string s)
        {
            s = Regex.Replace(s, @">\s+<", @"><");
            return s;
        }

        // Show the first and last n characters of a string
        public static string HeadTail(string s, int n)
        {
            if (s.Length <= 2 * n) return s;
            return String.Format("{0}...{1}", s.Substring(0, n), s.Substring(s.Length - n, n));
        }

        // Use for debugging - show any non-ASCII characters found in the string s.
        static void ShowNonAscii(string s)
        {
            bool foundone = false;
            foreach (char c in s) {
                if ((int)c >= 128) {
                    if (!foundone) {
                        Debug.WriteLine("Non-ASCII chars found...");
                        foundone = true;
                    }
                    Debug.Write(String.Format("{0:X} ", (int)c));
                }
            }
            if (foundone) Debug.WriteLine("");
        }

        // Wrap a long single-line string at 64 columns
        public static string Wrap(string singleLineString)
        {
            // Based on answer by Nikita B in Stack Exchange
            // https://codereview.stackexchange.com/questions/141501/wrapping-single-line-string-to-multiple-lines-with-specific-length

            const int columns = 64;

            // Better way to find ceil(length/columns)...
            int rows = (singleLineString.Length + columns - 1) / columns;
            if (rows < 2) return singleLineString;

            return String.Join(
              Environment.NewLine,
              Enumerable.Range(0, rows)
                  .Select(i => i * columns)
                  .Select(i => singleLineString
                     .Substring(i, Math.Min(columns, singleLineString.Length - i)))
            );
        }

        /// <summary>
        /// Template for Signature element.
        /// </summary>
        /// <returns>Signature template string.</returns>
        /// <remarks>Hard-coded algorithms here must match global default algorithms.</remarks>
        static string SignatureTemplate()
        {
            string s = 
@"<Signature xmlns=""http://www.w3.org/2000/09/xmldsig#"">
<SignedInfo>
<CanonicalizationMethod Algorithm=""http://www.w3.org/TR/2001/REC-xml-c14n-20010315""/>
<SignatureMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#rsa-sha1""/>
<Reference URI=""#@!DOCID!@"">
<Transforms>
<Transform Algorithm=""http://www.w3.org/TR/2001/REC-xml-c14n-20010315""/>
</Transforms>
<DigestMethod Algorithm=""http://www.w3.org/2000/09/xmldsig#sha1""/>
<DigestValue>@!DIGVAL!@</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>@!SIGVAL!@</SignatureValue>
<KeyInfo>
<KeyValue>
<RSAKeyValue>
<Modulus>@!RSA-MOD!@</Modulus>
<Exponent>@!RSA-EXP!@</Exponent>
</RSAKeyValue>
</KeyValue>
<X509Data>
<X509Certificate>@!CERTIFICATE!@</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>";
            return s;
        }
    }
}