Building Assemblies with VB.Net - Signing of the Assembly and Preventing Tampering (Page 3 of 3 )
The .NET framework relies on two industry-standard forms of cryptography known as one way hash functions and asymmetric encryption.
The hash function allows you to create a digital fingerprint from a large digital object such as a public key or a file image and returns a smaller value known as a hash value. The hash value of an assembly serves as its digital finger print for a particular build.
The next step in generating a digital signature involves asymmetric encryption. While signing an assembly, the private key is used to generate a digital signature by encrypting the hash value of an assembly file. Later, the public key can be used to decrypt the digital signature and retrieve the hash value of the assembly file.
Hence, the sequence of compilation involves the following steps:
First, the compiler generates an image of the assembly file containing its manifest, its type information, and its executable code in the form of IL. However, the compiler does not generate the digital signature and leaves a blank space at the end of the assembly file to act as a placeholder.
Second, the compiler generates the Hash value from the image of the assemble file that was built during the first phase. The compiler then encrypts the hash value with the private key to generate the digital signature and writes it into the placeholder at the end of the assembly file.
Once the assembly has been distributed, let us discuss how the CLR verifies the authenticity of the assembly’s digital signature.
The CLR uses the public key to decrypt the digital signature and get the hash value of the assembly that was present during compilation. Next the CLR uses the same Hash function to generate a hash value from the assembly’s physical image. The CLR compares the two hash values and if the hash values are equal then the CLR considers the test a success because it is sure of two things; namely, that the digital signature was generated by someone in possession of the private key and that the physical image of the assembly file was originally signed.
If the CLR determines that the two hash values are not equal it considers the verification test a failure. In case MyAssembly.dll is in the global assembly cache it is verified when it was placed in the GAC and is then not retrieved at run time.
Let’s discuss a typical case using MyApp.exe and MyAssembly.dll. Imagine you compiled MyAssembly.dll with a strong name and that you have compiled Myapp.exe with a reference to MyAssembly.dll. It is now impossible for someone without the private key to change the behavior of the application on the production machine by replacing the real version of the MyAssembly.dll with a tampered version.
Note that the assemble manifest for MyApp.exe contains a reference with the public key token associated with the same public key compile into MyLibarary.dll. Therefore, you have the guarantee that the CLR meets two important criteria:
The first is the assembly file for MyAssembly.dll contains a public key value that matches the public key token in the assembly manifest of MyApp.exe.
The second is that MyAssembly.dll has been signed by someone in possession of the private key.
When the user runs MyApp.exe it executes code that makes the first call to MyAssembly.dll. At this point, the CLR will attempt to load Myassembly.dll that has a strong name, so it runs a verification check. This verification check allows the CLR to detect if someone without access to the private key has made changes to the physical image of the assembly file.
If someone wants to tamper with MyAssembly.dll on the production machine, the verification scheme thwarts the bad guy. Therefore, any changes made to the physical image after it has been signed renders the existing digital signature invalid. Note that this verification scheme doesn’t really prevent tampering. It only prevents tampering from going undetected. When the CLR attempts to load a strongly named assembly with a digital signature that doesn’t match the physical layout of the assembly file, it fails to load attempt by throwing System.IO.FileLoadExecption. This scheme would not be as reliable if your application depended on a strongly named assembly, which in turn depended on another assembly that did not have a strong name. The hacker could replace the assembly that did not have a string name and the CLR would not be able to detect this. The CLR enforces this restriction so that you are guaranteed that a strongly named assembly depends on only other strongly named assemblies.
The following figure shows an assembly manifest for reference. This assembly manifest is using the IL dissembler tool of the .NET framework:
DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.