Previous Section  < Day Day Up >  Next Section

15.2. Strongly Named Assemblies

The software development process relies increasingly on integrating code with components from multiple vendors. The vendor may be well known and trusted, or the component may be Internet freeware from an unknown developer. In both cases, security concerns demand a way to identify and authenticate the software. The most reliable solution is to sign the software with some unique digital signature that guarantees its origin and trustworthiness. The certificates that identify the publisher of downloadable software on the Internet are an example of this.

One of the integral parts of .NET security is the use of signing to create a uniquely identifiable assembly. .NET refers to such an assembly as strongly named. A strongly named assembly uses four attributes to uniquely identify itself: its file or simple name, a version number, a culture identity, and a public key token (discussed later). Together, these four are referred to as the "name" of the assembly. In addition, the assembly has the digital signature created by signing it. All of this is stored in the assembly's manifest. [2]

[2] The manifest is a set of tables containing metadata that describes the files in the assembly.

Although an assembly does need to be strongly named, doing so offers several advantages:

  • It enables version control. Although you can add a version number to an assembly, the Common Language Runtime (CLR) ignores the version information unless the assembly is strongly named. As we will see later, having versioning in effect permits multiple versions of a component to run and ensures compatibility between assemblies sharing a version number.

  • It permits an assembly to be placed in the Global Assembly Cache (GAC) where it can be shared among calling assemblies.

  • The .NET Code Access Policy can be used to grant or restrict permissions to assemblies based on their strong name. In addition, the strong name can also be used programmatically to control access to resources.

  • It allows assemblies to have the same name, because the assemblies are identified by their unique information梟ot their name.

Creating a Strongly Named Assembly

The first step in creating a strong name is to create a pair of public and private encryption keys. During compilation, the private key is used to encrypt the hashed contents of the files contained in the assembly. The encrypted string that is produced is the digital signature that "signs" the assembly. It is stored in the manifest, along with the public key. Here are the steps to create and use a strongly named assembly:

1.
A strong name is generated using asymmetric public key cryptography. This scheme relies on a private key that is used for encryption and a public key that decrypts. To create a file containing this key pair, use the Strong Name command-line utility as shown here:


SN 杒 <keyfilename>

SN 杒  KeyLib.snk


2.
Because the public key that is created is so large, .NET creates a more manageable public key token that is a 64-bit hash of the public key. When a client assembly is built that references a strongly named assembly, the public key token of the referenced assembly is stored in the manifest of the client assembly as a way to reference the target assembly.

3.
After you have the file containing the public/private key pair, creating the strongly named assembly is simple: just place the AssemblyKeyFile attribute (located in the System.Refection namespace) in your code at the assembly level:


[assembly: AssemblyKeyFile("KeyLib.snk")]


4.
This statement causes the compiler to extract the private key from the specified file and use it to sign the assembly. At the same time, the public key is placed in the assembly's manifest. Note that if you invoke the C# compiler from the command line, you can leave out the AssemblyKeyFile attribute and use the /keyfile flag to specify the path to the .snk file.

5.
When the runtime loads this assembly, it essentially reverses the signing process: It decrypts the signature using the public key found in the manifest. Then, it performs a hash of the assembly's contents and compares this with the decrypted hash. If they do not match, the assembly is not allowed to run.

Figure 15-3 summarizes the overall process. Note how decryption works. The decrypted signature yields a hash that should match the output when a new hash is performed on the assembly. If the two match, you can be sure that the private key associated with the public key was used for the original signing, and that the assembly has not been tampered with梒hanging even one bit will result in a different hash.

Figure 15-3. Using private and public keys to sign and verify a strong assembly


Core Note

A digital signature is not the same thing as a digital certificate. The digital signature in a strongly named assembly tells you nothing about who created the assembly, whereas a certificate contains the identity information that is used to authenticate the certificate publisher. Refer to documentation on Authenticode to learn how to obtain and use a certificate with an assembly.


Delayed Signing

It is imperative that the private key generated using Sn.exe (or some other process) not be compromised, because it is how an organization uniquely signs its software. If another party has access to the key, consumers cannot trust the ownership of the assembly.

One measure to secure the key is to limit its availability to developers, or withhold it altogether. During the development stage, developers are given only the public key. Use of the private key is delayed until it is necessary to sign the final software version. Delayed signing requires different steps than are used for creating a strongly named assembly:

1.
Sn.exe is used to create a file containing the public/private key pair. This should be the task of the security administrator.

2.
Sn.exe is run again to extract the public key into a separate file. The command uses the switch, and specifies the original file and the file to contain the public key:


