Freigeben über


Searching for Custom ID Tags With Signed XML

Last week, I blogged about using references to sign only specific parts of an XML document. The biggest limitation with doing this is that you must refer to the nodes that are being signed by ID, which for v1.1 and 1.0 of the framework was given by an attribute named "Id". The problem there is that the Id attribute may already have another use in your schema, and you cannot reuse them for creating node names. Another problem that may come up is that the XML being signed may be generated by a tool or program, and it's not possible for you to add Id tags. Whidbey reduces this limitation somewhat by also allowing "id" and "ID", but the fundamental problem still exists.

Recently, this problem cropped for one of our customers who was having a problem getting signed XML to work in his environment. Their application was trying to sign XML generated by a Java program. The Java application had generated ids by using "_Id" attributes, making it impossible to sign with an id based reference.  If the only problem was creating a signature, then there would be an easy workaround.  However, the C# portion of the program was trying to verify the signature that the Java application had created, and could not resolve references to the _id elements.

So, how did we solve this problem? Actually with a very clever solution from one of the other members of the security team. Nodes that are being referred to by ID are resolved in the GetIdElement method of the SignedXml class. By subclassing SignedXml and overriding this method, its possible to create your own id node resolver. I'll show a sample here that allows ids prefixed with underscores to be resolved by XML signature engine.

Although this sample relies on a similar ID attribute mechanism for identifying nodes, there's nothing stopping you from creating a fancier system. Just as long as you always return exactly one XmlElement representing the specified node, or null if the node could not be found. The best use of this technique is to enable interop scenarios. If you're trying to do this simply to have more fine-grained control over which nodes your reference identifies, I'll show a better technique later this week.

Implementation

The only method that I'll need to override is the GetIdElement method. In addition, I'll provide a constructor that takes an XmlDocument, and passes it along to the SignedXml constructor. I've also defined an array of strings, which represent the attributes that I'll allow to identify nodes. Here's the code:

public class CustomIdSignedXml : SignedXml
{
    private static readonly string[] idAttrs = new string[]
    {
        "_id",
        "_Id",
        "_ID"
    };

    public CustomIdSignedXml(XmlDocument doc) : base(doc)
    {
        return;
    }
    
    public override XmlElement GetIdElement(XmlDocument doc, string id)
    {
        // check to see if it's a standard ID reference
        XmlElement idElem = base.GetIdElement(doc, id);
        if(idElem != null)
            return idElem;

        // if not, search for custom ids
        foreach(string idAttr in idAttrs)
        {
            idElem = doc.SelectSingleNode("//*[@" + idAttr + "=\"" + id + "\"]") as XmlElement;
            if(idElem != null)
                break;
        }

        return idElem;
    }
}

So what's going on here? At the beginning of the method, I call the default GetIdElement, and if that found a match, return that node. This allows my resolver to continue working with "Id" nodes. Since I do this at the beginning of the method, it also makes it so that nodes with the standard id attributes take precedence over nodes with my custom attributes. Next, I loop over my custom attributes, and perform an XPath query, looking for the first node that has a custom attribute with the correct value. Since I quit searching for nodes as soon as I find a match, the order that the attributes appear in the idAttrs array is also the order of precedence. For instance a reference to #idnode when run over the following XML,

<root>
  <node1 _id='idnode'/>
  <node2 Id='idnode'/>
  <node3 _ID='idnode'/>
  <node4 _ID='otherref'/>
  <node5 _id='otherref'/>
  <node6 _id='otherref'/>
</root>

will match node2, since IDs found by the SignedXml class have the highest precedence. A reference to #otheref will match node5, since _id occurs before _ID in my search and only the first match is returned.

Searching for the id nodes is done with the SelectSingleNode method, passing an XPath query customized for the name of the id attribute and the ID I'm looking for. For instance, when searching for nodes with an _ID attribute that would match a reference to #myref, the XPath would be:

//*[@_Id="myref"]

Signing With Custom IDs

Creating a signature that would use the custom id resolver is done in exactly the same way that normal signatures containing id references are done. The only difference is that instead of creating a SignedXml object, you create an instance of the custom ID resolver. Since GetIdElement is virtual, you can even assign the instance into a SignedXml object, and everything will still work as expected. This helps to enable scenarios where the code that actually computes signatures lives in a different method that you don't control, however you do have write access to the SignedXml object they use.

Enough talk, here's some sample code showing a signature being created with my custom _Id resolver. The XML that I'm going to sign is:

<xml>
  <tag Id='tag1'/>
  <tag _Id='tag2'/>
</xml>

And the code that computes the signature is:

XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

SignedXml signer = new CustomIdSignedXml(doc);
signer.AddReference(new Reference("#tag1"));
signer.AddReference(new Reference("#tag2"));
        
signer.SigningKey = new RSACryptoServiceProvider();
signer.ComputeSignature();
Console.WriteLine(signer.GetXml().OuterXml)

This produces a signature similar to the following:

