Reliable Message Exchange Using XML Signing

Security Center Introduction

A well known service oriented scenario is where a client and service application communicate using message exchange over a channel. This channel is either secured, using for instance SSL, or unsecured where the data is send in plain text.

One of the commonly used message bases is XML. XML is a standard way of describing relational data, and can be read by most platforms. This makes XML a interoperable message base.

On the downside, this message type is easy to read and it therefore easy to change. If a third party where to intercept the message, for instance using a proxy server, it could easily change the content of the message and send it to the original endpoint. This is called tampering.

To detect changes to the content, a message send through the channel can be signed. Signing is a way to provide reliable messaging over a non-secure channel. It also provides some sort of authentication because the message can be validate that is was send from the expected client.

Principle of signing

A digital signature on a message is like a footprint of that message. Before the message is send, the hash is calculated and encrypted using a key. This key can be a custom key, a fixed key from a key store, or the key from a certificate. On the receiving endpoint, the supplied signature can be validates against the newly calculated value. In any scenario, the sender and receiver should share some sort of key infrastructure to sing and validate the messages.

The following image gives a overview of the signing and validation of a message using the private and public key of a shared certificate.

Security - Signing 

So let’s put this theory to practice using the .NET Framework

Signing using the .NET Framework

The Setup

To get started, i have a small XML file representing the message to be transferred over the channel. Nothing fancy, just to show what the results will be.

  1: <?xml version="1.0" encoding="utf-8" ?>
  2: <Person>
  3:   <Firstname>John</Firstname>
  4:   <Lastname>Doe</Lastname>
  5: </Person>

Second, i have imported a certificate into the certificate store. The certificate contains a private and public key and will be used for signing and verification purpose. Normally you would use a certificate with private key on the sending endpoint and import the same certificate on the receiving side containing only the public key.

To demonstrate the signing and validation, I'll guide you trough the code required using a console application. Of course in a OOAD pattern, this logic could be in a separate logic library like a utilities library.

Disclaimer:
I did not take all the time to insert nice error handing code so don’t comment me on that. Always implement good exception handling.

Retrieving the certificate

I’m retrieving the certificate from the certificate store using the unique thumbprint. The .NET library has evolved nicely in the part, because it now supports a easy way to do this exact thing.

  1: X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
  2: store.Open(OpenFlags.ReadOnly);
  3: 
  4: X509Certificate2 Cert = store.Certificates.Find(
  5:     X509FindType.FindByThumbprint,
  6:     "e5 d9 de 0f ed 08 34 09 c2 83 0f f2 11 4a e9 a0 b0 7b 86 9f",
  7:     false)[0];
  8: 
  9: // Generate a signing key.
 10: RSACryptoServiceProvider privateKey = Cert.PrivateKey as RSACryptoServiceProvider;

The false bool in the Find method indicated if i only want to use valid certificates. As this is not important in the example i set this to false.

Signing

To sign the XML file, i created a method that takes the filename of the original message, the name of the resulting message, and the key to use for the signing.

  1: // Will sign the XML file using the key proivided and saves it.
  2: SignXmlFile("MySecureDocument.xml", "MySecureDocument-Signed.xml", privateKey);

The first thing we will do is read the XML file into a XmlDocument and create a SignedXml object referencing the message. We then set the key to the SigningKey property.

  1: // Create a new XML document.
  2: XmlDocument doc = new XmlDocument();
  3: 
  4: // Load the passed XML file using its name.
  5: doc.Load(new XmlTextReader(FileName));
  6: 
  7: // Create a SignedXml object.
  8: SignedXml signedXml = new SignedXml(doc);
  9: 
 10: // Add the key to the SignedXml document. 
 11: signedXml.SigningKey = Key;

The SignedXml object is kind of a misleading name, as it does not contain the signed XML but only the signature informational part. In my opinion, a more logical name would be XmlSignature. The SignedXml will be added to the XML message later.

Next, we need to add a Reference object to the SignedXml object. This tells the signing algorithm what part of the message to use to calculate the signature. It also contains a signature transform property that described the way the signature is provided.

  1: // Create a reference to be signed.  Pass "" 
  2: // to specify that all of the current XML
  3: // document should be signed.
  4: Reference reference = new Reference();
  5: reference.Uri = "";
  6: 
  7: // Add an enveloped transformation to the reference.
  8: XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
  9: reference.AddTransform(env);
 10: 
 11: // Add the reference to the SignedXml object.
 12: signedXml.AddReference(reference);

After the signing algorithm is setup we can start calculating the signature and assign it to the XML message. I say we as in code. .NET Framework does all the calculating for us.

  1: // Compute the signature.
  2: signedXml.ComputeSignature();
  3: 
  4: // Get the XML representation of the signature and save
  5: // it to an XmlElement object.
  6: XmlElement xmlDigitalSignature = signedXml.GetXml();
  7: 
  8: // Append the element to the XML document.
  9: doc.DocumentElement.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
 10: 
 11: // Save the signed XML document
 12: XmlTextWriter xmltw = new XmlTextWriter(SignedFileName, new UTF8Encoding(false));
 13: doc.WriteTo(xmltw);
 14: xmltw.Close();

The resulting XML file will contain a additional Signature node containing all the information to be validated.

  1: <Person>
  2:   <Firstname>John</Firstname>
  3:   <Lastname>Doe</Lastname>
  4:   <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
  5:     <SignedInfo>
  6:       <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />
  7:       <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
  8:       <Reference URI="">
  9:         <Transforms>
 10:           <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
 11:         </Transforms>
 12:         <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
 13:         <DigestValue>haEx+J1wgjeVDYyfdqNr2vdJypE=</DigestValue>
 14:       </Reference>
 15:     </SignedInfo>
 16:     <SignatureValue>PXh7px6IXOVh0ilFbIKhVqBupNIP3He...</SignatureValue>
 17:   </Signature>
 18: </Person>

Validation

Validation is more straight forward. As described in the introduction in our example we validate the signature using the public key of the shared certificate. We can retrieve this again from the certificate store.

  1: // This time we use the public key
  2: RSACryptoServiceProvider publicKey = Cert.PublicKey.Key as RSACryptoServiceProvider;

After this we simulate receiving the message by reading the signed file from disk and assigning it to the SignedXml object.

  1: // Create a new XML document.
  2: XmlDocument xmlDocument = new XmlDocument();
  3: 
  4: // Load the passed XML file into the document. 
  5: xmlDocument.Load(Name);
  6: 
  7: // Create a new SignedXml object and pass it
  8: // the XML document class.
  9: SignedXml signedXml = new SignedXml(xmlDocument);

Then we get the signature node and validate the signature.

  1: // Find the "Signature" node and create a newXmlNodeList object.
  2: XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
  3: 
  4: // Load the signature node.
  5: signedXml.LoadXml((XmlElement)nodeList[0]);
  6: 
  7: // Check the signature and return the result.
  8: return signedXml.CheckSignature(Key);

And that’s it. Your all done. The bool will tell you if the signature was OK.

Summary

To prevent tampering of data, non-repudiation and to provide extra authentication it is possible to sign your messages. Of course this involves some extra overhead and loss of performance, depending on the size of the document and to overcome this, hardware signing is a possibility.

Keep in mind that you are still sending readable, none encrypted data over the channel if you are not using SSL so it will not prevent someone else from reading it until you do.

Hope you enjoyed this post.

Regards,
Martijn.

Comments

Popular posts from this blog

Yet Another WiX Tutorial Part 3: Customizing the UI dialogs

Encrypted Cookies using ASP.NET

Yet Another WiX Tutorial Part 2: Your First Installer