SN 杙  KeyLib.snk  PubKeyLib.snk


3.
The public key file is distributed to developers who must add two attributes to their assemblies to perform partial signing and have the public key stored in the assembly's manifest.


[assembly: AssemblyKeyFile("PubKeyLib.snk")]

[assembly: AssemblyDelaySign(true)]


4.
If you follow these preceding steps to create an assembly, you'll find that an exception occurs ("strong name validation failed") when you try to load it. This is because the assembly does not have a valid signature. To instruct the runtime to skip signature verification, run Sn.exe with the 朧r switch and specify the delay-signed assembly:


SN 朧r <delay-signed assembly>


5.
Prior to deployment, the assembly should be officially signed. Use SN with the switch to sign it:


SN 朢 <delay-signed assembly> 


To reinstate signature verification for the assembly, run


SN 朧u <assembly>


Because an assembly references another strongly named assembly using its public key, there is no need to rebuild assemblies dependent on this one.

Global Assembly Cache (GAC)

The Global Assembly Cache is a special directory set aside where strongly named assemblies can be stored and located by the CLR. As part of resolving references at load time, the CLR automatically looks in the GAC for the requested assembly. The obvious advantage of storing assemblies in the GAC is that they are located in a central, well known location where they can be located and shared by multiple applications. A less obvious advantage is that the strong name signatures for assemblies in the GAC are verified only when installed in the GAC. This improves the performance of applications referencing these assemblies, because no verification is required when loading the assembly.

Physically, the GAC is a Microsoft Windows directory located on the following path:


C:\winnt\assembly


You can view its contents using a shell extension (ShFusion.dll) that is added to Windows Explorer as part of the .NET Framework installation. Each entry displays an assembly's name, type, version number, and public key token. By clicking an assembly entry, you can bring up a context menu that permits you to display the assembly's properties or delete it.

The easiest way to install a strongly named assembly into the GAC (or uninstall one) is to use GACUtil.exe, a command-line utility that ships with .NET SDK. Here is the syntax for performing selected operations:

>gacutil /i <assembly>

Installs assembly in GAC.

>gacutil /u <assembly>

Uninstalls an assembly from the GAC.

>gacutil /if <assembly>

Installs assembly in GAC; if an assembly with that name already exists, it is overwritten.

>gacutil /l

Lists contents of GAC.


There are a couple of drawbacks to storing an assembly in the GAC. First, it is difficult to reference during compilation due to the verbose GAC subdirectory naming conventions. An alternative is to compile referencing a local copy of the assembly. Then, remove the local assembly after compilation is completed.

Another possible drawback stems from the fact that an assembly cannot be copied into the GAC. If your application requires an assembly in the GAC, it eliminates deploying an application by simply copying files to a client's machine. Deployment issues are discussed in the last section of this chapter.

Versioning

A major benefit of using strongly named assemblies is that the CLR uses the assembly's version information to bind assemblies that are dependent on each other. When such an assembly is loaded, the CLR checks the version number of referenced assemblies to ensure they have the same version numbers as recorded in the calling assembly's manifest. If the version fails to match (usually because a new version has been created), an exception is thrown.

Assigning a Version Number to an Assembly

Every assembly has a version number. A default value is used if one is not explicitly defined. The version can be assigned using the Assembly Linker (Al.exe) tool, but is usually declared within the code using an AssemblyVersion attribute:


[assembly: AssemblyVersion("1.0.0.0")]


The version number has four parts:


<major version>.<minor version>.<build number>.<revision>


You can specify all the values or you can accept the default build number, revision number, or both by using an asterisk (*). For example:


[assembly:AssemblyVersion("2.3.")]     yields 2.3.0.0

[assembly:AssemblyVersion("2.3.*")]    yields 2.3.1830,4000


When an asterisk (*) is specified for the build number, a default build number is calculated by taking the number of days since January 1, 2000. The default revision number is the number of seconds past midnight divided by two.

You can use reflection to view an assembly's version along with the other parts of its identity. To illustrate, add the following attributes to the code in Listing 15-1 to create a custom version number and strong name:


[assembly: AssemblyKeyFile("Keylb.snk")]

[assembly: AssemblyVersion("1.0.*")]


Compile the code and use the Assembly.GetName method to display the assembly's identification.


Console.WriteLine(Assembly.GetExecutingAssembly().GetName());


This method returns an instance of the AssemblyName class that contains the assembly's simple name, culture, public key or public key token, and version.


fxtest, Version=1.0.1839.24546, Culture=neutral, 

   PublicKeyToken=1f081c4ba0eeb6db


    Previous Section  < Day Day Up >  Next Section