More Secure XML Digital Signatures

I've gotten some comments about my XML Digital Signatures entry, pointing out that since I chose to embed the signing key into the document, nothing is preventing anyone from simply removing the signature, modifying the document, and then resigning with their own key. This is true; in order to effectively use the signature from the previous article, it must be combined with another security mechanism. For instance, if the key could be checked against a known database of public keys, or validated as a key that came from a certificate that belongs to someone you trust, the previous signature would still be secure. If the entire document was encrypted (including the signature), using techniques from the XML Encryption posting, this would also make the signature secure.

However, in the absense of these items, you must distribute the key seperate from the signed documents. When signing the document, you would not add a key info clause, or would only add a key name to this clause, in case there are several keys that are distributed, and you need to be able to select the correct one. On the verification side, you would use the CheckSignature overload that takes the public key. Now the only challenge is to find a secure way to get the people validating your signature a copy of your public key.

Here's some sample code to show how the signature might be computed:

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

// setup the key for signing
RSA signingKey = new RSACryptoServiceProvider();
using(StreamWriter writer = new StreamWriter("signing-key.xml"))
    writer.WriteLine(signingKey.ToXmlString(false));

// now sign the document
SignedXml signer = new SignedXml(doc);
signer.SigningKey = signingKey;

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

// 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");

You'll notice the block of code:

// setup the key for signing
RSA signingKey = new RSACryptoServiceProvider();
using(StreamWriter writer = new StreamWriter("signing-key.xml"))
    writer.WriteLine(signingKey.ToXmlString(false));

Which exports the public key used for signing to an XML file. This file can then be distributed to anyone who needs to verify signatures made with the signingKey object.

To verify the signature:

XmlDocument doc = new XmlDocument();

doc.Load("order-signed.xml");

// read in the public key

RSA signingKey = new RSACryptoServiceProvider();

using(StreamReader reader = new StreamReader("signing-key.xml"))

    signingKey.FromXmlString(reader.ReadLine());

SignedXml verifier = new SignedXml(doc);

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

if(verifier.CheckSignature(signingKey))

    Console.WriteLine("Signature verified");

else

    Console.WriteLine("Signature not valid");

This is very similar to the previous verification code, except the public key is loaded from the XML file created by the signing step, and then passed to the CheckSignature method.