Trustworthy Code

Exchange Data More Securely with XML Signatures and Encryption

Mike Downen and Shawn Farkas

Parts of this article are based on a prerelease version of the .NET Framework 2.0. All information pertaining to those sections is subject to change.

This article discusses:
  • XML Signature and XML Encryption standards
  • Digital signing and encryption features in the .NET Framework 1.x and 2.0
  • X.509 certificate integration
This article uses the following technologies:
XML, .NET Framework, C#, Security

Code download available at:XMLSignatures.exe (241 KB)

Contents

Digital Signing
XML Signature Basics
References
Transforms
Canonicalization
Key Management
Signature Profiles
New Features in the .NET Framework 2.0
EncryptedData Elements
XML Encryption Example
X.509 Support
Conclusion

The XML Signature and XML Encryption standards are being used extensively as building-block technologies. Microsoft® Office InfoPath™ uses XML signatures to sign partial or whole forms. Web services use XML signatures to sign SOAP messages and XML encryption to encrypt them. The XML manifests for ClickOnce®-based applications, new in Visual Studio® 2005, also use XML signatures. The .NET Framework 1.x includes an object model for the XML Signature standard, and the .NET Framework 2.0 adds additional support, while adding an object model for XML encryption as well. This article explains the XML Signature and XML Encryption standards and shows you how to use them with .NET. For the actual XML Signature specification, see the W3C standard at XML-Signature Syntax and Processing.

Digital Signing

Before we dive into the XML Signature standard, let's review the basics of digital signing. Because it's important to prevent a malicious user from altering a message during transmission, digital signing protects the integrity of data and can detect any changes made to it while it's en route to its recipient. Because it's also important to be able to identify a sender, a message is typically signed using the private (secret) key of the sender and verified with the corresponding public key, allowing recipients to confirm the identity of the sender when they know the sender's public key. This prevents a malicious user from pretending to be a known sender, either by trying to send messages as the known sender or by intercepting messages from the known sender and replacing them with their own—a form of the man-in-the-middle attack.

To create a digital signature, first hash the message you are signing using a cryptographic hash function. For any length of input, a cryptographic hash function returns a fixed-length set of bits called the hash value. This hash value cannot be easily converted back to the input. The hash value changes unpredictably when even a single bit changes in the input, so someone cannot find an input that is similar to the original just by finding a similar hash value. One commonly used hash function is SHA-1, which produces a 160-bit hash value.

The next step is to sign the hash value using a signing algorithm and your private key to produce a signature value. You create the signature with your private key so others who have your public key can verify it (more on that later in the article). RSA is a popular cryptographic algorithm to use with signatures.

After you've sent the message and this signature to the recipient, the verification process begins. The received message is hashed with the same hash function used when signing. Then the signature value is verified by passing it, along with the public key and the computed hash, to the signing algorithm. If the computed hash and the signature hash match, then the signature is valid. If the hashes don't match, either the data or the signature has been changed and therefore the integrity of the data is not assured.

You can also sign and verify data using a keyed hash algorithm, but that's beyond the scope of this article. The .NET Framework already includes a rich set of classes for all kinds of hashing, encryption/decryption, and signing/verification algorithms. For more information on these, see the .NET Framework SDK documentation and .NET Framework Security, by Brian LaMacchia, et al (Addison-Wesley, 2002).

XML Signature Basics

You can sign any kind of data using XML Signature, including part of an XML document, other XML documents, or other data of any format. However, in practice, XML signatures are most frequently used to sign other data represented in XML. The XML Signature standard is also very flexible, allowing you to filter and transform your data before you sign it and letting you choose exactly what to sign and how.

Let's take a look at a simple XML Signature scenario: signing an entire document and including the signature in it (see Figure 1). Notice the Signature element that has been added to the document. This element contains the XML signature. Let's look at what each element contains.

Figure 1 Simple XML Document Before and After Signing

A simple document:

<docRoot>
  <a>Hello</a>
  <b>World</b>
</docRoot>

The document when signed:

<docRoot>
  <a>Hello</a>
  <b>World</b>
  <Signature xmlns="https://www.w3c.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod
        Algorithm="https://www.w3c.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod
        Algorithm="https://www.w3c.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.w3c.org/2000/09/xmldsig#sha1"/>
        <DigestValue>cbPT0951Ghb2G3UjpVjWw+7q0Bc=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>IoEwS(3 lines of Base64 text)XSo=</SignatureValue>
  </Signature>
</docRoot>

SignedInfo The children of this element contain all the information about what is signed and how it was signed. The signature algorithm is actually applied to this element and all of its children to generate the signature.

CanonicalizationMethod This specifies the canonicalization (C14N) algorithm used on the SignedInfo element to normalize the XML. We'll talk more about C14N later in the article.

SignatureMethod This element specifies the signature algorithm for this signature. In this example, the signature algorithm is SHA-1 (for hashing) with RSA (for signing the resulting hash value).

Reference These elements specify the data that will be signed and how it should be processed before hashing. The URI attribute, representing the Uniform Resource Identifier, identifies the data to be signed, while the Transforms element (described later) specifies how to process the data before hashing. In this example, we're using a special URI, the empty string, which specifies that the document containing the signature is the data to be included in the signature.

