One of the great promises of creating applications for the Microsoft .NET Framework is the end of the so-called DLL Hell, that unfortunate situation that arises when a component is updated, thereby breaking other applications that depend on it. However, to realize this promise, developers need to be conversant with the concept and implementation of strong names. In this article, I'll take you through the hows and whys of strong names in managed code.
The whys of strong names
Before extolling the benefits of strong names, let's begin with a definition. A strong name is made up of information used to identify an assembly and includes the assembly's text name, four-part version number, culture information (if provided), a public key, and a digital signature stored in the assembly's manifest that contains the assembly's metadata and is embedded in one of the files of the assembly.
Most assemblies, such as those created with Visual Studio .NET, are single-file assemblies, i.e., a single .exe or .dll file. In such cases, the manifest is embedded within the single-file assembly. However, the Assembly Generation Tool (Al.exe) can be used to create multiple-file assemblies.
By including a strong name in an assembly, the common language runtime (CLR) can be assured that two assemblies with the same strong name are indeed identical in all respects. In other words, strong names provide unique identification of an assembly to the CLR. In addition, adding a strong name ensures binary integrity by allowing the CLR to perform verification when the assembly is loaded to determine that it has not been tampered with since it was compiled.
There are two primary scenarios in which developers should include a strong name with an assembly:
- Shared assemblies. By including a strong name, the assembly can be shared by multiple applications running on the same machine when installed in the Global Assembly Cache (GAC). This code-sharing model is the inverse of the model used in the unmanaged world, where COM components are automatically shared when compiled and registered in the system registry.
- Serviced Components. For a .NET class to take advantage of Enterprise Services (COM+ Services), such as distributed transactions and object pooling, the assembly that contains the class—called a Serviced Component because it inherits from the class EnterpriseServices.ServicedComponent—must have a strong name. Having a strong name allows Enterprise Services to be sure it loads the correct assembly. Serviced Components that run in the process hosted by Enterprise Services (DLLHost.exe) should be placed in the GAC, while those running as library applications in the caller's process need not be.
In the case of the first scenario, where shared assemblies are placed in the GAC, the main benefits are as follows.
Since all applications on the same machine will load the shared assembly from the GAC, the assembly needn't be deployed with each application. This differs from private assemblies, which are the default in the Framework and which must be deployed with each application.
As mentioned previously, a strong name allows the CLR's class loader to verify that the assembly has not been altered since it was compiled. By placing the assembly in the GAC, the verification is performed only when the assembly is first placed in the GAC and not each time it is loaded by an application, thereby increasing performance.
Reduced working set
When multiple applications reference the same shared assembly, all of the applications will be loading the assembly from the same location. As a result, the operating system will share the assembly's code pages among all the applications, thereby reducing the amount of memory used on the system.
When an assembly is deployed in the GAC, it provides a single place to deploy fixes. Although applications will by default use the version of the assembly they were compiled with, and the GAC allows multiple versions of the same assembly to coexist within the GAC, a version policy can be added to the machine.config file that ensures that all applications on the machine use the new version instead of the old. In addition, when an assembly contains a strong name, other code can specify that only code from an assembly signed with a particular strong name may call the code. For example, decorating a class with the StrongNameIdentityPermissionAttribute ensures that only callers with the specified strong name may create instances of the class, as shown in Listing A. In this case, only the PublicKey property was specified, so any assembly signed with the private key counterpart to this public key will be allowed to instantiate the Products class.
Issues of concern
Although strong names offer the benefit of allowing code to be shared and allow managed code to use Enterprise Services, developers need to be aware of the following issues.
Calling private assemblies
If a shared assembly attempts to load a type from a private assembly, the CLR will throw an exception because shared assemblies can reference only other shared assemblies. This restriction exists so that DLL conflicts cannot be introduced.
Strong names encompass the concept of identity, but they don't include the notion of trust. In other words, an assembly signed with a strong name can ensure version compatibility, but it cannot be used to ensure, for example, that the assembly about to be loaded came from Quilogy. To sign the assembly using an Authenticode digital signature, developers use the Signcode.exe command-line utility that ships with the .NET Framework. After an assembly is signed with an Authenticode signature, administrators can create policies that allow it to be downloaded and loaded on users' machines using code access security (CAS) mechanisms. The signature becomes part of the evidence used by the CLR's class loader to determine whether the assembly should be loaded.
Since assemblies with strong names are typically placed in the GAC, this increases the complexity of the installation process for an application that must install the assembly in the GAC. Fortunately, the Windows Installer projects created with Visual Studio .NET can automatically install assemblies into the GAC. In addition, developers can use the Gacutil.exe command-line utility.
Closely related to the issue of installation is the issue of deployment. One of the great benefits of Framework applications is that they can be deployed in an XCOPY manner, whereby the application's directory can be moved to another machine without having to reregister components. However, using shared assemblies throws a wrench into this process since shared assemblies must be installed in the GAC when the application is deployed to another machine.
The hows of strong names
To create a strong name for an assembly, a developer can use the Assembly Generation Tool (Al.exe) or include attributes in their code found in the System.Reflection namespace in the Framework. First, however, there needs to be a public-private key pair in place that can be used to sign the assembly. This key pair can be present either in a file or in a key container within the cryptographic service provider (CSP) on the machine (ultimately in the registry) on which the strong name is generated.
To create the key pair in the file, the Framework includes the Strong Name utility (Sn.exe). For example, to create a file called keyfile.dat that contains a new key pair, you could execute the utility, like so:
Sn.exe –k keyfile.dat
The key file could then be used by the Assembly Generation Tool to generate the strong name:
Al.exe /out:AtomicData.dll /keyfile:keyfile.dat
The Assembly Generation Tool includes switches to use a particular CSP and key container. More typically, a developer will choose to use one of the attributes AssemblyKeyFileAttribute, AssemblyKeyNameAttribute, or the AssemblyDelaySignAttribute in the AssemblyInfo.vb (or .cs) file:
In this case, the file containing the key pair is accessed during compilation to create the strong name placed in the assembly manifest. Developers can instead use the AssemblyKeyNameAttribute to specify the name of the container that is storing the key pair.
Although the technique shown here is fine for small organizations or individual development, the key pair used to sign code in a large organization is often closely guarded. In that situation, developers typically have access to the public key, while the private key remains in the hands of a trusted few. However, access to the public key during development is critical because any assemblies referencing the strong name assembly must contain the token of the public key in their own manifests. To enable development to continue, the Framework also supports delayed or partial signing.
As implied by the name, partial signing reserves space at compile time in the assembly manifest for the full strong name signature. The signature can then be added later. In the meantime, other assemblies can reference the strong name assembly. To implement delayed signing, the AssemblyInfo file can include the AssemblyDelaySign attribute and pass True to the constructor. This implies that the key file referenced in AssemblyKeyFile contains only the public key (which can be done using a switch of the Strong Name tool).
After the assembly is partially signed, it must also have its verification turned off. This is because a partially constructed strong name is not valid and will not pass the binary integrity checks as discussed previously. To bypass verification, use the Strong Name tool's–Vr switch:
Sn.exe –Vr AtomicData.dll
At some point later, the assembly can be passed to the group that has access to the private key, where the group signs it with the strong name using the –R switch:
sn.exe –R AtomicData.dll keyfile.dat
Weigh the benefits
By creating a strong name for an assembly, a developer can share the assembly with other applications on a machine using the Global Assembly Cache and access Enterprise Services such as distributed transactions and object pooling. However, creating a strong name and using the GAC entails extra work at compile and deployment time. Developers should think carefully about whether creating a strong name is appropriate for the assemblies they create.