Maintaining the integrity of your binaries is important.  After all, we've seen how simple it is for someone to crack open your assemblies and start muddling around, making your binaries do terrible ... unspeakable things.  Strong names help in this regard, as they are treated as a form of load-time validation.  But what about your application configuration file?  It's just sitting there, plain ol' XML, just begging to be tampered with.  Obviously we don't want to store any super-secrets in the app.config (and if we do, we certainly want to encrypt them), but there are times when the data in our app.config dramatically affects program behavior, and we just want to make sure the app.config can be trusted before we make any drastic decisions.

It is surprising to me that the .NET framework doesn't provide canned support for such app.config validation, especialy when there is a readily accessible solution...

XML Digital Signatures

The .NET framework provides a limited implementation of the XMLDSIG  (XML Digital Signature) specification.  In a nutshell, XMLDSIG specifies a procedure for calculating the digital signature of an XML node tree, and also provisions different ways of including the signature either with or external to the XML node tree.  The process can be summed up as follows:

  1. An XML node set is selected for signing.
  2. The XML node set is then canonicalized, which is a fancy way of saying that the node set is put into a standard representation.  For example, redundant XML namespace references are removed, and attributes are placed in alphabetic order.
  3. The canonical XML representation is then hashed using a standard hashing algorithm like SHA1 or MD5.
  4. The hash is signed using standard private/public key cryptographic techniques, resulting in the XML digital signature of the node set.
  5. The digital signature is either embedded in the document as a new XML element, or left out-of-band as a detached signature.  The signature can be validated by repeating the hash calculation process and comparing the result against the decrypted signature value.  If the node set is altered in any way, the hash will vary from the signature and anyone can figure out that the XML has been altered.

There are some big words and concepts in there, but taken in small bites none of it is very difficult to digest; it doesn't matter anyway because the .NET Framework does most of the heavy lifting for us.

Applying XMLDSIG to App.Config Files

Since app.configs are just XML files, XMLDSIG seems like a good choice for ensuring the integrity of those files.  Here are the things we need to accomplish:

  1. massage our app.config file to be able to contain the XML digital signature without breaking the standard .NET configuration subsystem;
  2. finalize our app.config contents;
  3. generate the XML digital signature of our app.config and embed it in the app.config file;
  4. have our program validate the app.config digital signature before it attempts to load any configuration values;

Since any one of these three things can be a post unto itself, I'm going to focus on the how.  If there is enough interest, I can elaborate on some of the whys in future posts.  If you just can't wait, Rick Strahl has a good example available, and InfoMosaic has a nice backgrounder

Let's get started.

Preparing the App.Config for the XMLDSIG Signature Element 

When an XML document contains its own digital signature, the signature is described as "enveloped," meaning that the document element surrounds the signature XML like an envelope surrounds a letter.  After our app config is signed, the root <configuration> element will contain a new child element; it will look something like this (all detail removed for brevity):

 

<configuration>   ...   <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">     <SignedInfo>       <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" />       <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />       <Reference URI="">...</Reference>     </SignedInfo>     <SignatureValue>...</SignatureValue>     <KeyInfo>...</KeyInfo>   </Signature> </configuration>

 

We can't just plop this new element in the app.config and expect the .NET configuration manager to process it without knowing what it is; this will cause failure during application startup.  No special tricks here, we simply need to instruct the configuration system to ignore this element by adding the following to the top of the config file:

 

<configSections>     <section name="Signature" type="System.Configuration.IgnoreSectionHandler" />     </configSections> ...

The app.config is almost ready to accept the XML digital signature...

Finalizing the App.Config Contents

Once we apply the signature to the app.config, we won't be able to alter the app.config contents without invalidating the signature.  For our example, we need to make sure our application settings are set properly:

 

<configuration>     <configSections>         <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">             <section name="XMLDSIGValidate.Settings1" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />         </sectionGroup>         <section name="Signature" type="System.Configuration.IgnoreSectionHandler" />     </configSections>     <applicationSettings>         <XMLDSIGValidate.Settings1>             <setting name="ReallyImportantSetting" serializeAs="String">                 <value>if this value is altered, the application should not run</value>             </setting>         </XMLDSIGValidate.Settings1>     </applicationSettings> </configuration>

