Signing Specific XML With References

I've previously blogged about creating XML digital signatures using the .NET framework, but today I'd like to write about a more advanced technique using these signatures. My previous post signed an entire XML document, however, this is not always necessary or even desirable. For instance, if a particular XML document was being authored by several people, you might only want to sign the portions that you worked on.

In this post, I'm going to work with the following XML, signing the elements named signed, but not providing a signature for the unsigned element.

<xml>
  <signed id='tag1'>Signed Data</signed>
  <unsigned id='tag2'>Unsigned Data</unsigned>
  <signed id='tag3'>More Signed Data</signed>
</xml>

Previous signatures I've shown have all contained only one reference, which referred to the entire document. In order to sign more specific portions of the document, you can sign elements by id. An element's id is any element that contains an 'id', 'Id', or 'ID' attribute. Its important to keep in mind that signing an element will also sign any sub elements.

In order to refer to an element by ID, the reference must be created by passing a string of the form "#id". Here's some sample code that will create references to the signed data from above.

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

SignedXml signer = new SignedXml(doc);
signer.AddReference(new Reference("#tag1"));
signer.AddReference(new Reference("#tag3"));

Notice that its perfectly legal to use multiple references in the same signature. This allows me to sign multiple portions of the document with my key. The process of actually signing the document is the same as it was with only one reference. Of course, in reality I would not be generating a random key to sign with, but I would use a key pair that was available for interested parties to find, so that they could verify the signature.

signer.SigningKey = new RSACryptoServiceProvider();
signer.ComputeSignature();

This produces signed XML that looks like 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>6kGyfacrtc02JR23FNmcoR+OKzc=</DigestValue>
    </Reference>
    <Reference URI="#tag3">
      <DigestMethod Algorithm="https://www.w3.org/2000/09/xmldsig#sha1" />
      <DigestValue>DtoiDLz3qs7wLwN8Y9A8J5Qpb/I=</DigestValue>
    </Reference>
  </SignedInfo>
  <SignatureValue>SQN+6HlM . . . NqvsN04=</SignatureValue>
</Signature>

Verifying this signature works exactly the same as verifying the signature over an entire document.  You'll also notice that even if you modify the value of the unsigned element, the signature still verifies.

// modify the unsigned element -- the signature should still verify
XmlElement unsignedElem = doc.SelectSingleNode("//xml/unsigned") as XmlElement;
unsignedElem.InnerText = "Modified Data";

SignedXml verifier = new SignedXml(doc);
verifier.LoadXml(signer.GetXml());

if(verifier.CheckSignature(signer.SigningKey)))
    Console.WriteLine("Pass");
else
    Console.WriteLine("Fail");

Updated: I needed to be more clear above. "id" and "ID" only work on Whidbey. If you're using v1.0 or 1.1 of the framework, you'll need to use "Id" attributes.