Tuesday, September 27, 2011

Digitally Signing App.config


Ever thought of putting digital signatures on Application Configuration? Here is something we went through:


Scenario
We have some values in Application Configuration file which we do want user to play around with as some of them might lead to an alternate hack of the application. We need to ensure that App.config is not tampered when used by the application. First we thought of embedding the App.config file but this is not provisioned by CLR as the file is loaded during Application Startup, so if it’s missing then application will crash. We had no option but to digitally sign the App.config file and verify the signatures on Application load. 


What is the issue?
App.config file is not any other file in the application as we cannot embed it on application assembly. So, we are left with no option than to sign it digitally and check the signatures of config file on application load. Several issues are there:

  1. Config file cannot be signed using signtool, as this is an XML file.
  2. Application crashes if I just sign the config file as config file no more abide by the schema .net CRL expects.
  3. How to ensure the signature are not tampered.

How to resolve the issue?
As config file is nothing but an XML file, we will use XMLDSIG for signing the config file. We have some classes from the framework to support the same. So, before deploying the application we’ll sign the configuration file. 
The signing of the file, adds another section to the config file – “<Signature>”. But the application fails to load because of this, as config file no more abide by the schema that .net CRL expects. So, for resolving this we add a new section declaration in “<configSections>” element before signing the config file.


For signing the App.config we’ll use asymmetric keys. We’ll use pfx certificate file for the same. The process has two steps:

  1. Signing – For this we import the pfx file in Local Computer’s Personal certificate store and then use the private key of this certificate for signing the config file. 
  2. Verify – To verify the signatures we need the corresponding public key. So, we can embed public key in the assembly and retrieve that during application load and verify the signatures on the config file.

To simplify we need to do the following things in order to sign the config file:

  1. Import pfx certificate for signing the config file
  2. Add a new <section> item to <configSections> element
  3. Execute the code for signing the config file
  4. On application load retrieve the embedded public key (.cer file)
  5. Verify the signature on config file and return the result



Code for the solution

Import the pfx file in Local Computer’s Personal certificate store


Add the following section to the <configSections> element :


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



Sign the config file using the console application generated by compiling the following code:


namespace SigningAppConfigConsole
{
    using System;
    using System.Text;
    using System.IO;
    using System.Linq;
    using System.Xml;
    using System.Security.Cryptography.Xml;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Cryptography;

    class Program
    {
        private static void Main(string[] args)
        {
            // Expecting first argument as path of the config file
            string path = args[0];

            // Expecting second argument as certifacte's thumbprint which will be used for signing
            string thumbprint = args[1];

            // Sign the Xml File
            SignXml(path, thumbprint);
        }

        /// 
        /// Method for signing the xml file
        /// 
        /// Path of teh file</param>
        /// Thumbprint of the certificate which should be used for signing</param>
        private static void SignXml(string path, string thumbprint)
        {
            // Check if file exists
            if (File.Exists(path))
            {
                // Loading Applcaition Configuration file as an Xml Document
                var configurationFile = new XmlDocument { PreserveWhitespace = true };
                configurationFile.Load(path);

                // Adding the transform
                var transform = new XmlDsigEnvelopedSignatureTransform();
                var reference = new Reference { Uri = string.Empty };
                reference.AddTransform(transform);

                // Creating XMLDSIG processor  
                var xmldsig = new SignedXml(configurationFile);
                xmldsig.AddReference(reference);

                // Setting signature key  
                xmldsig.SigningKey = GetPrivateKey(thumbprint);

                // Computing the signature
                xmldsig.ComputeSignature();

                // Signature XML
                var signature = xmldsig.GetXml();

                // Node to be inserted in the configuration file
                var signatureNode = configurationFile.ImportNode(signature, true);

                // Adding node to teh configuration file
                configurationFile.DocumentElement.AppendChild(signatureNode);

                // Write back the configuraiton file
                GenerateFile(path, configurationFile.InnerXml);
            }
            else
            {
                Console.WriteLine("File does not exists.");
            }
        }

