| jclement.ca · Signing an XML Document | [ Home · Photoblog (Flickr) · Software · Twitter · Search Site ] |
Here is a quick way to sign an Xml document. Usually the app would only contain the public key and whatever generated the xmldoc would have the private key. That is easy enough to do though (here).
Microsoft provides a SignedXml object in System.Security.Cryptography.Xml (need to add a reference). Basically you provide an Xml document and key and it can attach a signature to the document which you can later strip off and use to verify the document. The really cool thing is that the signed document is still valid Xml so if you have a parser that doesn't want to check the signature, or can't, it doesn't have to.
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;
namespace ConsoleApplication1
{
/// <summary>
/// Quick example of signing XML Files
/// </summary>
class Class1
{
/// <summary>
/// Build a simple XML Demo Docuement to play with.
/// </summary>
/// <returns>XmlDocument with a name and expiry field</returns>
private static XmlDocument BuildTestDocument()
{
XmlDocument xmldoc = new XmlDocument();
XmlNode node;
node = xmldoc.CreateNode(XmlNodeType.Element, "license", "testuri");
xmldoc.AppendChild(node);
node = xmldoc.CreateElement("name");
node.InnerText = "Jeff";
xmldoc.DocumentElement.AppendChild(node);
node = xmldoc.CreateElement("expires");
node.InnerText = "Tomorrow";
xmldoc.DocumentElement.AppendChild(node);
System.Console.Error.WriteLine("Data to sign:\n" + xmldoc.OuterXml + "\n");
return xmldoc;
}
/// <summary>
/// Attach a signature to the XmlDocument.
/// </summary>
/// <param name="key">RSA Private key used for signing</param>
/// <param name="doc">XMLDocument to sign</param>
private static void SignXmlDocument(RSA key, XmlDocument doc)
{
SignedXml sxml = new SignedXml(doc);
sxml.SigningKey = key;
sxml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl;
// Add reference to XML data
Reference r = new Reference("");
r.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));
sxml.AddReference(r);
// Build signature
sxml.ComputeSignature();
// Attach signature to XML Document
XmlElement sig = sxml.GetXml();
doc.DocumentElement.AppendChild(sig);
}
/// <summary>
/// Verify the signature attached to a document. Or return false if no signature
/// </summary>
/// <param name="key">RSA public key for verification</param>
/// <param name="doc">Document to Verify (should have attached sig)</param>
/// <returns>Whether or not document contains a valid signature.</returns>
private static bool VerifyXmlDocument(RSA key, XmlDocument doc)
{
SignedXml sxml = new SignedXml(doc);
try
{
// Find signature node
XmlNode sig = doc.GetElementsByTagName("Signature", SignedXml.XmlDsigNamespaceUrl)[0];
sxml.LoadXml((XmlElement) sig);
}
catch
{
// Not signed!
return false;
}
return sxml.CheckSignature(key);
}
[STAThread]
static void Main(string[] args)
{
/* My RSA keys - usually pub/priv is not in same program */
RSA r = RSA.Create();
/* Build a sample XML Document and sign it*/
XmlDocument doc = BuildTestDocument();
SignXmlDocument(r, doc);
/* Verify the signature on our XML Document is good */
if (VerifyXmlDocument(r, doc))
{
System.Console.WriteLine("OK");
}
else
{
System.Console.WriteLine("BAD SIGNATURE");
}
/* Tamper with the data */
XmlNode node = doc.GetElementsByTagName("name")[0];
node.InnerText = "Joe";
/* Now the signature check should fail */
if (VerifyXmlDocument(r, doc))
{
System.Console.WriteLine("OK");
}
else
{
System.Console.WriteLine("BAD SIGNATURE");
}
System.Console.ReadLine();
}
}
}