The XML Signature standard uses an indirect signing mechanism for Reference data. Rather than hashing and then encrypting the hash value for all of the data from the Reference, the standard hashes the data for each Reference, using the algorithm specified by the Reference's DigestMethod element, and then stores the hash value in the DigestValue element of the Reference. Then the SignedInfo element and all of its children (including the Reference elements) are hashed; that hash value is encrypted to generate the signature. So you're really signing the hash of the hash of data referred to in Reference elements, but this scheme still protects the integrity of the data. Figure 2 and Figure 3 show the signing and verification process alongside the matching XML.

Transforms Each Reference element can have zero or more transforms specified for it. These transforms are applied to the data for the Reference in the order they are listed in the XML. Transforms allow you to filter or modify the data for the Reference before the data is hashed. In this example, we're using the enveloped signature transform, which selects all of the XML in the containing document except for the Signature element. We must remove the Signature element from the data to be signed; otherwise we would be modifying the data we're trying to sign when we store the signature value. We'll talk more about transforms later in the article.

SignatureValue This element contains the signature value computed by signing the SignedInfo element and all of its children.

Figure 2** Signing Process **

Now let's discuss the processing model for creating a signature (see Figure 2). First, for each Reference element in the signature:

  • Apply each transform algorithm specified in the Transform elements to the data for the Reference, in the order the transforms appear under the Transforms element.
  • Hash the transformed data using the hashing algorithm specified by the DigestMethod element of the Reference.
  • Store the resulting hash value in the DigestValue element of the Reference.

The next step is to canonicalize the SignedInfo element and its children using the algorithm specified in the CanonicalizationMethod element of the signature. Then you sign the SignedInfo element and its children using the algorithm specified in the SignatureMethod element of the signature. The signature value is stored in the eponymous SignatureValue element.

Figure 3** Verification Process **

Signature verification is the inverse of the process just described (see Figure 3). First, you must canonicalize the SignedInfo element and its children using the C14N algorithm specified in the CanonicalizationMethod element. Then you must verify the signature value stored in the SignatureValue element against the SignedInfo element and its children.

Finally, for each reference element in the signature:

  • Apply each transform algorithm specified in the Transform elements of the Reference to the data for the Reference, in the order the transforms appear under the Transforms element.
  • Hash the transformed data for the reference using the hashing algorithm that is specified by the DigestMethod element of the reference.
  • Compare the hash value computed with the value stored in the DigestValue element.

If the signature verification succeeds and the hash value for each reference is equal to the one stored in the signature, the XML signature is valid. Otherwise, either the data referred to by one of the reference elements has changed, or the signature element has been changed.

A signature embedded in the document it signs is called an enveloped signature. The code to create this kind of signature is shown in Figure 4. The code to verify the signature is shown in Figure 5.

Figure 5 Verifying an Enveloped Signature

using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
// Also, add a reference to System.Security.dll

// Load the signed data
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
doc.Load("data-signed.xml");

// Find the Signature element in the document
XmlNamespaceManager nsm = new XmlNamespaceManager(new NameTable());
nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
XmlElement sigElt = (XmlElement)doc.SelectSingleNode(
  "//dsig:Signature", nsm);

// Load the signature for verification
SignedXml sig = new SignedXml(doc);
sig.LoadXml(sigElt);

// Verify the signature, assume the public key part of the
// signing key is in the key variable
if (sig.CheckSignature(key))
    Console.WriteLine("Signature verified");
else 
    Console.WriteLine("Signature not valid");

Figure 4 Creating an Enveloped Signature

using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
// Also, add a reference to System.Security.dll

// Assume the data to sign is in the data.xml file, load it, and
// set up the signature object.
XmlDocument doc = new XmlDocument();
doc.Load("data.xml");
SignedXml sig = new SignedXml(doc);

// Make a random RSA key, and set it on the signature for signing.
RSA key = new RSACryptoServiceProvider();
sig.SigningKey = key;

// Create a Reference to the containing document, add the enveloped
// transform, and then add the Reference to the signature
Reference refr = new Reference("");
refr.AddTransform(new XmlDsigEnvelopedSignatureTransform());
sig.AddReference(refr);

// Compute the signature, add it to the XML document, and save
sig.ComputeSignature();
doc.DocumentElement.AppendChild(sig.GetXml());
doc.Save("data-signed.xml");

You've seen how to create and verify enveloped signatures. While they are commonly used and handy for signing entire XML documents, the XML Signatures standard lets you sign other data as well by specifying different URIs in the Reference element. So let's take a look at different types of references next.

References

In addition to enveloped references (a Reference element that has an empty string for the URI attribute), there are two other broad types of references defined in the XML Signature standard: references to detached data and references to XML data by ID. Detached data is outside of the XML document containing the signature. These references can point to another XML document or to any other type of resource. You would typically use this type of reference when signing multiple resources in one signature, such as an XML document and some other files that are referred to by the document. The following XML code fragment shows an example of a detached reference:

<Reference URI="https://www.example.com/foo.jpg">
  <DigestMethod
    Algorithm="https://www.w3c.org/2000/09/xmldsig#sha1" />
  <DigestValue>cbPT0951Ghb2G3UjpVjWw+7q0Bc=</DigestValue>