        /// 
        /// Method for getting the private key of the certificate
        /// 
        /// Thumbprint of the certificate whose private key is required</param>
        /// 
        private static RSACryptoServiceProvider GetPrivateKey(string thumbprint)
        {
            // Removing spaces if thumbrint is supplied with spaces
            thumbprint = thumbprint.Replace(" ", string.Empty).ToLower();

            // StroreName.My = Personal Store, StoreLocation.LocalMachine = Local Computer Stores
            var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);

            // Opening the certificate store
            store.Open(OpenFlags.ReadWrite);

            // Getting the cettificate with the mentioned thumbprint
            var x509Certificate2 = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(certificate => certificate.Thumbprint.ToLower().Equals(thumbprint));

            // If certificate is not found in the store then throw exception that no certificate with
            // the mentioned thumbrint does not exists
            if (x509Certificate2 == null)
                throw new Exception("No Certificate exists with the thumbprint : " + thumbprint);

            // Returing the private key of the certificate
            if (x509Certificate2.HasPrivateKey)
            {
                var rsaCryptoServiceProvider = (RSACryptoServiceProvider)x509Certificate2.PrivateKey;

                if (rsaCryptoServiceProvider != null)
                    return rsaCryptoServiceProvider;
            }

            // Throw exception if certificate used does not have private key
            throw new Exception("Certificate used does not have private key");
        }

        /// 
        /// Method for genrating file with the passed contents
        /// 
        /// Path of the file</param>
        /// Contents</param>
        private static void GenerateFile(string filePath, string text)
        {
            // Delete file if the file with same name exists
            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }

            // Writing the contents to the file
            var fileContents = new StringBuilder();
            fileContents.AppendLine(text);
            using (var outFile = new StreamWriter(filePath))
            {
                outFile.WriteLine(fileContents.ToString());
            }
        }
    }
}

The above code takes config file path and certificate’s thumbprint as input. And as output it signs the configuration file.


On Application load get the embedded public key certificate. For more details on this task follow this link – I have explained there, how to embed and get the public key certificate and ensure that it is not tampered. Once you get the certificate verify the signatures using following code:


/// 
/// Checks if the digital signature on config are valid or not
/// 
/// Returns true if signatures are valid else returns false
public static bool CheckDigitalSignature()
{
 // Get the path of the configuration file
 string configFilePath = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None).FilePath;  
 
 // Loading configuration file as an Xml Document
 XmlDocument config = new XmlDocument();
 // Preserving whitespaces is important the signatures are computed considering each and every character in the file
 config.PreserveWhitespace = true;
 config.Load(configFilePath);

 // Creating XMLDSIG processor 
 SignedXml xmldsig = new SignedXml(config);

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

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

 // Getting the public cerficate file
 X509Certificate2 certificate = new X509Certificate2(GetCertificateFile());

 // Public Key to verify the digital signatures
 RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PublicKey.Key;

 // Checking the signatures and returning the result
 return xmldsig.CheckSignature(rsa);
}

/// 
/// Get the public certificate file
/// 
/// Returns the path of the certificate file
private static string GetCertificateFile()
{
 // How to retrieve the embedded certificate can be seen in detail at :
 // http://www.rahulchugh.com/2011/09/ensuring-file-is-not-tampered-when-used.html
 string certificateFilePath = "D:\ApplicationFolder\Resources\Certificate.cer"
 return certificateFilePath;
}


4 comments:

  1. Nice. But using this we would loose the "Sandbox" nature of app. And majority of clients won't like to add a certificate for a app.

    If all we want is to ensure the tempering with app.config than we achieve it as following:
    1. Encrypt your app.config with any private key.
    2. add the encrypted content to your app.config file as a comment.
    3. Now every time your application loads, read this specific comment.
    4. Decrypt the comment using the private key.
    5. Match the decrypted result with app.config. Make sure you delete the comment part from the app.config before matching.
    6. If the result matches then app.config is not tempered.

    ReplyDelete
  2. @Ankush... thanks for the comment. I guess there are some things that you are missing :
    1. Why would I keep private key on client - I would never ever distribute my private key.
    2. Once I add new content(the comment that you are saying) to the config its no more the same config - its modified. XMLDSIG takes care of this while it adds signature.

    ReplyDelete