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:
- Config file cannot be signed using signtool, as this is an XML file.
- Application crashes if I just sign the config file as config file no more abide by the schema .net CRL expects.
- 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:
- 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.
- 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:
- Import pfx certificate for signing the config file
- Add a new <section> item to <configSections> element
- Execute the code for signing the config file
- On application load retrieve the embedded public key (.cer file)
- 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; }