Every now and then we use application files which are installed as part of your application on user’s machine. Recently, I came across the following scenario:
Scenario
We have a .net Think Client Application. Just take for example - We have a public key certificate (.cer) file which validates some signature. We want to ensure that this file is not tampered when we are using it. (I have just used certificate file as an example. It can be any file – say a pdf file which serves as a help file for my application.)
What is the issue?
As this file is part of our .net application which is installed on a user’s machine. User can change this certificate file with some of his own file, which may lead to security concerns.
How to resolve the issue?
Let’s make it simple and split it into steps:
- We’ll embed the file in our assembly
- Just before we want to use the file we’ll stream the file to user’s hard disk.
- The first time we stream the file we’ll store the MD5 hash value of the file’s content.
- Every time we use the file we’ll check the hash value:
a. If it matches we are good to use the file
b. If it doesn’t matches then we’ll delete this file and will go back to Step 2
Code for the solution
- Embedding file to the assembly : Add a “Resources” folder to your project. (optional – we’ll use this while retrieving file). Add the file Certificate.cer file to that folder and in the properties window of that file set the “Build Action” to “Embedded Resource".
- To retrieve and use this file use the following code :
namespace DefaultNamespace.FileHelper { using System; using System.Configuration; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text; ///The Certificate File Helper Class public class CertificateFileHelper { ////// For entry in assembly /// private static Assembly _entryAssembly; ////// Syncronization Root /// private static object _syncRoot = new object(); ////// Hash of the help file /// private static string _hashCertificate; ////// Gets the Certificate File Resource Namespace /// public const string CertificateFileResourceNamespace = ".Resources.CertificateFile.cer"; ////// Gets the location of the Certificate file. /// public const string CertificateFileLocation = @"Resources\CertificateFile.cer"; ////// Get or Set Entry in Assembly /// private static Assembly EntryAssembly { get { lock (_syncRoot) { if (null == _entryAssembly) { _entryAssembly = Assembly.GetEntryAssembly(); } // In a automated test scenario the Entry Assembly comes back null so grab yourself if (null == _entryAssembly) { _entryAssembly = Assembly.GetExecutingAssembly(); } return _entryAssembly; } } set { lock (_syncRoot) { _entryAssembly = value; } } } ////// Checks if the digital signature on config are valid or not /// ///Returns true if signatures are valid else returns false public static void DoWorkUsingCertificateFile() { // Your Code // Your Code // Your Code // Gets the cerficate file string file = GetCertificateFile(); // Your Code // Your Code // Your Code } ////// Writes the cetificate file to disk if the file is not present. /// ///Path at which file is written private static string GetCertificateFile() { // Certificate file location var certificateFile = string.Format( CultureInfo.CurrentCulture, "{0}\\{1}", Path.GetDirectoryName(EntryAssembly.Location), CertificateFileLocation); // Checks if certificate file already exists if (File.Exists(certificateFile)) { // Returns the certificate file path if the hash file is not tampered if (!string.IsNullOrEmpty(_hashCertificate) && GetMD5Hash(GetBytesFromFile(certificateFile)).Equals(_hashCertificate)) { return certificateFile; } // Delete the file if the file is tampered File.Delete(certificateFile); } // Getting the default namespace // Common string across al the types that assembly has string defaultNamespace = EntryAssembly.GetTypes().Aggregate( string.Empty, (current, type) => string.IsNullOrEmpty(current) ? type.Namespace : GetCommonString(current, type.Namespace)); // Getting the file stream embedded in the assembly using its resource information var certificateFileStream = EntryAssembly.GetManifestResourceStream(defaultNamespace + CertificateFileResourceNamespace); // Create a FileStream object to write a stream to a file using (FileStream fileStream = File.Create(certificateFile, (int)certificateFileStream.Length)) { // Fill the bytes[] array with the stream data byte[] bytesInStream = new byte[certificateFileStream.Length]; certificateFileStream.Read(bytesInStream, 0, bytesInStream.Length); // Storing the hash of the file _hashCertificate = GetMD5Hash(bytesInStream); // Use FileStream object to write to the specified file fileStream.Write(bytesInStream, 0, bytesInStream.Length); } return certificateFile; } ////// Gets the MD5 hash of the file. /// /// The input bytes.</param> ///Hexadecimal hash string private static string GetMD5Hash(byte[] inputBytes) { var hashString = new StringBuilder(); using (var cryptoServiceProvider = new MD5CryptoServiceProvider()) { byte[] hash = cryptoServiceProvider.ComputeHash(inputBytes); foreach (byte b in hash) { hashString.Append(b.ToString("x2").ToLower()); } } return hashString.ToString(); } ////// Gets the common string. (We are using this method for getting the default namespace) /// /// The first string.</param> /// The second string.</param> ///common string private static string GetCommonString(string firstString, string secondString) { string commonString = string.Empty; int shortStringLength = firstString.Length < secondString.Length ? firstString.Length : secondString.Length; for (int i = 0; i < shortStringLength; i++) { if (!firstString[i].Equals(secondString[i])) { break; } commonString = commonString + firstString[i]; } // Removing trailing periods commonString = commonString.TrimEnd('.'); return commonString; } ////// Gets the bytes from file. /// /// The full file path.</param> ///File read in byte array private static byte[] GetBytesFromFile(string fullFilePath) { byte[] bytes; // this method is limited to 2^32 byte files (4.2 GB) using (FileStream fileStream = File.OpenRead(fullFilePath)) { bytes = new byte[fileStream.Length]; fileStream.Read(bytes, 0, Convert.ToInt32(fileStream.Length)); } return bytes; } } }
No comments:
Post a Comment