If multiple applications are going to access an assembly, the assembly must be placed in a well-known directory, and the CLR must know to look in this directory automatically when a reference to the as- sembly is detected. However, we have a problem: Two (or more) companies could produce assemblies that have the same file name. Then, if both of these assemblies get copied into the same well-known directory, the last one installed wins, and all of the applications that were using the old assembly no longer function as desired. (This is exactly why DLL hell exists today in Windows, in which shared DLLs are all just copied into the System32 directory.)
Obviously, differentiating assemblies simply by using a file name isn’t good enough. The CLR needs to support some mechanism that allows assemblies to be uniquely identified. This is what the term strongly named assembly refers to. A strongly named assembly consists of four attributes that uniquely identify the assembly: a file name (without an extension), a version number, a culture identity, and a public key. Because public keys are very large numbers, we frequently use a small hash value derived from a public key. This hash value is called a public key token. The following assembly identity strings (sometimes called an assembly display name) identify four completely different assembly files.
The first string identifies an assembly file called MyTypes.exe or MyTypes.dll (you can’t actually
determine the file extension from an assembly identity string). The company producing the assembly is creating version 1.0.8123.0 of this assembly, and nothing in the assembly is sensitive to any one culture because Culture is set to neutral. Of course, any company could produce a MyTypes.dll (or MyTypes.exe) assembly file that is marked with a version number of 1.0.8123.0 and a neutral culture.
There must be a way to distinguish this company’s assembly from another company’s assembly that happens to have the same attributes. For several reasons, Microsoft chose to use standard public/ private key cryptographic technologies instead of any other unique identification technique such as GUIDs, URLs, or URNs. Specifically, cryptographic techniques provide a way to check the integrity of the assembly’s bits as they are installed on a machine, and they also allow permissions to be granted on a per-publisher basis. I’ll discuss these techniques later in this chapter. So a company that wants
to uniquely mark its assemblies must create a public/private key pair. Then the public key can be as- sociated with the assembly. No two companies should have the same public/private key pair, and this distinction is what allows two companies to create assemblies that have the same name, version, and culture without causing any conflict.
In Chapter 2, I showed you how to name an assembly file and how to apply an assembly version number and a culture. A weakly named assembly can have assembly version and culture attributes embedded in the manifest metadata; however, the CLR always ignores the version number and uses only the culture information when it’s probing subdirectories looking for the satellite assembly. Be- cause weakly named assemblies are always privately deployed, the CLR simply uses the name of the assembly (tacking on a .dll or an .exe extension) when searching for the assembly’s file in the applica- tion’s base directory or in any of the application’s subdirectories specified in the Extensible Markup Language (XML) configuration file’s probing element’s privatePath XML attribute.
A strongly named assembly has a file name, an assembly version, and a culture. In addition, a
strongly named assembly is signed with the publisher’s private key.
The first step in creating a strongly named assembly is to obtain a key by using the Strong Name utility, SN.exe, that ships with the .NET Framework SDK and Microsoft Visual Studio. This utility offers a whole slew of features depending on the command-line switch you specify. Note that all SN.exe’s command-line switches are case-sensitive. To generate a public/private key pair, you run SN.exe as follows.
SN –k MyCompany.snk
This line tells SN.exe to create a file called MyCompany.snk. This file will contain the public and
private key numbers persisted in a binary format.
Public key numbers are very big. If you want to, after creating the file that contains the public and private key, you can use the SN.exe utility again to see the actual public key. To do this, you must ex- ecute the SN.exe utility twice. First, you invoke SN.exe with the –p switch to create a file that contains only the public key (MyCompany.PublicKey).1
SN –p MyCompany.snk MyCompany.PublicKey sha256
Then, you invoke SN.exe, passing it the –tp switch and the file that contains just the public key.
SN –tp MyCompany.PublicKey
When I execute this line, I get the following output.
Microsoft (R) .NET Framework Strong Name Utility Version 4.0.30319.17929 Copyright (c) Microsoft Corporation. All rights reserved.
Public key (hash algorithm: sha256): 00240000048000009400000006020000002400005253413100040000010001003f9d621b702111
The SN.exe utility doesn’t offer any way for you to display the private key.
The size of public keys makes them difficult to work with. To make things easier for the developer (and for end users too), public key tokens were created. A public key token is a 64-bit hash of the pub- lic key. SN.exe’s –tp switch shows the public key token that corresponds to the complete public key at the end of its output.
Now that you know how to create a public/private key pair, creating a strongly named assembly is simple. When you compile your assembly, you use the /keyfile:<file> compiler switch.
csc /keyfile:MyCompany.snk Program.cs
When the C# compiler sees this switch, the compiler opens the specified file (MyCompany.snk), signs the assembly with the private key, and embeds the public key in the manifest. Note that you sign only the assembly file that contains the manifest; the assembly’s other files can’t be signed explicitly.
If you are using Visual Studio, you can create a new public/private key file by displaying the prop- erties for your project, clicking the Signing tab, selecting the Sign The Assembly check box, and then choosing the <New…> option from the Choose A Strong Name Key File combo box.
1 In this example, I am using Enhanced Strong Naming, which was introduced in .NET Framework 4.5. If you need to produce an assembly that is compatible with previous versions of the .NET Framework, then you will also have to create a counter-signature by using the AssemblySignatureKeyAttribute. For details about this, see http://msdn.microsoft.com/ en-us/library/hh415055(v=vs.110).aspx.
Here’s what it means to sign a file: When you build a strongly named assembly, the assembly’s FileDef manifest metadata table includes the list of all the files that make up the assembly. As each file’s name is added to the manifest, the file’s contents are hashed, and this hash value is stored along with the file’s name in the FileDef table. You can override the default hash algorithm used with AL.exe’s /algid switch or apply the assembly-level System.Reflection.AssemblyAlgorithm IdAttribute custom attribute in one of the assembly’s source code files. By default, a SHA-1 algo- rithm is used.
After the PE file containing the manifest is built, the PE file’s entire contents (except for any Au- thenticode Signature, the assembly’s strong name data, and the PE header checksum) are hashed, as shown in Figure 3-1. This hash value is signed with the publisher’s private key, and the resulting RSA digital signature is stored in a reserved section (not included in the hash) within the PE file. The CLR header of the PE file is updated to reflect where the digital signature is embedded within the file.
Calculus.dll
FIGURE 3-1Signing an assembly.
The publisher’s public key is also embedded into the AssemblyDef manifest metadata table in this PE file. The combination of the file name, the assembly version, the culture, and the public key gives this assembly a strong name, which is guaranteed to be unique. There is no way that two companies could each produce an assembly named OurLibrary with the same public/private keys unless the companies share this key pair with each other.
At this point, the assembly and all of its files are ready to be packaged and distributed.
As described in Chapter 2, when you compile your source code, the compiler detects the types and members that your code references. You must specify the referenced assemblies to the compiler. For the C# compiler, you use the /reference compiler switch. Part of the compiler’s job is to emit an AssemblyRef metadata table inside the resulting managed module. Each entry in the AssemblyRef metadata table indicates the referenced assembly’s name (without path and extension), version num- ber, culture, and public key information.
The AssemblyRef metadata information (obtained by using ILDasm.exe) for a simple class library
Unfortunately, ILDasm.exe uses the term Locale when it really should be using Culture. If you look at the DLL assembly’s AssemblyDef metadata table, you see the following.
In this line, no public key token is specified because the DLL assembly wasn’t signed with a public/ private key pair, making it a weakly named assembly. If I had used SN.exe to create a key file com- piled with the /keyfile compiler switch, the resulting assembly would have been signed. If I had then used ILDasm.exe to explore the new assembly’s metadata, the AssemblyDef entry would have bytes appearing after the Public Key field, and the assembly would be strongly named. By the way, the AssemblyDef entry always stores the full public key, not the public key token. The full public key is necessary to ensure that the file hasn’t been tampered with. I’ll explain the tamper resistance of strongly named assemblies later in this chapter.