</Reference>

In order to create a detached reference using the Framework classes, you simply set the URI of the reference to the URI of the resource when creating the reference object, and then add the reference to the signature, as shown here:

// Create a Reference to detached data, assume a SignedXml object in sig
Reference refr = new Reference("https://www.example.com/foo.jpg");
sig.AddReference(refr);

References to XML data by ID point to XML within the document containing the signature or within the signature itself. For these references, the signature engine looks for an element with an Id attribute that matches the URI in the reference, not including the #. Here's an example of an ID-based reference:

<Reference URI="#myData">
  <DigestMethod
    Algorithm="https://www.w3c.org/2000/09/xmldsig#sha1" />
  <DigestValue>cbPT0951Ghb2G3UjpVjWw+7q0Bc=</DigestValue>
</Reference>

If the element with the Id of "myData" is in the document containing the signature, this reference is complete and the signature engine will find it when processing the signature. You would typically use this type of reference to limit the scope of a signature to a specific part of an example document. For example, in a document processing application, a reviewer would only sign the portions of the XML document she reviewed, not the entire document.

The standard also allows you to add arbitrary data to the signature in an Object element. The XML for an Object element looks like the following line of code:

<Object Id="myData">Your XML goes here</Object>

To create an ID-based reference to an element that is already in the document containing the signature, add code like this:

// Create a Reference to XML data in the containing document,
// assume a SignedXml object in sig
Reference refr = new Reference("#myData");
sig.AddReference(refr);

If the ID-based reference is to an Object element in the signature, you must also add a DataObject to the signature in addition to adding the reference, as in the code that was just shown. The data object can contain any XML that you pass in.

// Adds a DataObject with an Id of "#myData" to the signature, assume a 
// SignedXml object in sig, and xml data of type XmlNodeList in data
DataObject dobj = new DataObject();
dobj.Id = "myData";  // Note: no #
dobj.Data = data;    // XML Data of the Object
sig.AddObject(dobj);

You would typically use a reference to an Object element for signing metadata about the signature, such as a unique identifier for the signer or some other information about the signature.

Transforms

Transforms give you even more control over the content signed by allowing you to modify the data for a reference before the hash value for that data is generated. The enveloped signature transform, for example, removes the Signature node from the XML document before signing it. A reference can specify any number of transforms, which are applied in the order specified in the Transforms element. The .NET Framework classes support the following transforms in addition to the enveloped signature transform that we mentioned earlier:

  • Any canonicalization algorithm can be used as a transform.
  • The Base64 transform allows you to decode data in the Base64 encoding.
  • The XSLT transform allows you to apply an XSLT stylesheet to XML data before signing it. The XSLT stylesheet to be applied is specified as XML under the Transform element.
  • The XPath transform allows you to filter XML data with an XPath expression.

The XPath expression is specified as the text content of an XPath element under the Transform element. It's important to note that the XPath transform acts as a filter, not as a means of selecting nodes in the XML being passed as input. The transform evaluates the XPath expression against each node passed as input to the transform, and the result is converted to a Boolean. An input node is considered to pass the evaluation and will be included in the output of the transform if the result of the evaluation is true. Consider the following XML input to the transform:

<a>
  <b>Some data</b>
  <c>More data</c>
</a>
<d>
  <b>Even more data</b>
</d>

Let's say we only want to select "b" nodes for signing. An XPath transform with the XPath expression of "ancestor-or-self::b" will return the following nodeset, which is what we want:

<b>Some data</b>
<b>Even more data</b>

The Transform element for this XPath expression would look like the following code snippet:

<Transform
  Algorithm="https://www.w3c.org/TR/1999/REC-xpath-19991116">
  <XPath>ancestor-or-self::b</XPath>
</Transform>

To create transforms programmatically when signing, create an instance of a transform object, set its properties appropriately, and add it to the reference to which it applies. The following example adds the XPath transform used in the previous example to a reference to create a transform:

// Add an XPath transform to a reference.
// Assume a Reference object in refr 
XmlDocument doc = new XmlDocument();
doc.LoadXml("<XPath>ancestor-or-self::b</XPath>");
XmlDsigXPathTransform xptrans = new XmlDsigXPathTransform();
xptrans.LoadInnerXml(doc.ChildNodes);
refr.AddTransform(xptrans);

Canonicalization

The purpose of canonicalization is to produce the same XML data for two XML fragments that are logically the same, but may not be represented by the same text. For example, take a look at the following two code fragments. They are logically the same; they differ only in text representation. But if you were to hash both of them as is, the hash values would be different:

<root>
  <a   >Some text</a>
  <b attr1="yes" attr2="no"></b>
  <c    Id="foo">More text</c>
</root>

<root>
  <a>Some text</a>
  <b attr2="no" attr1="yes" />
  <c Id="foo">More text</c>
</root>

To avoid this problem, the default canonicalization algorithm specified in the standard performs a number of tasks including the removal of white space in start and end tags and the conversion of empty elements to start/end tag pairs. It does not, however, change any white space in the content of elements. The complete list of operations performed is available in the Canonical XML recommendation, located at Canonical XML.