<Signature xmlns="https://www.w3.org/2000/09/xmldsig#">
  <SignedInfo>
    <CanonicalizationMethod Algorithm="https://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
    <SignatureMethod Algorithm="https://www.w3.org/2000/09/xmldsig#rsa-sha1" />
    <Reference URI="#tag1">
      <DigestMethod Algorithm="https://www.w3.org/2000/09/xmldsig#sha1" />
      <DigestValue>/dsJPkLT3QydsHQ1dpmMLPEIbRo=</DigestValue>
    </Reference>
    <Reference URI="#tag2">
      <DigestMethod Algorithm="https://www.w3.org/2000/09/xmldsig#sha1" />
      <DigestValue>AWY9mgt5Z+jRSS+CevluG77gFC8=</DigestValue>
    </Reference>
  </SignedInfo>
  <SignatureValue>JVXWblnT . . . 55rZ7zc=</SignatureValue>
</Signature>

Verifying the Signature

Remember that since the standard SignedXml class will have no idea how to resolve references to custom ID nodes, so verification will also need an instance of the custom signed xml class. However, aside from this difference verification works exactly as you would expect

SignedXml verifier = new CustomIdSignedXml(doc);
verifier.LoadXml(signer.GetXml());
if(verifier.CheckSignature(signer.SigningKey))
    Console.WriteLine("Signature verified");
else
    Console.WriteLine("Invalid signature");

Comments

  • Anonymous
    June 22, 2004
    I need something similar; to override the URI attribute of <Reference URI="">
    I need a URI like this
    "cid:test.submission@filer.com/UniqueId1"
    to point to something like this
    "c:batchUniqueId1.xml"
    I think I would need to subclass the Reference class and override
    Reference.GetXml or
    Reference.LoadXml
    Can you please give me a hint?
  • Anonymous
    June 22, 2004
    Unfortunately you're going to have a problem implementing your solution. Deriving from the Reference class will not work, because the SignedXml class won't know to create your derived class. The only two virtual methods on SignedXml are GetIdElement and GetPublicKey, neither of which would be able to do the conversion for you.

    -Shawn
  • Anonymous
    June 23, 2004
    Thank you for the quick response;
    Our applications needs to create a sort of detached signature used in a SOAP with attachments MIME type document and the URI has to be in this format for the client to be able to verify the signature. Unfortunately, SignedXML can not solve this URI type; Could you please suggest a workaround? Under what circumstances does SignedXML use GetElementByID method to find the content and how does it resolve URIs that point to external resources?
    Thank you
    - Andrew
  • Anonymous
    June 23, 2004
    SignedXml takes looks at reference URIs and does three things:

    1. If the URI is the empty string, it resolves to the whole document
    2. If the URI starts with the # symbol, GetElementByID is called
    3. Otherwise, the URI is considered external, and is attempted to be resolved

    If the URI is resolved, first a System.Uri object is created for it. Then this Uri object is passed to WebRequest.Create. The resulting WebRequest is then used to get the data being referenced.

    -Shawn
  • Anonymous
    June 23, 2004
    Is there a way to use this URI "c:batchUniqueId1.xml" with SignedXML to calculate the digest of the document, and then, just before signing, replace the Reference URI with this one "cid:test.submission@filer.com/UniqueId1" so the client can veify it?
  • Anonymous
    June 24, 2004
    You could try using "file://c:/batch/UniqueId1.xml", though you won't be able to swap out for the other URI before signing. Have you looked at the WSE toolkit we make available? They also have a signed xml class, and I don't know much about it, but it might be worth looking at to see if it supports your scenario.

    -Shawn
  • Anonymous
    June 25, 2004
    All these issues arise because we are trying to sign a SOAP with Attachments type message; At this point we have no better idea then to try to create it manually. If you have any examples that show how to create SwA using the .Net framework or other Microsoft tool that would be of great help.

    Thank you
  • Anonymous
    July 21, 2004
    Shawn,
    I am still trying to find a solution to the "cid:..." type URI.
    My latest idea is as follows:
    Generate the SignedXML using "file://c:/batch/UniqueId1.xml" type URIs
    Next I will edit the <SignedInfo> element and replace the URIs with the "cid:" type URIs and then use the System.Security.Cryptography classes to manually recompute the digest and <SignatureValue>; Hopefully the SignatureValue will be able to be verified by an api that understands the "cid:" URIs.
    My questions are:
    1. is the approach valid
    2. what are the steps required to compute the new digest and signature value like
    a. canonicalize <SignedInfo> element
    b. compute digest using SHA1Managed.ComputeHash
    c. RSAPKCS1SignatureFormatter.CreateSignature(HashValue)

    Your support is much appreciated
  • Anonymous
    July 22, 2004
    Sure, that will work, although it is a lot of work for you :-)

    For the steps, you can follow the outline in the W3C XML Encryption standard. Specifically, you'll want to loook at section 3.1.2. (http://www.w3.org/TR/xmldsig-core/#sec-CoreGeneration)

    -Shawn
  • Anonymous
    October 04, 2005
    Great post!!!!

    You've save me a great problem!

    Thanks a lot.

    Joe.