Using the XSLT Transform with XML Signatures

One of the transforms that ships with the .Net framework is the XmlDsigXsltTransform, which implements the XSLT transform specified in the W3C recommendation. A few people have asked me to write a bit on how to use this transform, so here's a brief explanation and some sample code. This transform basically applies an XSL transform to the XML that will be signed, before the signature is computed, but after the reference has been resolved.

To clarify that last point, lets say I want to sign some XML with an id of "myxml". Currently no elements in the XML I want to sign have this ID, but I do have some XSL that will transform the input XML into some output that contains a node with the myxml id. This won't work, because the Reference will search my document for the element with the myxml id before the transform is applied. Since it won't find it, the transform will never be applied. (Something like the above scenario could be accomplished by creating a reference to the entire document, then applying a transform chain that first included the XSLT transform followed by an XPath one.)

One of the quirks of the XmlDsigXsltTransform class is that there is no easy way to programmatically set the XSL you'd like the transform to use. In order to create XSLT transforms, I use the following utility function, which takes as input the XSL you'd like the transform to apply.

/// <summary>
/// Utility method to create an XSLT transform from a string of XSL
/// </summary>
/// <param name='xsl'>XSL to use in the transform</param>
/// <returns>An XmlDsigXsltTransform that will apply the given XSL transform before signing</returns>
public static XmlDsigXsltTransform CreateXsltTransform(string xsl)
{
    XmlDocument doc = new XmlDocument();
    doc.LoadXml(xsl);

    XmlDsigXsltTransform xform = new XmlDsigXsltTransform();
    xform.LoadInnerXml(doc.ChildNodes);
    
    return xform;
}

This method is easy to use. Simply pass it the XSL you want in your transform, and it will return you an XmlDsigXsltTransform object containing this XSL. It works by turning the XSL string into an XmlNodeList, and then calling LoadInnerXml on XmlDsigXsltTranform with this node list.

Using the transform is done in the same manner as the other transforms. First you create a reference to the XML you wish to sign. Then attach the transform object to that reference's transform chain. Verification code is unchanged from the standard verification code, since the transform will be embedded in the signature that is produced. Here's some sample code that signs a simple XML document using a basic XSL transform

// transform that will be used before signing the document

string xsl = @"

    <xs:transform xmlns:xs='https://www.w3.org/1999/XSL/Transform' version='1.0'>

        <xs:template match='/'>

            <xs:apply-templates/>

        </xs:template>

        <xs:template match='elementToTransform'>

            <transformedElement/>

        </xs:template>

    </xs:transform>";

// XML document to sign
string xml = @"

    <xml>

        <elementToTranform/>

    </xml>";
XmlDocument doc = new XmlDocument();
doc.LoadXml(xml);

// setup the signature as usual -- generally you'd have a known key
// to sign with instead of producing a random one.
RSA signingKey = new RSACryptoServiceProvider();
SignedXml signer = new SignedXml(doc);
signer.SigningKey = signingKey;

// create a reference to the entire document
Reference r = new Reference("");
r.AddTransform(new XmlDsigEnvelopedSignatureTransform());
r.AddTransform(CreateXsltTransform(xsl));
signer.AddReference(r);

// signature computation works as usual
signer.ComputeSignature();
doc.DocumentElement.AppendChild(signer.GetXml());

After running this code, the signed XML looks like this:

<xml>
  <elementToTransform />
  <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" />
          <Transform Algorithm="https://www.w3.org/TR/1999/REC-xslt-19991116">
            <xs:transform xmlns:xs="https://www.w3.org/1999/XSL/Transform" version="1.0">
              <xs:template match="/">
                <xs:apply-templates />
              </xs:template>
            <xs:template match="elementToTransform">
                <transformedElement xmlns="" />
              </xs:template>
            </xs:transform>
          </Transform>
        </Transforms>
        <DigestMethod Algorithm="https://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>lqgLQKa0...e66pmKI=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>juQQvRyG...E7r1Y0E=</SignatureValue>
  </Signature>
</xml>

One thing to notice here is that the output XML contains the original XML, not the version that would be produced when transformed by the XSL. Since the output also contains your XSL, whoever is verifying the signature can recompute the output. This is convenient, sine it means that your XSL doesn't have to produce XML (for instance, it could create HTML). However, if you don't want to share your XSL, then this may not be the transform for you. Instead, you might want to apply your XSL before computing the signature, in which case the XSL you use will not be available for anyone to see.