The signature engine automatically canonicalizes data when necessary to comply with the W3C standard. Specifically, the signature engine canonicalizes XML data anytime it needs to convert it to binary data for hashing. This happens when it prepares to sign the SignedInfo element and its children, for instance. It also may happen when preparing a reference or the output of a transform for signing. For example, if you use an ID-based reference to other XML data in the document containing the signature, and the reference has no transforms associated with it, the signature engine will canonicalize the XML data for that reference before hashing the data.

Key Management

The XML Signature standard provides the KeyInfo element to help with key management. This element can store a key name, key value, key retrieval method, or certificate information to help receivers verify the signature. The standard does not specify how or if any information in the KeyInfo element should be trusted.

The KeyInfo element can be useful when the sender and recipient share a list of trusted keys or if you come up with some other method for mapping key names to keys, for example. The .NET Framework 1.x has some support for key name, value, and retrieval method. The .NET Framework 2.0 also includes support for X.509 certificates.

Let's say the sender and recipient share a list of keys. The recipient has a public key for each sender from whom she expects to receive messages. The signing application can add the following code to add a KeyInfo element to the signature:

// Adds an KeyInfo element with RSA public key information to the 
// signature. 
// Assumes a SignedXml object in sig, and an RSA object in key.
KeyInfo ki = new KeyInfo();
ki.AddClause(new RSAKeyValue(key));
sig.KeyInfo = ki;

This code should be added before ComputeSignature is called. It will produce a KeyInfo element in the signature that looks something like the following code:

<KeyInfo><KeyValue><RSAKeyValue>
  <Modulus>4LfG(2 lines of Base64 text)2Fr=</Modulus>
  <Exponent>AQAB</Exponent>
</RSAKeyValue></KeyValue></KeyInfo>

This represents the RSA public key used to sign the XML document. The receiving application should compare this key to a list of trusted keys and should not trust the document if the public key is not on the list. Otherwise an attacker could replace a signed document while in transit, signing it with another key. If a signature includes an RSAKeyValue like this, the verification code can call the CheckSignature method of the SignedXml class that takes no parameters, and the .NET Framework will figure out the key from the RSAKeyValue element. Here's an example:

// Verify a signature that includes RSAKeyInfo or DSAKeyInfo.
// Assume a SignedXml object in sig.
bool verified = sig.CheckSignature();

Signature Profiles

Along with the flexibility of XML signatures comes some amount of risk. In particular, because transforms are so flexible, it can be difficult to figure out exactly what data was covered by the signature, which can lead to unexpected or insecure results. These signature profiles can help in this regard by specifying the form of signature your application supports. While there is no standard for signature profiles a signature profile should at the least specify references and transforms that the application expects for a signature, so you can be sure that the data you expect to be signed is indeed signed. Signature profiles could also contain additional data, such as signing algorithms or key sizes expected for signed data. Your application should check and enforce that those signatures it creates and verifies comply with the signature profile that your application supports.

To better understand why profiles are important, consider Figure 1. Suppose you are writing an application that accepts XML signed data, but your application only expects signatures that use the enveloped signature transform and no other transforms. Now someone sends you a signed document with an extra XPath transform, as shown in Figure 6.

Figure 6 Signed Document with an Extra Transform

<docRoot>
  <a>Hello</a>
  <b>World</b>
  <Signature xmlns="https://www.w3c.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod
        Algorithm="https://www.w3c.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod
        Algorithm="https://www.w3c.org/2000/09/xmldsig#rsa-sha1"/>
      <Reference URI="">
        <Transforms>
          <Transform
            Algorithm="https://www.w3.org/2000/09/xmldsig#enveloped-
              signature"/>
          <Transform
            Algorithm="https://www.w3c.org/TR/1999/REC-xpath-19991116">
              <XPath>ancestor-or-self::a</XPath>
         </Transform>
       </Transforms>
       <DigestMethod
         Algorithm="https://www.w3c.org/2000/09/xmldsig#sha1"/>
       <DigestValue>mN4R0653F4ethOiTBeAu+7q0Be=</DigestValue>
     </Reference>
    </SignedInfo>
    <SignatureValue>X4Ie(3 lines of Base64 text)nP3=</SignatureValue>
  </Signature>
</docRoot>

In the example in Figure 6, the signature only covers the "a" element in the sample document. If you just loaded the document and called the CheckSignature method of the SignedXml class, the signature may still verify even though the "b" element is not covered by the signature, because the signature engine will apply the transform specified in the signature. If your application relies on the "b" element being covered in the signature, the integrity of your data has been compromised. The application should verify the signature profile it expects by checking that there is just one reference that has an empty string as the URI and that the reference has one transform: the enveloped signature. It would reject any other signature profile when verifying a signature. Some sample code that checks this signature profile is shown in Figure 7.

Figure 7 Checking a Signature Profile

