xml:id and SignedXml

A few weeks back, I posted about customizing how SignedXml searches for XML elements identified by a reference to an ID.  By default, SignedXml searches for elements with an attribute named Id that has the given value.

Recently, the W3C has come up with a working draft for xml:id version 1.0.  xml:id is meant to be a uniform way for XML elements to be uniquely identified.  How it works is relatively simple.  Elements simply add an attribute named id, with a namespace prefix of xml, and give it a unique value.  For instance:

<element xml:id='myElem'/>

The xml prefix is a special prefix, and it should be mapped to the URI http://www.w3.org/XML/1998/namespace, according to the Namespaces in XML recommendation.  Before xml:id, the only attributes defined in the xml namespace were xml:lang, xml:space, and xml:base.

As a more realistic example than the one I posted in my earlier blog entry, I've written an extension to the SignedXml class which allows for nodes to be signed given their xml:id.  I've also made the complete source code to the XmlIdSignedXml class available.  (With the standard disclaimers that Microsoft is not responsible if this code destroys your data, eats your dinner, or kicks your puppy when you're not looking.)

The class is pretty simple, providing only one new property to the SignedXml class.  This property, a boolean called Strict, allows you to specify what the class should do if there's not a valid xml:id available.  If Strict is set to true, then the search will fail.  However, if Strict is set to false, then the default SignedXml id search is done.  In either case, xml:id's trump the default search.  XmlIdSignedXml also provides a constant that specifies the namespace URI that the xml prefix should map to.  (Although you'll never need this, since the System.Xml classes will provide this mapping for you).

All of the interesting code is found in the GetIdElement override.  After validating its parameters, this method sets up an XmlNamespaceManager that maps the xml prefix to the URI specified in the W3C's specs.  Then the value to search for is quoted, in single quotes if it contains a double quote, and in double quotes if it contains a single quote.  Searching for an id that contains both single and double quotes is forbidden.

The XPath query //*[@xml:id='idValue'] is used to get all the nodes that match the given xml id.  Since the xml:id recommendation specifically requires that ID's be unique within the document, matching more than one node will cause XmlIdSignedXml to throw an InvalidOperationException.  Assuming it finds only one match, this is returned as the result.  If no matches are found, then the behavior depends on the setting of the Strict property.  By default, Strict is set to false, so XmlIdSignedXml will revert back to the SignedXml semantics.

Signing with this code works exactly the same as signing with a standard SignedXml object.  Remember that when validating documents signed by reference to xml:id, you must use an instance of the XmlIdSignedXml class, otherwise the validation will fail.  If you'd like some sample code to perform the signing / verification, check out my previous post on Searching for Custom IDs, or More Secure XML Digital Signatures.

When using this class, XML that looks like this:

  <signed xml:id='tag1'>Signed Data</signed>
  <unsigned xml:id='tag2'>Unsigned Data</unsigned>
  <signed xml:id='tag3'>More Signed Data</signed>
  <signed id='tag4'>Signed with default processing</signed>

Will create a signature similar to the following:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
    <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="#tag1">
      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    <Reference URI="#tag3">
      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
    <Reference URI="#tag4">
      <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
  <SignatureValue>KAtTJfRQ ... hQUGA+Y=</SignatureValue>