This is a companion page to Signing XML documents using XMLDSIG (Part 3). It gives code samples showing how to use CryptoSys PKI Pro and SC14N to sign XML documents using the W3C recommendation XML-Signature Syntax and Processing [XML-DSIG].
diCrSysPKINet.dll
and diSc14nNet.dll
You should find these in
C:\Program Files (x86)\CryptoSysPKI\DotNet
and
C:\Program Files (x86)\Sc14n\DotNet
, respectively.
Then add a using directive to the CryptoSysPKI
and Sc14n
namespaces in your code.
using Pki = CryptoSysPKI; using Sc14n;
There are also examples using the command line version of SC14N and a short Python script using CryptoSys PKI.
You could use this approach together with a simple text editor to achieve the same results as the C# code.
/// <summary> /// Sign an enveloped-signature XML-DSIG document. /// </summary> /// <param name="outputXmlFile">Name of signed output file to be created</param> /// <param name="baseXmlFile">Path to base XML document</param> /// <param name="keyFile">Filename of private key to be used for signing (or PEM string)</param> /// <param name="password">Password for encrypted private key ("" if not encrypted)</param> /// <returns>String containing the name of the output document, or an error message beginning "**ERROR:"</returns> /// <remarks> /// The base XML document is expected to be a well-formed XML document with placeholders of the form <c>@!...!@</c>. /// This code assumes specific hard-coded values for algorithms in the Signature template. /// </remarks> static string SignEnvSigDoc(string outputXmlFile, string baseXmlFile, string keyFile, string password) { // DEFAULT ALGORITHMS // These match the algorithms hard-coded into the Signature template // (change the template, remember to change these) const Pki.SigAlgorithm sigAlg = Pki.SigAlgorithm.Rsa_Sha1; // http://www.w3.org/2000/09/xmldsig#rsa-sha1 const Sc14n.DigAlg sigDigAlg = Sc14n.DigAlg.Sha1; // To match digest alg in signature alg const Sc14n.DigAlg digAlg = Sc14n.DigAlg.Sha1; // http://www.w3.org/2000/09/xmldsig#sha1 const Sc14n.TranMethod tranMethod = Sc14n.TranMethod.Inclusive; // http://www.w3.org/TR/2001/REC-xml-c14n-20010315 string xmlStr; string strMsg; byte[] b; StringBuilder sbPriKey; string s, sidigval; // Read in base XML document xmlStr = File.ReadAllText(baseXmlFile); // Read in private key sbPriKey = Pki.Rsa.ReadPrivateKey(keyFile, password); if (sbPriKey.Length == 0) { strMsg = String.Format("**ERROR: Failed to read private key file '{0}'", keyFile); return strMsg; } // Get XML as a byte array // Assumes base file is UTF-8 encoded b = System.Text.Encoding.UTF8.GetBytes(xmlStr); // Compute the digest value over the canonicalized data excluding the Signature element s = Sc14n.C14n.ToDigest(b, "Signature", Sc14n.Tran.OmitByTag, digAlg, tranMethod); Debug.WriteLine("DigestValue=" + s); // Set the DigestValue in the original string xmlStr = xmlStr.Replace("@!DIGVAL!@", s); // Compute digest of (completed) SignedInfo sidigval = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "SignedInfo", Tran.SubsetByTag, sigDigAlg, tranMethod); Debug.WriteLine("SignedInfo Digest=" + sidigval); if (sidigval.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute C14N digest value: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Compute signature value and insert in Signature string s = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(sidigval), sbPriKey.ToString(), "", sigAlg); // Clean up private key Pki.Wipe.String(sbPriKey); Debug.WriteLine(String.Format("SignatureValue=" + s)); if (s.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute signature: '{0}'", Pki.General.LastError()); return strMsg; } xmlStr = xmlStr.Replace("@!SIGVAL!@", s); // Final check for uncompleted '@!..!@' int nret = xmlStr.IndexOf("@!"); if (nret >= 0) { Debug.WriteLine("WARNING: uncompleted '@!..!@' items"); } // Output XML should now be complete // Final check (Note convert string to UTF-8 bytes before passing) s = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "", 0, 0, 0); if (s.Length == 0) { strMsg = String.Format("**ERROR: XML internal problem: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Write final document string to output file File.WriteAllText(outputXmlFile, xmlStr); // Return full path name of output file created FileInfo fi = new FileInfo(outputXmlFile); return fi.FullName; }
string signedDoc = SignEnvSigDoc("env-sig1-signed.xml", "env-sig1-base.xml", "AlicePrivRSASign.p8e", "password");
Example base document: env-sig1-base.xml
Final signed document: env-sig1-signed.xml
More details on Enveloped Signature.
> sc14n -d -x Signature env-sig1-base.xml wNowPDgtPkF2fZlyGURmSTXmOso= > sc14n -d -s SignedInfo env-sig1-signed.xml y1r4smbzRRqqH/5unQZ80tgm+e4= > pki-sign-digest.py y1r4smbzRRqqH/5unQZ80tgm+e4= FLyMGIe/R9K9RFS3VdYqJ1kPXffXFrh+2uW+m2w7hDgxEekEJEX8G7jgm5iLjuTF4aNOPni1SfDkJe2CuvE4/H7kIq0pVFiO2h9+qZykD/nnh32qdOtDrJuF9US93+SfExjjU4WHi8KOAc/xdNtdXOaJjbXBpY47WaCnhnN4g1w=
/// <summary> /// Sign a XML-DSIG document with an ID reference. /// </summary> /// <param name="outputXmlFile">Name of signed output file to be created</param> /// <param name="baseXmlFile">Path to base XML document</param> /// <param name="keyFile">Filename of private key to be used for signing (or PEM string)</param> /// <param name="password">Password for encrypted private key ("" if not encrypted)</param> /// <returns>String containing the name of the output document, or an error message beginning "**ERROR:"</returns> /// <remarks> /// The base XML document is expected to be a well-formed XML document with placeholders of the form <c>@!...!@</c>. /// This code assumes specific hard-coded values for algorithms in the Signature template. /// </remarks> static string SignSigIdDoc(string outputXmlFile, string baseXmlFile, string keyFile, string password) { // DEFAULT ALGORITHMS // These match the algorithms hard-coded into the Signature template // (change the template, remember to change these) const Pki.SigAlgorithm sigAlg = Pki.SigAlgorithm.Rsa_Sha256; // http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 const Sc14n.DigAlg sigDigAlg = Sc14n.DigAlg.Sha256; // To match digest alg in signature alg const Sc14n.DigAlg digAlg = Sc14n.DigAlg.Sha256; // http://www.w3.org/2001/04/xmlenc#sha256 const Sc14n.TranMethod tranMethod = Sc14n.TranMethod.Inclusive; // http://www.w3.org/TR/2001/REC-xml-c14n-20010315 string xmlStr; string strMsg; byte[] b; StringBuilder sbPriKey; string s, sidigval; // Read in base XML document xmlStr = File.ReadAllText(baseXmlFile); // Read in private key sbPriKey = Pki.Rsa.ReadPrivateKey(keyFile, password); if (sbPriKey.Length == 0) { strMsg = String.Format("**ERROR: Failed to read private key file '{0}'", keyFile); return strMsg; } // Get XML as a byte array // Assumes base file is UTF-8 encoded b = System.Text.Encoding.UTF8.GetBytes(xmlStr); // Compute the digest value over the the element with ID attribute value ID=MyInvoice s = Sc14n.C14n.ToDigest(b, "ID=MyInvoice", Sc14n.Tran.SubsetById, digAlg, tranMethod); Debug.WriteLine("DigestValue=" + s); // Set the DigestValue in the original string xmlStr = xmlStr.Replace("@!DIGVAL!@", s); // Compute digest of (completed) SignedInfo sidigval = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "ds:SignedInfo", Tran.SubsetByTag, sigDigAlg, tranMethod); Debug.WriteLine("ds:SignedInfo Digest=" + sidigval); if (sidigval.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute C14N digest value: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Compute signature value and insert in Signature string s = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(sidigval), sbPriKey.ToString(), "", sigAlg); // Clean up private key Pki.Wipe.String(sbPriKey); Debug.WriteLine(String.Format("SignatureValue=" + s)); if (s.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute signature: '{0}'", Pki.General.LastError()); return strMsg; } xmlStr = xmlStr.Replace("@!SIGVAL!@", s); // Final check for uncompleted '@!..!@' int nret = xmlStr.IndexOf("@!"); if (nret >= 0) { Debug.WriteLine("WARNING: uncompleted '@!..!@' items"); } // Output XML should now be complete // Final check (Note convert string to UTF-8 bytes before passing) s = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "", 0, 0, 0); if (s.Length == 0) { strMsg = String.Format("**ERROR: XML internal problem: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Write final document string to output file File.WriteAllText(outputXmlFile, xmlStr); // Return full path name of output file created FileInfo fi = new FileInfo(outputXmlFile); return fi.FullName; }
string signedDoc = SignSigIdDoc("sig-id2-signed.xml", "sig-id2-base.xml", "AlicePrivRSASign.p8e", "password");
Example base document: sig-id2-base.xml
Final signed document: sig-id2-signed.xml
More details on Signed document with ID.
> sc14n -d2 -S "ID=MyInvoice" sig-id2-base.xml 6+2TsbCd1x+P5vxjuBaC0ocxInh3wYMu6eBZWIyMcjY= > sc14n -d2 -s "ds:SignedInfo" sig-id2-signed.xml CwgsuV8sM6/xnjStFifw7QlWX00+dZuEfUDuuprD5gY= > pki-sign-digest.py CwgsuV8sM6/xnjStFifw7QlWX00+dZuEfUDuuprD5gY= rYK4hmQ0lETI4urJQVkfhUcxDBq5a2KI3kcL7v6I7dDkvQq7S3Hc6z/Zcd2vgyuX4kaOPyR6ZJ4KcqsguxkHXJEhVk/OlTKakCyS+LA8yi5O6/mB+nR8kPxTISOtITjJ8+hpTbiCrj6NNDKF683lxe8nioNLUzEBaK/P5dfCdZA=
/// <summary> /// Sign a SOAP document using WSS /// </summary> /// <param name="outputXmlFile">Name of signed output file to be created</param> /// <param name="baseXmlFile">Path to base XML document</param> /// <param name="keyFile">Filename of private key to be used for signing (or PEM string)</param> /// <param name="password">Password for encrypted private key ("" if not encrypted)</param> /// <returns>String containing the name of the output document, or an error message beginning "**ERROR:"</returns> /// <remarks> /// The base XML document is expected to be a well-formed XML document with placeholders of the form <c>@!...!@</c>. /// This code assumes specific hard-coded values for algorithms in the Signature template. /// </remarks> static string SignSoapDoc(string outputXmlFile, string baseXmlFile, string keyFile, string password) { // DEFAULT ALGORITHMS // These match the algorithms hard-coded into the Signature template // (change the template, remember to change these) const Pki.SigAlgorithm sigAlg = Pki.SigAlgorithm.Rsa_Sha256; // http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 const Sc14n.DigAlg sigDigAlg = Sc14n.DigAlg.Sha256; // To match digest alg in signature alg const Sc14n.DigAlg digAlg = Sc14n.DigAlg.Sha256; // http://www.w3.org/2001/04/xmlenc#sha256 const Sc14n.TranMethod tranMethod = Sc14n.TranMethod.Exclusive; // http://www.w3.org/2001/10/xml-exc-c14n# string xmlStr; string strMsg; byte[] b; StringBuilder sbPriKey; string s, sidigval; // Read in base XML document xmlStr = File.ReadAllText(baseXmlFile); // Read in private key sbPriKey = Pki.Rsa.ReadPrivateKey(keyFile, password); if (sbPriKey.Length == 0) { strMsg = String.Format("**ERROR: Failed to read private key file '{0}'", keyFile); return strMsg; } // Get XML as a byte array // Assumes base file is UTF-8 encoded b = System.Text.Encoding.UTF8.GetBytes(xmlStr); // Compute the digest value over the the element with ID attribute value wsu:Id=TheBody // with PrefixList="wsu soap" s = Sc14n.C14n.ToDigest(b, "wsu:Id=TheBody", Sc14n.Tran.SubsetById, digAlg, tranMethod, "wsu soap"); Debug.WriteLine("DigestValue=" + s); // Set the DigestValue in the original string xmlStr = xmlStr.Replace("@!DIGVAL!@", s); // Compute digest of (completed) SignedInfo // with PrefixList="ds wsu soap" sidigval = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "ds:SignedInfo", Tran.SubsetByTag, sigDigAlg, tranMethod, "ds wsu soap"); Debug.WriteLine("ds:SignedInfo Digest=" + sidigval); if (sidigval.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute C14N digest value: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Compute signature value and insert in Signature string s = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(sidigval), sbPriKey.ToString(), "", sigAlg); // Clean up private key Pki.Wipe.String(sbPriKey); Debug.WriteLine(String.Format("SignatureValue=" + s)); if (s.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute signature: '{0}'", Pki.General.LastError()); return strMsg; } xmlStr = xmlStr.Replace("@!SIGVAL!@", s); // Final check for uncompleted '@!..!@' int nret = xmlStr.IndexOf("@!"); if (nret >= 0) { Debug.WriteLine("WARNING: uncompleted '@!..!@' items"); } // Output XML should now be complete // Final check (Note convert string to UTF-8 bytes before passing) s = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "", 0, 0, 0); if (s.Length == 0) { strMsg = String.Format("**ERROR: XML internal problem: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Write final document string to output file File.WriteAllText(outputXmlFile, xmlStr); // Return full path name of output file created FileInfo fi = new FileInfo(outputXmlFile); return fi.FullName; }
string signedDoc = SignSoapDoc("soap-example-signed.xml", "soap-example-base.xml", "AlicePrivRSASign.p8e", "password");
Example base document: soap-example-base.xml
Final signed document: soap-example-signed.xml
> sc14n -d2 -e -p "wsu soap" -S "wsu:Id=TheBody" soap-example-base.xml m08TWBsFxFQIN3Vuab3HbUcy932fI8DFnf7NedTjF/c= > sc14n -d2 -e -p "ds wsu soap" -s "ds:SignedInfo" soap-example-signed.xml tAaj6jvjtxnX1HL+tCDyMIkgZ6WEcLh4OzD9XTbBQ1U= > pki-sign-digest.py tAaj6jvjtxnX1HL+tCDyMIkgZ6WEcLh4OzD9XTbBQ1U= ygStRzhW8cG9tXPp/7a5mhtO81fnwU3G2dw3JGg3gVrXP0EbuUG4aATmhAcymWCpJTEH9hXUgCcaLp8QC4J+m9ssfn3htwEeIzVxsTWvOPMktdhg3rbBTui0aC0+OZvUiV4cqVsx39EAwmYAHW9+fTR4rOV2dgYjfnNZ4W3laJ8=
/// <summary> /// Sign a XAdES-BES document /// </summary> /// <param name="outputXmlFile">Name of signed output file to be created</param> /// <param name="baseXmlFile">Path to base XML document</param> /// <param name="keyFile">Filename of private key to be used for signing (or PEM string)</param> /// <param name="password">Password for encrypted private key ("" if not encrypted)</param> /// <returns>String containing the name of the output document, or an error message beginning "**ERROR:"</returns> /// <remarks> /// The base XML document is expected to be a well-formed XML document with placeholders of the form <c>@!...!@</c>. /// This code assumes specific hard-coded values for algorithms in the Signature template. /// </remarks> static string SignXadesBesDoc(string outputXmlFile, string baseXmlFile, string keyFile, string password) { // DEFAULT ALGORITHMS // These match the algorithms hard-coded into the Signature template // (change the template, remember to change these) const Pki.SigAlgorithm sigAlg = Pki.SigAlgorithm.Rsa_Sha256; // http://www.w3.org/2001/04/xmldsig-more#rsa-sha256 const Sc14n.DigAlg sigDigAlg = Sc14n.DigAlg.Sha256; // To match digest alg in signature alg const Sc14n.DigAlg digAlg = Sc14n.DigAlg.Sha256; // http://www.w3.org/2001/04/xmlenc#sha256 const Sc14n.TranMethod tranMethod = Sc14n.TranMethod.Inclusive; // http://www.w3.org/TR/2001/REC-xml-c14n-20010315 string xmlStr; string strMsg; byte[] b; StringBuilder sbPriKey; string s, sidigval; // Read in base XML document xmlStr = File.ReadAllText(baseXmlFile); // Read in private key sbPriKey = Pki.Rsa.ReadPrivateKey(keyFile, password); if (sbPriKey.Length == 0) { strMsg = String.Format("**ERROR: Failed to read private key file '{0}'", keyFile); return strMsg; } // Get XML as a byte array // Assumes base file is UTF-8 encoded b = System.Text.Encoding.UTF8.GetBytes(xmlStr); // We have *three* references over which to compute a digest value // 1. Compute the digest value over the element with ID attribute value ID=MyInvoice s = Sc14n.C14n.ToDigest(b, "ID=MyInvoice", Sc14n.Tran.SubsetById, digAlg, tranMethod); Debug.WriteLine("DigestValue=" + s); // Set the DigestValue in the original string xmlStr = xmlStr.Replace("@!DIG-REF0!@", s); // 2. Compute the digest value over the element with Id xmldsig-keyinfo s = Sc14n.C14n.ToDigest(b, "xmldsig-keyinfo", Sc14n.Tran.SubsetById, digAlg, tranMethod); Debug.WriteLine("DigestValue=" + s); // Set the DigestValue in the original string xmlStr = xmlStr.Replace("@!DIG-KEYINFO!@", s); // 3. Compute the digest value over the element with Id xmldsig-signedprops s = Sc14n.C14n.ToDigest(b, "xmldsig-signedprops", Sc14n.Tran.SubsetById, digAlg, tranMethod); Debug.WriteLine("DigestValue=" + s); // Set the DigestValue in the original string xmlStr = xmlStr.Replace("@!DIG-SIGNEDPROPS!@", s); // Compute digest of (completed) SignedInfo sidigval = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "ds:SignedInfo", Tran.SubsetByTag, sigDigAlg, tranMethod); Debug.WriteLine("ds:SignedInfo Digest=" + sidigval); if (sidigval.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute C14N digest value: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Compute signature value and insert in Signature string s = Pki.Sig.SignDigest(Pki.Cnv.FromBase64(sidigval), sbPriKey.ToString(), "", sigAlg); // Clean up private key Pki.Wipe.String(sbPriKey); Debug.WriteLine(String.Format("SignatureValue=" + s)); if (s.Length == 0) { strMsg = String.Format("**ERROR: Failed to compute signature: '{0}'", Pki.General.LastError()); return strMsg; } xmlStr = xmlStr.Replace("@!SIGVAL!@", s); // Final check for uncompleted '@!..!@' int nret = xmlStr.IndexOf("@!"); if (nret >= 0) { Debug.WriteLine("WARNING: uncompleted '@!..!@' items"); } // Output XML should now be complete // Final check (Note convert string to UTF-8 bytes before passing) s = Sc14n.C14n.ToDigest(Encoding.UTF8.GetBytes(xmlStr), "", 0, 0, 0); if (s.Length == 0) { strMsg = String.Format("**ERROR: XML internal problem: '{0}'", Sc14n.Err.LastError()); return strMsg; } // Write final document string to output file File.WriteAllText(outputXmlFile, xmlStr); // Return full path name of output file created FileInfo fi = new FileInfo(outputXmlFile); return fi.FullName; }
string signedDoc = SignXadesBesDoc("xades-bes1-signed.xml", "xades-bes1-base.xml", "AlicePrivRSASign.p8e", "password");
Example base document: xades-bes1-base.xml
Final signed document: xades-bes1-signed.xml
> sc14n -d2 -S "ID=MyInvoice" xades-bes1-base.xml r6YfXvQbTc7UvR9HaAwmiVpJvCJoLE8I+iYk8cSpfOY= > sc14n -d2 -S "xmldsig-keyinfo" xades-bes1-base.xml gETpjHP4SKV4Wgxpra3123ZhP8Nqfa556+PtxqP+EXw= > sc14n -d2 -S "xmldsig-signedprops" xades-bes1-base.xml i07ubwVc8oeOUo3QhaMmg/POnkytK2PxtC3KcYRMc24= > sc14n -d2 -s "ds:SignedInfo" xades-bes1-signed.xml GBFa/FInfd2dWCvzmzVPL6JPzGg8BR0TSc7Sb9HTpsw= > pki-sign-digest.py GBFa/FInfd2dWCvzmzVPL6JPzGg8BR0TSc7Sb9HTpsw= WhC6WchlS239Jn/TTikj2dlWV+gR7B9ko38xrR9/jY3CMc13e4y900qDEHPaHeAvp6T+lDod/dKeSLkGIICBwWLOhcUpBaNs+ukGNWCMomcOms2nOaIeeaFTZId8PA1pv3UK9J9hGlfD8lftEi4/y8/WVAyEzmXGGRDRV7iktyY=
// INLINE TEMPLATES... // Edit these to suit requirements, e.g. remove "ds:" prefix, change white-space, remove unwanted nodes, etc. const string KeyInfoTemplate = @"<ds:KeyInfo Id=""xmldsig-@!GUID!@-keyinfo""> <ds:KeyValue> <ds:RSAKeyValue> <ds:Modulus>@!RSA-MOD!@</ds:Modulus> <ds:Exponent>@!RSA-EXP!@</ds:Exponent> </ds:RSAKeyValue> </ds:KeyValue> <ds:X509Data> <ds:X509Certificate>@!X509-CERT!@</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> "; const string xadesSigningCertTemplate = @"<xades:SigningCertificate> <xades:Cert> <xades:CertDigest> <ds:DigestMethod Algorithm=""@!CERT-DIG-ALG!@"" /> <ds:DigestValue>@!DIG-CERT!@</ds:DigestValue> </xades:CertDigest> <xades:IssuerSerial> <ds:X509IssuerName>@!X509-ISSUERNAME!@</ds:X509IssuerName> <ds:X509SerialNumber>@!X509-SERIALNUMBER!@</ds:X509SerialNumber> </xades:IssuerSerial> </xades:Cert> </xades:SigningCertificate> "; /// <summary> /// Complete the KeyInfo template for given X.509 certificate /// </summary> /// <param name="certFileOrString">Filename of X.509 certificate or string containing base64 or PEM representation</param> /// <returns>String containing completed XML element, or message starting with "**ERROR"</returns> static string MakeKeyInfo(string certFileOrString) { string strMsg; string s, xs; string pubKey, certStr; // Get template s = KeyInfoTemplate; // Read in certificate as a one-line base64 string certStr = Pki.X509.ReadStringFromFile(certFileOrString); if (certStr.Length == 0) { strMsg = String.Format("**ERROR: Failed to read X.509 certificate in '{0}'", certFileOrString); return strMsg; } // We will use the cert string from now on... // Extract the RSA public key from the certificate pubKey = Pki.Rsa.ReadPublicKey(certStr).ToString(); if (pubKey.Length == 0) { strMsg = String.Format("**ERROR: Could not extract public key from certificate"); return strMsg; } // Compute and substitute values for placeholders s = s.Replace("@!X509-CERT!@", certStr); xs = Pki.Rng.Guid(); // NB different each time s = s.Replace("@!GUID!@", xs); // Compute and substitute RSAKeyValue components xs = Pki.Rsa.KeyValue(pubKey, "Modulus"); s = s.Replace("@!RSA-MOD!@", xs); xs = Pki.Rsa.KeyValue(pubKey, "Exponent"); s = s.Replace("@!RSA-EXP!@", xs); return s; } /// <summary> /// Complete the <c>xades:SigningCertificate</c> template for given X.509 certificate /// </summary> /// <param name="certFileOrString">Filename of X.509 certificate or string containing base64 or PEM representation</param> /// <param name="useSha256">If true, use SHA-256 algorithm for CertDigest value (default = use SHA-1)</param> /// <returns>String containing completed XML element, or message starting with "**ERROR"</returns> static string MakeXadesSigningCert(string certFileOrString, bool useSha256 = false) { const string DIGALG = "http://www.w3.org/2000/09/xmldsig#sha1"; const string DIGALG256 = "http://www.w3.org/2001/04/xmlenc#sha256"; string strMsg; string s, xs; string certStr; Pki.HashAlgorithm hashAlg = Pki.HashAlgorithm.Sha1; // Get template s = xadesSigningCertTemplate; // Read in certificate as a one-line base64 string certStr = Pki.X509.ReadStringFromFile(certFileOrString); if (certStr.Length == 0) { strMsg = String.Format("**ERROR: Failed to read X.509 certificate in '{0}'", certFileOrString); return strMsg; } // Extract and substitute values for IssuerSerial xs = Pki.X509.QueryCert(certStr, "serialNumber", Pki.X509.OutputOpts.Decimal); s = s.Replace("@!X509-SERIALNUMBER!@", xs); xs = Pki.X509.QueryCert(certStr, "issuerName", Pki.X509.OutputOpts.Ldap); s = s.Replace("@!X509-ISSUERNAME!@", xs); // Compute CertDigest value if (useSha256) { // Use SHA-256 algorithm s = s.Replace("@!CERT-DIG-ALG!@", DIGALG256); hashAlg = Pki.HashAlgorithm.Sha256; } else { // Default SHA-1 s = s.Replace("@!CERT-DIG-ALG!@", DIGALG); } xs = Pki.Cnv.ToBase64(Pki.Cnv.FromHex(Pki.X509.CertThumb(certStr, hashAlg))); s = s.Replace("@!DIG-CERT!@", xs); return s; }
string s; s = MakeKeyInfo("AliceRSASignByCarl.cer"); Console.WriteLine(s); s = MakeXadesSigningCert("AliceRSASignByCarl.cer"); Console.WriteLine(s); s = MakeXadesSigningCert("AliceRSASignByCarl.cer", useSha256: true); Console.WriteLine(s);
<ds:KeyInfo Id="xmldsig-1c041492-d343-4d86-abe1-cc953dc23542-keyinfo"> <ds:KeyValue> <ds:RSAKeyValue> <ds:Modulus>4IlzOY3Y9fXoh3Y5f06wBbtTg94Pt6vcfcd1KQ0FLm0S36aGJtTSb6pYKfyX7PqCUQ8wgL6xUJ5GRPEsu9gyz8ZobwfZsGCsvu40CWoT9fcFBZPfXro1Vtlh/xl/yYHm+Gzqh0Bw76xtLHSfLfpVOrmZdwKmSFKMTvNXOFd0V18=</ds:Modulus> <ds:Exponent>AQAB</ds:Exponent> </ds:RSAKeyValue> </ds:KeyValue> <ds:X509Data> <ds:X509Certificate>MIICLDCCAZWgAwIBAgIQRjRrx4AAVrwR024uxBCzsDANBgkqhkiG9w0BAQUFADASMRAwDgYDVQQDEwdDYXJsUlNBMB4XDTk5MDkxOTAxMDg0N1oXDTM5MTIzMTIzNTk1OVowEzERMA8GA1UEAxMIQWxpY2VSU0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOCJczmN2PX16Id2OX9OsAW7U4PeD7er3H3HdSkNBS5tEt+mhibU0m+qWCn8l+z6glEPMIC+sVCeRkTxLLvYMs/GaG8H2bBgrL7uNAlqE/X3BQWT3166NVbZYf8Zf8mB5vhs6odAcO+sbSx0ny36VTq5mXcCpkhSjE7zVzhXdFdfAgMBAAGjgYEwfzAMBgNVHRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIGwDAfBgNVHSMEGDAWgBTp4JAnrHggeprTTPJCN04irp44uzAdBgNVHQ4EFgQUd9K00bdMioqjzkWdzuw8oDrj/1AwHwYDVR0RBBgwFoEUQWxpY2VSU0FAZXhhbXBsZS5jb20wDQYJKoZIhvcNAQEFBQADgYEAPnBHqEjME1iPylFxa042GF0EfoCxjU3MyqOPzH1WyLzPbrMcWakgqgWBqE4lradwFHUv9ceb0Q7pY9Jkt8ZmbnMhVN/0uiVdfUnTlGsiNnRzuErsL2Tt0z3Sp0LF6DeKtNufZ+S9n/n+dO/q+e5jatg/SyUJtdgadq7rm9tJsCI=</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> <xades:SigningCertificate> <xades:Cert> <xades:CertDigest> <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /> <ds:DigestValue>swxIhVBVwuZM4xlkktS4ODGms8s=</ds:DigestValue> </xades:CertDigest> <xades:IssuerSerial> <ds:X509IssuerName>CN=CarlRSA</ds:X509IssuerName> <ds:X509SerialNumber>93318145165434344057210696409401045936</ds:X509SerialNumber> </xades:IssuerSerial> </xades:Cert> </xades:SigningCertificate> <xades:SigningCertificate> <xades:Cert> <xades:CertDigest> <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /> <ds:DigestValue>EOeamZPCaofyEJ7B6B4Kw62g7husH+V/2FRQ4sfCQGs=</ds:DigestValue> </xades:CertDigest> <xades:IssuerSerial> <ds:X509IssuerName>CN=CarlRSA</ds:X509IssuerName> <ds:X509SerialNumber>93318145165434344057210696409401045936</ds:X509SerialNumber> </xades:IssuerSerial> </xades:Cert> </xades:SigningCertificate>
<SignedInfo>
or <ds:SignedInfo>
element.
It has the example RSA private key hardcoded in.
# pki-sign-digest.py import sys import cryptosyspki as pki def sign_digest(sidigval, prikeyfile, password): """Compute signature value from base64-encoded digest value. Args: sidigval (str): Digest value of C14N transformed <SignedInfo> element in base64 encoding. prikeyfile (str): Filename of RSA private key or a string containing its PEM representation. password (str): Password for encrypted key (or ``""`` if no password). Returns: str: Signature value in base64 encoding suitable for a <SignatureValue> element. """ # We can deduce the digest algorithm by the length of the encoded digest value diglen = len(sidigval) if len(sidigval) == 28: sigalg = pki.Sig.Alg.RSA_SHA1 elif len(sidigval) == 44: sigalg = pki.Sig.Alg.RSA_SHA256 else: raise ValueError("Invalid digest length") return pki.Sig.sign_digest(pki.Cnv.frombase64(sidigval), prikeyfile, password, sigalg) def show_tests(): print("Tests...") sigval = sign_digest("y1r4smbzRRqqH/5unQZ80tgm+e4=", "AlicePrivRSASign.p8e", "password") print(sigval) sigval = sign_digest("E/zGQtZUBdFG1Q5Mevs2MjQog9KmmbuhFPsWRYF9GH4=", "AlicePrivRSASign.p8e", "password") print(sigval) if __name__ == "__main__": if len(sys.argv) <= 1: show_tests() else: # Pass sidigval as first argument on command line; hardcoded key and password. print(sign_digest(sys.argv[1], "AlicePrivRSASign.p8e", "password"))To use on the command line, pass the base64-encoded digest value as the first parameter. For example
> pki-sign-digest.py GBFa/FInfd2dWCvzmzVPL6JPzGg8BR0TSc7Sb9HTpsw= WhC6WchlS239Jn/TTikj2dlWV+gR7B9ko38xrR9/jY3CMc13e4y900qDEHPaHeAvp6T+lDod/dKeSLkGIICBwWLOhcUpBaNs+ukGNWCMomcOms2nOaIeeaFTZId8PA1pv3UK9J9hGlfD8lftEi4/y8/WVAyEzmXGGRDRV7iktyY=
For more details on using Python, see A Python interface to CryptoSys PKI Pro.
Download example files used above: xmldsig3-examples.zip (14 kB).
Code snippets from above in complete source code modules.
To contact us or comment on this page, please send us a message.
This page first published 24 February 2022. Last updated 24 February 2022