// This method checks the signature profile for the signature
// in the supplied document. It ensures there is only one
// Signature element and only one enveloped reference with only
// one enveloped signature transform
public bool CheckSignatureProfile(XmlDocument doc)
{
    // Make sure there is only one Signature element
    XmlNamespaceManager nsm = new XmlNamespaceManager(new NameTable());
    nsm.AddNamespace("dsig", SignedXml.XmlDsigNamespaceUrl);
    XmlNodeList sigList = doc.SelectNodes("//dsig:Signature", nsm);
    if (sigList.Count > 1)
        return false; //Wrong number of Signature elements

    //Make sure the Signature element has only one Reference
    XmlElement sigElt = (XmlElement)sigList[0];
    XmlNodeList refList = sigElt.SelectNodes(
        "dsig:SignedInfo/dsig:Reference", nsm);
    if (refList.Count > 1)
        return false; //Wrong number of Reference elements

    // Make sure the Reference URI is ""
    XmlElement refElt = (XmlElement)refList[0];
    XmlAttributeCollection refAttrs = refElt.Attributes;
    XmlNode uriAttr = refAttrs.GetNamedItem("URI");
    if ((uriAttr == null) || (uriAttr.Value != ""))
        return false; // Wrong type of reference

    // Make sure the only tranform is the enveloped signature transform
    XmlNodeList transList = refElt.SelectNodes(
        "dsig:Transforms/dsig:Transform", nsm);
    if (transList.Count != 1)
        return false; //Wrong number of Transform elements
    XmlElement transElt = (XmlElement)transList[0];
        string transAlg = transElt.GetAttribute("Algorithm");
    if (transAlg != SignedXml.XmlDsigEnvelopedSignatureTransformUrl)
        return false; //Wrong type of transform

    return true;
}

So far, we've looked at some different aspects of the XML Signature standard and the support for it in the .NET Framework. Let's put some of these features together into a more complete example. Suppose you're writing an application to exchange messages in XML and you want to sign the entire content of the message. You also want to add some XML data about the signer to the signature element as an object, only signing the signerID element of this data. Your application has access to a well-known list of keys, so you will also store the public key information in the signature and check to make sure the key maps to a well-known key during verification. The code for signing and verifying a message is included in the complete code download for this article. Signing a message with this code will produce an XML signature that looks something like Figure 8.

Figure 8 A More Complex Signed Document

<root>
  <myData1>Some Data</myData1>
  <myData2>More data</myData2>
  <Signature xmlns="https://www.w3c.org/2000/09/xmldsig#">
    <SignedInfo>
      <CanonicalizationMethod
        Algorithm="https://www.w3c.org/TR/2001/REC-xml-c14n-20010315"/>
      <SignatureMethod
        Algorithm="https://www.w3c.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.w3c.org/2000/09/xmldsig#sha1"/>
        <DigestValue>cbPT0951Ghb2G3UjpVjWw+7q0Bc=</DigestValue>
      </Reference>
      <Reference URI="#signer">
        <Transforms>
          <Transform
          Algorithm=
          "https://www.w3c.org/TR/1999/REC-xpath-19991116">
            <XPath xmlns:my="https://example">
              ancestor-or-self::my:SignerID
            </XPath>
          </Transform>
        </Transforms>
        <DigestMethod
          Algorithm="https://www.w3c.org/2000/09/xmldsig#sha1"/>
        <DigestValue>mN4R0653F4ethOiTBeAu+7q0Be</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>IoEwS...</SignatureValue>
    <KeyInfo>
      <KeyValue>
        <RSAKeyValue>
          <Modulus>4LfG(2 lines of Base64 text)2Fr=</Modulus>
          <Exponent>AQAB</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>
    <Object Id="signer">
      <my:SignerData xmlns:my="https://example">
        <my:SignerName>Mike</my:SignerName>
        <my:SignerID>4815</my:SignerID>
      </my:SignerData>
    </Object>
  </Signature>
</root>

New Features in the .NET Framework 2.0

All the XML Signature features discussed so far are available in both the .NET Framework 1.x and 2.0. XML Encryption and X.509 certificate integration are new to version 2.0. X.509 certificate integration makes it easier to use X.509 certificates with XML Signature. The new X509CertificateEx class and related classes make it easier to manipulate and use certificates, and the XML signing object model uses these classes when appropriate. We'll talk more about X.509 integration at the end of this article.

XML Encryption is another W3C standard. Just as XML Signature specifies a format and processing model for creating digital signatures in XML, XML Encryption standardizes how to encrypt data in XML. XML digital signatures are driven through the SignedXml class, and XML encryption is performed using the new EncryptedXml class.

Although XML encryption can be used to encrypt arbitrary data, you would most frequently use it to encrypt other XML. When used in this way, you have a lot of flexibility in how documents are encrypted. For instance, different nodes of an XML document could be encrypted with different keys, while some nodes are left in plain text. And since encrypting something with the EncryptedXml class results in XML, you can even encrypt the encrypted results, a process known as super encryption.

Let's take a look at some encrypted XML (see Figure 9). One interesting thing to note right away is that the XML Encryption standard uses the XML Signature namespace for some elements, including the KeyInfo element.

Figure 9 XML Document Before and After Encryption

A simple document:

<docRoot>
  <a>Hello</a>
  <b>World</b>
</docRoot>

The document when encrypted:

