XML Digital Signatures in .Net

The .Net framework has built in support for signing XML files with an XML digital signature.  Here's a sample of how to create and verify an enveloped digital signature using these classes.

There are three types of XML digital signatures:

  1. Enveloped - The signature is contained within the document it is signing
  2. Enveloping - The signed XML is contained within the signature element
  3. Detached - The signature is in a seperate document from the signed data

In this sample, I will create an enveloped signature over a order record recieved from a ficticous online store.  The XML for that order, saved in a file order.xml is:

<order>
   <purchase>
     <item quantity="1">Def Leppard: Pyromania</item>
     <item quantity="1">Ozzy Osbourne: Goodbye to Romance</item>
   </purchase>
   <shipping>
     <to>Shawn Farkas</to>
     <street>5 21st Street</street>
     <city>Seattle</city>
     <state>WA</state>
     <zip>98000</zip>
   </shipping>
   <payment>
     <card type="visa">0000-0000-0000-0000</card>
     <address sameAsShipping="yes"/>
   </payment>
</order>

Creating the Signature

The first step in signing this document, is loading it into an XmlDocument object, and creating a SignedXml object for that XmlDocument:

// setup the document to sign
XmlDocument doc = new XmlDocument();
doc.Load("order.xml");
SignedXml signer = new SignedXml(doc);

Next, the key that will be used to sign the document must be setup.  In this sample, I will just generate a random RSA key, but in reality, the website would probably have an RSA key that they would always use to sign the documents with.

// setup the key used to sign
RSA key = new RSACryptoServiceProvider();
signer.KeyInfo = new KeyInfo();
signer.KeyInfo.AddClause(new RSAKeyValue(key));
signer.SigningKey = key;

The key must be set as the signing key, as well as placed in an RSAKeyValue clause.  The RSAKeyValue clause puts the public portion of the keypair into the signature itself, allowing anyone who retrieves the document to validate the signature, without having to know what key was used to sign it with.  The next step is to create a reference to the data being signed.

// create a reference to the root of the document
Reference orderRef = new Reference("");
orderRef.AddTransform(new XmlDsigEnvelopedSignatureTransform());
signer.AddReference(orderRef);

A reference with a URI that is the empty string refers to the entire containing document.  However, since this is going to be an enveloped signature, validating the entire document would result in an invalid signature, since the signature value itself will be a part of the document.  Therefore, we must add an XmlDsigEnvelopedSignatureTransform, which prevents the signature validator from looking at the actual signature itself when validating the document.  The last step is to compute the signature, and add it to the document:

// add transforms that only select the order items, type, and
// compute the signature, and add it to the document
signer.ComputeSignature();
doc.DocumentElement.AppendChild(signer.GetXml());
doc.Save("order-signed.xml");

The resulting signed order looks like this:

<?xml version="1.0" standalone="yes"?>
<order>
  <purchase>
    <item quantity="1">Def Leppard: Pyromania</item>
    <item quantity="1">Ozzy Osbourne: Goodbye to Romance</item>
  </purchase>
  <shipping>
    <to>Shawn Farkas</to>
    <street>5 21st Street</street>
    <city>Seattle</city>
    <state>WA</state>
    <zip>98000</zip>
  </shipping>
  <payment>
    <card type="visa">0000-0000-0000-0000</card>
    <address sameAsShipping="yes" />
  </payment>
  <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="">
        <Transforms>
          <Transform Algorithm="https://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="https://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>BPoz+CmKZyTATOhskqke3iOXmvA=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>gkw197s1e ...  N60Og+U=</SignatureValue>
    <KeyInfo>
      <KeyValue>
        <RSAKeyValue>
          <Modulus>xC4bfXcL ...  fUV5phs=</Modulus>
          <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</order>

Verifying the Signature

Verifying the signature produced above is a very easy process, with the help of the SignedXml class.  It involves only three steps:

  1. Load the XML containing the signature
  2. Load the signature itself
  3. Call CheckSignature

The first step, loading the XML containing the signature is very similar to loading the unsigned XML above.

XmlDocument doc = new XmlDocument();
doc.Load("order-signed.xml");SignedXml verifier = new SignedXml(doc);

Next, the SignedXml class must be given the value of the signature it is to validate.  This can be done by looking for elements with the tag name of Signature:

verifier.LoadXml(doc.GetElementsByTagName("Signature")[0] as XmlElement);

Finally, the signature needs to be checked for validity:

if(verifier.CheckSignature())
    Console.WriteLine("Signature verified");
else
    Console.WriteLine("Signature not valid");

Finishing Up

The above example shows how to create a signature that prevents a malicious person from modifying the contents of a CD order.  However, nothing above prevents that person from reading the order and stealing the address or even credit card number of the person who placed it.  In a future post, I'll show an example of using a new feature being added to Whidbey, XML Encryption, to prevent unwanted eyes from viewing this sensitive information.