The only setting in this mess is on lines 12-13.  I set the value to something obnoxious, then move on to signing the file.

Generating the XML Digital Signature

There is no tool in the .NET SDK for applying XMLDSIG to a file, so we need to create one.  Here's the code, explanation of important stuff follows:

using System; using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography;
using System.IO;
namespace AppConfigSigner
{
    class Program
    {
        static void Main( string[] args )
        {
            // load the app config as an XmlDocument
            XmlDocument config = new XmlDocument();
            config.Load( args[ 0 ] );

            /* build up the XMLDSIG processor
             *
             * we'll use the following elements in our signature:
             *
             * - an enveloped signature transform; this will remove the
             *      actual Signature element from the document tree during
             *      XMLDSIG validation
             * - an empty Reference URI; this will indicate that the signature is applied
             *      to the entire containing document
             * - a default RSA signature key pair
             */
            XmlDsigEnvelopedSignatureTransform transform = new XmlDsigEnvelopedSignatureTransform();
            Reference reference = new Reference();
            reference.Uri = "";
            reference.AddTransform( transform );
            
            // create the XMLDSIG processor
            SignedXml xmldsig = new SignedXml( config );
            xmldsig.AddReference( reference );
            
            // set the signature key
            xmldsig.SigningKey = new RSACryptoServiceProvider();
            
            /* configure the key info elements of the signature;
             * this will instruct the XMLSIG validator on how to
             * obtain the public key to validate the signature.
             *
             * in our case, we're simply dumping the public key
             * right into the Signature element.  in production
             * this is a REALLY BAD idea, but it keeps the example
             * simple
             */
            xmldsig.KeyInfo = new KeyInfo();
            xmldsig.KeyInfo.AddClause(
                new RSAKeyValue( ( RSA )xmldsig.SigningKey )
            );
            
            xmldsig.ComputeSignature();
            
            XmlElement signature = xmldsig.GetXml();
            XmlNode signatureNode = config.ImportNode( signature, true );
            config.DocumentElement.AppendChild( signatureNode );
            
            /* save the config file
             *
             * note that we need to retain control over the file encoding
             * and the XML formatting to keep the signature valid.
             *
             * if you try this:
             *
             * config.Save( args[ 0 ] );
             *
             * you may never get a valid signature because the XmlDocument class
             * will pretty-up the XML with whitespace that wasn't there during
             * signature generation
             */
            using( FileStream fs = File.OpenWrite( args[ 0 ] ) )
            {
                using( XmlTextWriter writer = new XmlTextWriter( fs, Encoding.UTF8 ) )
                {
                    config.WriteTo( writer );
                }
            }
        }
    }
} 

The program does three things:

  1. Load an XML file specified on the command line;
  2. Sign the XML;
  3. Save the XML file back to its original location.

To calculate the XML digital signature, we need to specify few items; minimally:

  • what XML is being signed - this is called a Reference in XMLDSIG speak;
  • the private/public key pair to use for signing operations;

In addition, we may also want to include information that the signature validator can use to find the appropriate public key during signature validation.  This is referred to as the XML digital signature KeyInfo, or sometimes the KeyInfo clause.

Creating the Signature 

The signature generation begins on line 30 with the creation of our Reference.  The XMLDSIG spec allows an XML digital signature to apply to all or part of an XML node set; the reference object is the way we identify what XML is being signed.  By supplying an empty URI value on line 32, we are indicating that the signature applies to the containing XML document.

On line 36, the SignedXml instance is created.  This is the class that will do the XMLDSIG heavy lifting.  Line 37 adds our Reference to the XMLDSIG processor.

Line 40 supplies the XMLDSIG processor with the signature key pair.  In this example, I'm using a randomly generated key set each time the config is signed; in production, you will likely want to use a specific key set every time.  