<docRoot>
  <EncryptedData Type="https://www.w3.org/2001/04/xmlenc#Element"
  xmlns="https://www.w3.org/2001/04/xmlenc#">
    <EncryptionMethod
    Algorithm="https://www.w3.org/2001/04/xmlenc#aes256-cbc"/> 
    <KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
      <EncryptedKey xmlns="https://www.w3.org/2001/04/xmlenc#">
        <EncryptionMethod
        Algorithm="https://www.w3.org/2001/04/xmlenc#rsa-1_5"/> 
        <KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
          <KeyName>recipient_public_key</KeyName> 
        </KeyInfo>
        <CipherData>
          <CipherValue>PrI6(3 lines of Base64 text)Dwy4=</CipherValue> 
        </CipherData>
      </EncryptedKey>
    </KeyInfo>
    <CipherData>
      <CipherValue>awcH(3 lines of Base64 text)NNqQ=</CipherValue> 
    </CipherData>
  </EncryptedData>
</docRoot>

EncryptedData Elements

EncryptedData is the root element generated by encrypting the XML, and it contains all the information about the data. EncryptedData contains three main subelements: EncryptionMethod specifies which algorithm was used to encrypt the data, KeyInfo element provides information about which key to use to decrypt the data, and the CipherData or CipherReference element contains the actual encrypted information. The EncryptionMethod element specifies the algorithm used to encrypt and decrypt the cipher data. The algorithms are specified by a URI, like the XML Signature standard. EncryptionMethod is used for both encrypted data and encrypted keys, but not every algorithm can be used in both places. Figure 10 shows where each algorithm can be used. Note that Advanced Encryption Standard (AES) is also available with 192- and 128-bit keys; you just change the key size in the URI.

Figure 10 Where to Use Encrypting Algorithms

  URI Properties of EncryptedXml Class Encrypting Data Encrypting Keys
AES XmlEncAES256Url  
XmlEncAES256KeyWrapUrl  
DES XmlEncDESUrl  
TripleDES XmlEncTripleDESUrl  
XmlEncTripleDESKeyWrapUrl  
RSA XmlEncRSA1_5Url  

Before any data can be encrypted or decrypted, the encryption engine needs to know which key should be used for encryption and decryption. Keys can be identified in two ways, the easiest of which is to assign the key a name and place a KeyName element within the KeyInfo element. The application decrypting the document will be responsible for taking the KeyName tag and providing the key that matches the name given.

Instead of providing only the key's name, a key can also be embedded directly into the KeyInfo element as an EncryptedKey. EncryptedKey contains the same elements as EncryptedData: the encryption method, the key used to decrypt this key, and the cipher data that makes up the encrypted key.

Encrypted keys are often used in combination with named keys as random session keys. First a random session key is generated and used to encrypt the XML. Then the session key is itself encrypted with a named key that is known by anyone who needs to decrypt the document. Finally, the named key would be inserted into the key info element of the encrypted session key, and the encrypted session key attached to the encrypted data. The sample in Figure 9 shows this. The encryption method for the XML data is 256-bit AES, and the key for the AES algorithm has been encrypted. The EncryptedKey element contains information about how the key for the AES algorithm was encrypted. In this sample, it was encrypted using an RSA key named recipients_public_key. The applications using XML Encryption would have to map this name to an actual key.

The actual encrypted data may either be embedded into the EncryptedData element or placed in a separate location and then referenced from EncryptedData. If the cipher data is to be placed directly into EncryptedData, it is placed in a CipherData element as Base64-encoded binary. The example shown in Figure 9 uses a CipherData element.

Another scenario involves placing the encrypted data outside the EncryptedData element. The cipher text might be placed anywhere from another element in the document to a remote Web site. In both cases, a CipherReference is used instead of a CipherData element (see Figure 11).

Figure 11 CipherReferences

CipherReference Location URI Format Cipher Text Format
Same document #order Base64 string
Remote Web site https://www.example.com/order.bin Binary

CipherReferences to remote Web sites present an interesting security scenario. In the process of decrypting the XML, the decryption engine will have to go to an arbitrary Web site and download the cipher text. Since the document being decrypted may not be trusted as much as the code that's doing the decryption, this operation should be done in a sandbox. This is accomplished by providing evidence to the EncryptedXml object that it will use when resolving any CipherReferences.

Sandboxing allows for safer code execution since the decrypting application may not have the same permissions as the site that supplies the encrypted data. For instance, if an application attempts to decrypt an untrusted site, and the untrusted site does not have access to some trusted data on a secure, trusted site, it could cause the decrypting application to access that file for it by including a cipher reference.

Since the .NET security policy revolves around evidence, generally the evidence supplied should include at least Site, Zone, and Url objects, as shown here:

