TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=769a8f10a7f072b4

If the above line means anything to you, you are probably a .NET developer. You also probably know that the hex string at the end represents a public key token, which is a sign that the assembly has a strong name signature. But do you know how this token is calculated? Or do you know the structure of the strong name signature? In this post, I will go into details how strong naming works and what are its shortcomings. We will also have a look at certificate-based signatures and, in the end, we will examine the assembly verification process.

Strong name signature and Public Key token

A valid strong name signature ensures the recipients that the assembly they received has not been tampered. It also uniquely identifies a given assembly. Though, it does not say anything about the identity of the signer. Two parts of the assembly binary code play role in the strong name verification process.

The first part is the public key, which is part of the #Blob stream in the assembly metadata (screenshot shows a part of the dnSpy window):

The image below lists the elements which build the Public Key block:

The Public Key Token, which is used to uniquely reference the assembly, is a hex representation of the low 8 bytes of the SHA-1 hash of the Public Key in a reverse byte order. For my test assembly, it equals: 769a8f10a7f072b4 (SHA-1(00 24 00 00 0c 80 … c8 8a c1 b1) = 9aa4de0a96ada8d83d6d7678b472f0a7108f9a76).

The second part is the RSA signature of the hash of the assembly content. Before counting this hash, we need to fill with zeros the following bytes of the file: the authentication signature entry (we will get to it in a moment), the strong name blob and the PE header checksum. The signature is later stored in the PE file text section at an offset saved in the COR20 header:

To calculate the RSA signature, we need to own the private key corresponding to the public key listed in the previous paragraph. If you would like to see C# code implementing the validation, have a look at the StrongNameSigner.cs file from the dnLib library.

Having examined the strong name signature structure, let’s focus on tools we may use to create it. We start by generating the .snk file, which will store the RSA keys details (if you are interested in the details of the .snk file format, have a look at my 010 editor template):

sn.exe -k 2048 TestLib.snk

We should keep the generated .snk file secret. Next, depending on our scenario, we may use either the C# compiler (csc.exe) or assembly linker (al.exe). Both accept the /keyfile parameter, in which we provide the path to the just generated .snk file, e.g.

csc.exe /keyfile:TestLib.snk /t:library TestLib.cs

This command will generate a signature based on the SHA-1 hash of the file content. Nowadays, SHA-1 is considered inadequate for secure hashing and it is highly recommended to use SHA-2 digest. To sign our assembly using SHA-2 hash, we first need to extract the public key part of the .snk file:

sn.exe -p TestLib.snk TestLibPubKey.snk sha256

Then delay the private key signing (the strong name signature block will be zeroed and the Strong Name Signed flag will not be set). Both csc.exe and al.exe accept the /delaysign+ parameter for this purpose:

csc.exe /keyfile:TestLibPubKey.snk /delaysign+ /t:library TestLib.cs

Finally, we need to re-sign the assembly using the private key:

sn -Ra TestLib.dll TestLib.snk

If you have a strong named assembly and would like to migrate the signature, have a look at this article.

Authenticode signature

Autheticode signature, as its name suggests, is used to authenticate the owner of the assembly. It also guards the integrity of the assembly. The size and the location of the signature is stored in the PE optional header:

The Force Integrity flag, I also marked on the image, is used to force the loader to always check the signature of the given assembly (Windows skips the signature verification, except for drivers and modules loaded into the protected processes). For native code, there is a special linker option to enable it. I haven’t found such an option in either csc.exe or al.exe and used dnSpy to set it (I needed to first delay signed the assembly, set the flag, and re-sign it).

To create an Authenticode we need to possess a certificate containing a private key (.pfx format is required). For test purposes a self-signed certificate will work (unless you set the Force Integrity flag), but for release deployments you should obtain one from a trusted provider. A sample command to sign an assembly using a certificate file might look as follows:

signtool sign /v /ph /fd sha256 ` /f .\fileSignature.pfx ` /p {certificate-password} ` /t http://timestamp.verisign.com/scripts/timstamp.dll .\TestLib.dll

Remember to create the strong name signature before creating the Authenticode.

Signature verification

.NET, starting from the version 3.5, does not perform the strong name signature validation when an assembly is loaded into a full-trust application domain. This basically means that in full-trust app domains we may replace a strong named assembly with an assembly containing only a public key and no one will notice. We may change this behavior by either enabling the bypassTrustedAppStrongNames property of the runtime in the configuration file, or by zeroing the AllowStrongNameBypass value of the HKLM\SOFTWARE\Microsoft.NETFramework key (use Wow6432 for 32-bit apps on a 64-bit system).

Even with those settings in place, there is still a way to load a partially signed assembly into our application app domain. This mechanism is used when working with delay signed assemblies. During development, we often do not want to fully sign the assemblies on each build (the private key should be kept safe in one place), but at the same time, we want the assemblies to behave as they had strong names. This could be achieved by adding our assembly name and public key token to the registry under the key HKLM\Software\Microsoft\StrongName\Verification, for example: HKLM\Software\Microsoft\StrongName\Verification\TestLib,8FCE6031CC56162D, or by using sn command with the -Vr option. Only system administrator is allowed to modify the verification list.

When it comes to the Authenticode verification, .NET performs it only if the Force Integrity flag is set in the PE header.

Fortunately, we do not need to rely only on the automatic verification; we can perform the verification on our own. The same tools we used to sign the assemblies, provide us with ways to validate them. To check the strong name signature, we may use sn –vf (the -f option forces sn to check the signature even if it is disabled in the registry). Adding the key file as an argument is optional, but recommended if we do not use the Authenticode. Example usage:

sn -vf TestLib.dll TestLib.snk

For Authenticode verification, we may use either signtool or sigcheck. Example calls might look as follows:

signtool verify TestLib.dll (add /pa if you are using a self-signed root certificate) sigcheck -i TestLib.dll (-i will show the certificate chain used to sign the assembly)

Sigcheck can also recursively scan directories and verify all found binaries. I encourage you to check its help to find more interesting options (like submitting the binary hash to VirusTotal).

Summary

Given that it is so easy to skip the signature verification, you may doubt the whole concept of signing binaries. Consider though that the verification while loading is not that important. If a malicious person gets write access to the binaries, signatures will not protect you against his/her activities. But… signing the assembly is the only way to prove that the code in the assembly is legitimate and has not been tampered (I encourage you to use both: strong name signature and Authenticode). We should perform the first verification after the client receives the binaries (this is often done by the installers) – to be sure that no tampering in transit happened. Next, it is extremely important to set valid access rights on the folder where binaries reside – only authorized persons should be allowed to modify the files. Finally, whenever there is a problem with our application, we should ask the client to verify the signatures before filling the bug report – only that way we could be sure that the bug is really ours.