CryptoSys Home > PKI > XML-DSIG signing with CryptoSys PKI and SC14N

XML-DSIG signing with CryptoSys PKI and SC14N


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].

C# code on this page

The C# code on this page is for a C# Console Application using .NET 4.0 and above. In your project, add references to the .NET library files 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;

Using command line:

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.

License

All code on this page is licenced under the terms of the MIT license. For a copy, see <http://opensource.org/licenses/MIT>.

Sign an enveloped-signature XML-DSIG document

Show/hide 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;
}

Example:

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.

Using command line:

> 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=

[Go to top]

Sign a XML-DSIG document with an ID reference

Show/hide C# code

/// <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;
}

Example:

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.

Using command line:

> 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=

[Go to top]

Sign a SOAP document with WSS

Show/hide C# code

/// <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;
}

Example:

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

Using command line:

> 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=

[Go to top]

Sign a XAdES-BES document

Show/hide C# code

/// <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;
}

Example:

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

Using command line:

> 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=

[Go to top]

Populate the KeyInfo element

Show/hide C# code

// 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;
}

Example:

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);

Output:

Show/hide output

<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>

[Go to top]

Python script to compute signature value

Use this Python script on the command line to compute the signature value given the digest value of the C14N-transformed <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.

Example files, code and cross-references to XML documents

Download example files used above: xmldsig3-examples.zip (14 kB).

C# Code

Code snippets from above in complete source code modules.

[Go to top]

References

Contact us

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

[Go to top]