// Create evidence based on the referring document
Evidence evidence = new Evidence();
evidence.AddHost(new Zone(SecurityZone.Internet));
evidence.AddHost(new Site("<em xmlns="https://www.w3.org/1999/xhtml">untrustedsite</em>"));
evidence.AddHost(new Url("<em xmlns="https://www.w3.org/1999/xhtml">untrustedsite</em>/encrypted.xml"));

EncryptedXml exml = new EncryptedXml(untrustedDoc, evidence);

XML Encryption Example

Now let's look at how to use the classes in .NET Framework 2.0. This example shows a Web site that sells CDs. Each purchase is filed as an XML document that contains details on what was ordered, shipping information, and a credit card number. Figure 12 shows some sample XML for an order.

Figure 12 XML of a CD Order

<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>One Microsoft Way</street>
     <zip>98052</zip>
   </shipping>
   <payment>
     <card type="visa">0000-0000-0000-0000</card>
   </payment>
</order>

In this instance, it is acceptable for anyone inside the company to see the items in the order, but you might want to secure some of the more sensitive data such as the shipping address and credit card information. In fact, you might even want to encrypt them separately, so that only the billing department has access to the credit card information, and only the shipping department has access to the shipping address.

To accomplish this, the portion of the XML under the payment element would be encrypted so that only the billing department has access to it. A separate key that is only available to the shipping department would be used to encrypt the shipping element. Finally, the entire order would be encrypted with a key available to anyone in the company.

The first step is to create an EncryptedXml object. This is done by passing in the document that has the data to be encrypted or decrypted, as shown here:

// Assumes the order is in the order.xml file.
XmlDocument doc = new XmlDocument();
doc.Load("order.xml");
EncryptedXml exml = new EncryptedXml(doc);
 

Before the XML can be encrypted, the keys to be used have to be mapped to their corresponding names. These are the names that will appear in KeyName elements. You use the AddKeyMapping method of the EncryptedXml for this:

// Set up the key mapping. Assumes a method called GetBillingKey
// that returns the RSA key for the billing department.
RSA billingKey = GetBillingKey();
exml.AddKeyNameMapping("billing", billingKey);

Once the key-to-name mapping is set up, encrypting the XML is easy. The first step is to call the Encrypt method, which does the actual encryption and returns an EncryptedData object representing the encrypted portion of the document. After that, you call a utility method to swap the unencrypted portion of the original XML document for the new encrypted data:

// Find the element to encrypt.
XmlElement paymentElement = 
  doc.SelectSingleNode("//order/payment") as XmlElement;
// Encrypt the payment element, passing in the key name.
EncryptedData encryptedPayment = 
  exml.Encrypt(paymentElement, "billing");
// Swap the encrypted element for the unencrypted element.
EncryptedXml.ReplaceElement(paymentElement, encryptedPayment, true);

This will result in the encrypted XML shown in Figure 13.

Figure 13 CD Order with Payment Information Encrypted

<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>One Microsoft Way</street> 
    <zip>98052</zip> 
  </shipping>
    <payment>
      <EncryptedData Type="https://www.w3.org/2001/04/xmlenc#Element" 
           xmlns="https://www.w3.org/2001/04/xmlenc#">
        <EncryptionMethod Algorithm="https://www.w3.org/2001/04/
             xmlenc#aes256-cbc" />
        <KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
          <EncryptedKey xmlns="https://www.w3.org/2001/04/xmlenc#">
            <EncryptionMethod Algorithm="https://www.w3.org/2001/04/
                 xmlenc#kw-aes256" />
            <KeyInfo xmlns="https://www.w3.org/2000/09/xmldsig#">
              <KeyName>billing</KeyName>
            </KeyInfo>
            <CipherData>
              <CipherValue>Sce6lLD+u2f8HzPFyuGxTF32z4mb2ugql3JuJIPAqIP98iYs+Muhqg==
              </CipherValue>
            </CipherData>
          </EncryptedKey>
        </KeyInfo>
        <CipherData>
          <CipherValue>FXKC(3 lines of Base64 text)ApqQt</CipherValue>
        </CipherData>
      </EncryptedData>
    </payment>
</order>

Looking at the encrypted data, you can see the various parts that we've described. First, the EncryptionMethod element shows the URI for AES-256, meaning that the document was encrypted with the AES algorithm (as implemented by the RijndaelManaged class) with a 256-bit key.

The Encrypt method generates a random session key for you; this key is encrypted in the KeyInfo element. From looking at the EncryptedKey element you can see that it was encrypted with the RSA algorithm by a key named "billing." The CipherData element holds the encrypted value of the key. Following the KeyInfo is the CipherData that contains the encrypted contents of what used to be the payment element.

Decrypting the document just shown is easy, thanks to the DecryptDocument method of EncryptedXml. First, you load the document with encrypted content:

// Assumes the encrypted order is in encrypted.xml
XmlDocument doc = new XmlDocument("encrypted.xml");
EncryptedXml exml = new EncryptedXml(doc, documentEvidence);

Next, you set up the key name mappings, like this:

// Set up the key mapping. Assumes a method called GetBillingKey
// that returns the RSA key for the billing department.
RSA billingKey = GetBillingKey();
exml.AddKeyNameMapping("billing", billingKey);

Finally, you call DecryptDocument. This method will take care of decrypting the cipher text and replacing the encrypted XML with its decrypted contents.

// Decrypt the encrypted XML in the document
exml.DecryptDocument();

Behind the scenes, the encryption engine will look for any EncryptedData elements when DecryptDocument is called. In this case, it finds only the one below the payment element. Once it finds this element, it will look at the KeyInfo clause and see that it holds an encrypted 256-bit AES key. The KeyInfo for the encrypted key will show that it was encrypted with an RSA key named billing. It then checks the key mapping table for a key named billing. We added a key mapping for a key named billing, so the engine then decrypts the encrypted key. Now that it has the key, the engine decrypts the CipherData. The results of the decrypted CipherData are then swapped for the EncryptedData element. The engine will follow this process for each EncryptedData element it finds in the document when DecryptDocument is called.

X.509 Support

One of the challenges in security is trust. Securely distributing and storing trusted public keys on a network is not easy. Windows® provides lots of infrastructure for solving this problem, however. The Cryptographic API (CAPI) provides support for distributing, manipulating, and storing X.509 certificates. The support that CAPI provides is also known as Public Key Infrastructure (PKI).

An X.509 certificate provides additional information about a key. Certificates specify who issued the key, to whom the key was issued, and when the certificate is valid, along with the actual key information. Certificates are issued by Certificate Authorities (CAs). A CA is an entity (an external company or your company's IT department, for example) that you trust to vouch for the identity of a certificate holder. If you have a valid certificate containing your billing department's public key that is issued by a CA you trust, you can be sure the public key is really the billing department's.

The Windows PKI support allows you to securely store certificates in a certificate store on a computer, add and remove certificates in certificate stores on machines across the network, add and remove trusted CAs on machines across the network, and obtain and verify information about individual certificates, among other things. This allows you, for example, to distribute the certificate containing the public key for your company's billing department to all the computers on your company's network. In the .NET Framework 1.x, you had to call unmanaged APIs to take advantage of most of this support. In the .NET Framework 2.0, most of these APIs are available in managed code, via the X509CertificateEx class and related classes. The XML Signature classes support the X509CertificateEx class directly.

To create an XML signature with a certificate, you simply get the private key from the certificate's PrivateKey property and use it for the SignedXml object's signing key.

// Use the private key from the certificate. Assumes a SignedXml
// object in sig and an X509CertificateEx object in cert.
sig.SigningKey = cert.PrivateKey;

Of course, the certificate must have a private key associated with it, or the PrivateKey property returns null.

You can also add the X.509 certificate information to the KeyInfo element of the signature by using the KeyInfoX509Data class, as shown in the following code:

// Add X.509 certificate info to the KeyInfo element. Assumes a
// SignedXml object in sig and an X509CertificateEx in cert.
KeyInfoX509Data keyInfoX509 = 
  new KeyInfoX509Data(cert, X509IncludeOption.EndCertOnly);
sig.KeyInfo.AddClause(keyInfoX509);

To verify an XML signature signed with a certificate's private key, you can call a new overload of the CheckSignature method of the SignedXml class. This new overload takes an X509CertificateEx object and a Boolean. If the Boolean is set to true, the method will verify the signature against the public key in the certificate and verify the certificate by checking key usage and building a chain to a trusted root issuer.

// Check the signature against the cert and verify the cert. Assumes a
// SignedXml object in sig and an X509CertificateEx object in cert.
bool verified = sig.CheckSignature(cert, true);

You can also use the CheckSignature method that takes no parameters to verify a signature, but it will not also verify an X.509 certificate if one was used to sign the XML. You would have to recreate the X509CertificateEx object from the KeyInfo and verify it as a separate step.

For more information about creating X.509 certificates for testing purposes, see the Certificate Creation Tool (Makecert.exe) topic in the .NET Framework SDK documentation.

The .NET Framework 2.0 adds a few new transforms and canonicalization algorithms to the signature engine. These include the Exclusive C14N canonicalization algorithm, XML Decryption transform, and the LTA transform. Figure 14 contains a complete table of the transforms and canonicalization algorithms available in the .NET Framework 2.0 signature engine.

Figure 14 Transforms and Canonicalization Algorithms

Class Description
XmlDecryptionTransform Decrypts encrypted XML
XmlDsigBase64Transform Decodes base64 encoded data
XmlDsigC14NTransform Performs C14N canonicalization (see https://www.w3.org/TR/xml-c14n for more information)
XmlDsigEnvelopedSignatureTransform Removes an enveloped signature from a document
XmlDsigExcC14NTransform Performs exclusive C14N canonicalization (see https://www.w3.org/TR/2002/REC-xml-exc-c14n-20020718 for more information)
XmlDsigXPathTransform Applies an XPath filter to the input XML
XmlDsigXsltTransform Applies an XSLT transform to the input XML
XmlLicenseTransform Implements the LTA transform
XmlDsigC14NWithCommentsTransform Performs C14N canonicalization, but leaves comments in the canonicalized XML
XmlDsigExcC14NWithCommentsTransform Performs exclusive C14N canonicalization, but leaves comments in the canonicalized XML

Conclusion

We've discussed the basics of the XML Signature standard and how it is implemented in the .NET Framework 1.x. We've also covered some new features in the .NET Framework 2.0, including support for the XML Encryption standard and X.509 certificate integration for XML signing. These building-block technologies allow you to interoperate with other applications using the standards and build standards support into your own applications.

Mike Downen is a program manager on the CLR security team at Microsoft, working on code access security, cryptography, and the new ClickOnce security model. You can reach Mike at mdownen@microsoft.com.

Shawn Farkas is a software design engineer in test on the CLR security team at Microsoft. He works on ClickOnce, cryptography, and IL verification. See his blog at https://blogs.msdn.com/shawnfa.