Lines 52-55 add key information to the XML digital signature.  Whoever will be verifying the app.config integrity will need to know what public key will validate the XML digital signature.  There are a plethora of options as to how to communicate public key information between two parties, enough best practices to fill a book.  To keep things simple (vs. secure), line 53 merely dumps the entire public key BLOB right into the XML digial signature.  No prizes for realizing this is a VERY BAD IDEA; there should be a very formal mechanism of public key exchange between the app.config signer and the app.config validator.  That's another post, let's get this working for now.

Line 57 calculates the signature of the app.config, and 59-62 add the XML digital signature to the app.config. 

Save the XML Delicately

The rest of the program saves the signed app.config.  This can be tricky business, as ANY significant alteration of the XML will result in the signature becoming invalid.  What's a significant alteration you ask?  Well, the addition of any whitespace for one, element ordering, text node changes of course, or the encoding of the XML.  I find that you cannot simply call the Save method of the XmlDocument class; the resulting XML is somehow invalidated during the write due to the addition of whitespace.  Your mileage may vary; if you find a better way, let the world know.

Validating the App.Config Digital Signature

The application in question will need to validate the digital signature of its app.config before it attempts to load any configuration values.  If the app.config is validated against its signature, then the application can merrily load up any values it needs, fully trusting that the configuration file is in a proper state.  If the file fails validation, the application knows that the configuration file has been tampered with and cannot be trusted.

Let's look at a sample program: 

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography.Xml;
using System.Xml;
using System.Configuration;
using System.Security.Cryptography;

namespace My
{
    static class Program
    {
        static void Main(string[] args)
        {
            // discover the path to the app config
            string configFilePath = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.None ).FilePath;

            /*
            * load the app config as an XmlDocument
            *
            * it is vitally important to preserve any whitespace in
            * the original document, as the XMLDSIG spec defines ALL
            * whitespace as SIGNIFICANT
            */
            XmlDocument config = new XmlDocument();
            config.PreserveWhitespace = true;
            config.Load( configFilePath );

            // Create and initialize the XMLDSIG processor from our app config XmlDocument
            SignedXml xmldsig = new SignedXml( config );

            // find the XMLDSIG element
            XmlElement signature = ( XmlElement )config.GetElementsByTagName( "Signature" )[ 0 ];

            // load the XMLDSIG element into the XMLDSIG processor
            xmldsig.LoadXml( signature );

            // check the result of the signature
            bool result = xmldsig.CheckSignature();

            Console.WriteLine( result );
        }
    }
}

All this program does is validate the signature on its app.config and write the result of that validation to the console.

On line 26, the configuration XML document is told to preserve whitespace during load.  This is vital to proper XMLDSIG processing.  Normally the XmlDocument class will ignore "pretty-print" whitespace that exists between element nodes, but XMLDSIG considers this whitespace significant, so we need to make sure it gets into the XML node tree.

Line 30 initializes the XMLDSIG processor with the configuration XML document.  This is the document we need to validate.

Line 33 locates the XML digital signature element embedded in our app.config.  This element was added by our signer application.  The signature is fed to the XMLSDIG processor in line 36.

Line 39 is the money: the XMLDSIG processor is instructed to validate the app.config XML document against the digital signature by invoking the CheckSignature method.

The remainder of the program outputs the state of the app.config: True if the file is valid, False if it isn't. 

Do I Really Need to do This?

I tend to use DI/IoC a lot, and I tend to use one or more frameworks for configuring IoC containers, and they tend to like to read their configuration from my application configuration file, and I tend to wrinkle at the possibility of someone being able to modify my well-planned object graph with a little text file tinkering.  I've seen projects with configuration settings named "DisableSecurity" and "EnablePremiumFeatures" and yes the settings do exactly what you think they do.  In these cases I think this technique is valuable.

The question you need to ask yourself is: what could happen to my application if I can't trust my configuration file?  Consider the possibilities, and you'll have your answer.

Tune In 

I think this is a pretty kewl technique, if you think so too then grab the code and play around with the possibilities.  If there's interest and I get some time, I'd like to expand on some of the ideas started in this post.

XMLDSIGValidate.zip (8.43 kb)