Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate Digital Signature but with a Specific Namespace Prefix ("ds:")

I digitally sign XML files, but need the signature tags contain the namespace prefix "ds". I researched quite the google and found many of the same questions, but no satisfactory answer.

I tried to put the "ds" manually in the file, but the signature becomes invalid. The tag "SignatureValue" signs the tag "SignedInfo" so the signature becomes invalid.

Could somebody show me how I generate the value of the tag "SignatureValue" so I can replace the signature after adding the prefix "ds"?

like image 669
Renato Sucoski Siqueira Avatar asked Jun 01 '15 17:06

Renato Sucoski Siqueira


People also ask

How do you add a prefix in XML?

When using prefixes in XML, a namespace for the prefix must be defined. The namespace can be defined by an xmlns attribute in the start tag of an element. The namespace declaration has the following syntax. xmlns:prefix="URI".

What is DS signature?

A digital signature is a mathematical technique used to validate the authenticity and integrity of a message, software or digital document. It's the digital equivalent of a handwritten signature or stamped seal, but it offers far more inherent security.


2 Answers

Apparently a lot of people ran into the same problem. After investigating source code of the class Signature, I came to conclusion that Microsoft aimed to help us. There is hardcoded prefix "ds" in the method LoadXml(). So, it is possible to generate signature, then add namespace prefix "ds" to it, load modified signature back and recompute "SignatureValue". Unfortunatelly bug in the library makes things a bit harder than they need to be. The code with workaround and comments is below.

public static void SignXml(XmlDocument xmlDoc, X509Certificate2 cert)
{
        // transformation cert -> key omitted
        RSACryptoServiceProvider key;

        // Create a SignedXml object. 
        SignedXml signedXml = new SignedXml(xmlDoc);

        // Add the key to the SignedXml document. 
        signedXml.SigningKey = key;
        signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256";
        signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;

        // Create a reference to be signed. 
        Reference reference = new Reference();
        reference.Uri = "#foo";
        reference.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
        // Add an enveloped transformation to the reference. 
        reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
        reference.AddTransform(new XmlDsigExcC14NTransform());
        signedXml.AddReference(reference);

        KeyInfo keyInfo = new KeyInfo();
        KeyInfoX509Data keyInfoData = new KeyInfoX509Data();
        keyInfoData.AddIssuerSerial(cert.IssuerName.Format(false), cert.SerialNumber);
        keyInfo.AddClause(keyInfoData);
        signedXml.KeyInfo = keyInfo;

        // Compute the signature. 
        signedXml.ComputeSignature();

        // Add prefix "ds:" to signature
        XmlElement signature = signedXml.GetXml();
        SetPrefix("ds", signature);

        // Load modified signature back
        signedXml.LoadXml(signature);

        // this is workaround for overcoming a bug in the library
        signedXml.SignedInfo.References.Clear();

        // Recompute the signature
        signedXml.ComputeSignature();
        string recomputedSignature = Convert.ToBase64String(signedXml.SignatureValue);

        // Replace value of the signature with recomputed one
        ReplaceSignature(signature, recomputedSignature);

        // Append the signature to the XML document. 
        xmlDoc.DocumentElement.InsertAfter(xmlDoc.ImportNode(signature, true), xmlDoc.DocumentElement.FirstChild);
    }

    private static void SetPrefix(string prefix, XmlNode node)
    {
        node.Prefix = prefix;
        foreach (XmlNode n in node.ChildNodes)
        {
            SetPrefix(prefix, n);
        }
    }

    private static void ReplaceSignature(XmlElement signature, string newValue)
    {
        if (signature == null) throw new ArgumentNullException(nameof(signature));
        if (signature.OwnerDocument == null) throw new ArgumentException("No owner document", nameof(signature));

        XmlNamespaceManager nsm = new XmlNamespaceManager(signature.OwnerDocument.NameTable);
        nsm.AddNamespace("ds", SignedXml.XmlDsigNamespaceUrl);

        XmlNode signatureValue = signature.SelectSingleNode("ds:SignatureValue", nsm);
        if (signatureValue == null)
            throw new Exception("Signature does not contain 'ds:SignatureValue'");

        signatureValue.InnerXml = newValue;
    }
like image 134
Ilya Stepanov Avatar answered Sep 20 '22 20:09

Ilya Stepanov


Edit: You can see an algorithm which this post kind of alluded to in my other answer.

Assuming that it isn't possible without writing your own algorithm to canonicalize and sign the document, a possible workaround could be to "inject" the namespace prefix on the signature elements after signing, and then removing it from them before verifying.

For example:

void SignXml(XmlDocument xmlDoc, RSA Key)
{
    SignedXml signedXml = new SignedXml(xmlDoc);
    signedXml.SigningKey = Key;

    Reference reference = new Reference("");
    reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());

    signedXml.AddReference(reference);

    signedXml.ComputeSignature();

    XmlElement xmlSignature = signedXml.GetXml();

    //Here we set the namespace prefix on the signature element and all child elements to "ds", invalidating the signature.
    AssignNameSpacePrefixToElementTree(xmlSignature, "ds");

    xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlSignature, true));
}

bool VerifyXmlSignature(XmlDocument xmlDoc, RSA Key, string prefix)
{
    SignedXml signedXml = new SignedXml(xmlDoc);

    //Get the <ds:Signature /> element
    XmlElement xmlSignature = (XmlElement)xmlDoc.GetElementsByTagName(prefix + ":Signature")[0];

    //Undo what we did after signing
    AssignNameSpacePrefixToElementTree(xmlSignature, "");

    //Now it will pass verification.
    signedXml.LoadXml(xmlSignature);
    return signedXml.CheckSignature(Key);
}

void AssignNameSpacePrefixToElementTree(XmlElement element, string prefix)
{
    element.Prefix = prefix;

    foreach (var child in element.ChildNodes)
    {
        if (child is XmlElement)
            AssignNameSpacePrefixToElementTree(child as XmlElement, prefix);
    }
}
like image 42
Adrian Avatar answered Sep 18 '22 20:09

Adrian