[ Team LiB ] Previous Section Next Section

12.4 Naming and Signing Assemblies

Assemblies are identified with a four-part name, consisting of:

  • The simple name of the assembly

  • The version number of the assembly

  • An optional originator public key (and associated digital signature)

  • An optional set of culture information

The assembly's simple name is defined in the assembly manifest, and is generally the same as the name of the module that contains the manifest, minus the extension (i.e., the simple name for the assembly MyAssembly.dll is MyAssembly).

The assembly's version number is divided into four parts and looks like this:

<major>.<minor>.<build>.<revision>

The System.Reflection.AssemblyVersionAttribute assembly-level custom attribute allows you to specify either a full or partial version number for the assembly, as well as allowing you to have the <build> and <revision> portions of the version number automatically change for each build. For example, apply the following custom attribute to an assembly:

using System.Reflection;
[assembly:AssemblyVersion("1.0.0.*")]

This results in an assembly in which the revision number is different every time the assembly is compiled. This represents the number of seconds since midnight, divided by two, since the day the assembly was built.

Assemblies can also be digitally signed using public-key technology to identify the developer (known as the "originator") and to detect tampering with the assembly after it has been signed. When an assembly is signed, an eight-byte originator ID (known as a public-key token) forms part of the assembly's name.

In order to sign an assembly, the originator needs to first generate an RSA public/private key pair, which can be done using the sn.exe utility, as follows:

sn.exe -k PublicPrivate.snk

This generates a file (PublicPrivate.snk) containing both a 128-byte RSA public key and a matching private key.

Once this is done, the System.Reflection.AssemblyKeyFileAttribute assembly-level custom attribute is used to tell the compiler where to find the key pair. This attribute also tells the compiler that after generating the assembly it should sign it with the private key, insert the signature into the PE file, and add the public key to the assembly manifest.

using System.Reflection;
[assembly:AssemblyKeyFile("PublicPrivate.snk")]

The presence of this additional information in the PE file and assembly manifest allows the CLR to, before loading the assembly at runtime, verify that it hasn't been tampered with. The sn.exe utility can also be used to manually verify that a signed assembly has not been tampered with, by using the -v option:

sn.exe -v MyAssembly.dll

Additionally, clients that use types from a signed assembly include a simplified 8-byte MD5 hash of the public key (again, this is known as a public key token) directly in the assembly reference.

This further protects clients, since re-signing an assembly that has been tampered with either requires the originator's private key (something most originators guard closely), or the use of a different public/private key pair. In the latter case, the public key token for the new key pair doesn't match the original public key token embedded in the client's assembly reference, and the modified assembly is not loaded.

To determine what the public key token is for a given originator's public/private key pair, use the sn.exe utility with the -T (for assemblies) or with -t (for public-key-only keyfiles), as follows:

sn.exe -T MyAssembly.dll
sn.exe -t Public.snk

Since the originator's private key is so critical, some organizations prefer to centralize signing authority, and not to distribute the key widely even within the organization. It is also possible to delay-sign an assembly, in which the compiler embeds the public key in the manifest and preserves space in the PE file for the signature, but doesn't actually sign the assembly.

The first step in this procedure is to generate a file that only contains the public key:

sn.exe -p PublicPrivate.snk Public.snk

The resulting Public.snk file can then be used in place of the PublicPrivate.snk file. Additionally, the System.Reflection.AssemblyDelaySign attribute is used to tell the compiler we wish to delay-sign the assembly:

using System.Reflection;
[assembly:AssemblyKeyFile("Public.snk")]
[assembly:AssemblyDelaySign(true)]

The resulting assembly can be used almost identically to normally signed assemblies for development purposes, except it cannot be placed in the Global Assembly Cache and it cannot be verified with sn.exe.

Delay-signing the assembly means that the compiler (and consequently, the developers) only needs access to the public key. At some later point a trusted member of the organization can use the sn.exe tool and the closely guarded private key to re-sign the assembly, which inserts the appropriate digital signature in the PE file. This is done using the sn.exe utility, as follows:

sn.exe -R MyAssembly.dll PublicPrivate.snk

Once the assembly has been re-signed, it can be verified using sn.exe and placed in the GAC using gactutil.exe.

The final part of the assembly name is the culture information. This is used when the assembly contains only culture-specific resources such as localized strings, dialogs, and other interface elements. The culture is controlled by the presence of the System.Reflection.AssemblyCulture custom attribute on the assembly, as follows:

using System.Reflection;
[assembly:AssemblyCulture("en-US")]

If no culture is specified, the assembly is considered to be culture-neutral.

When combined, the four elements name, version, culture and originator make up a unique identifier (known as an AssemblyRef) for an assembly, which is part of a TypeRef to unambiguously identify a type.

To allow user code to refer to a unique assembly in this way (e.g., for programmatic loading of assemblies via reflection), these four elements can be combined in a stringified, human-readable format known as the assembly display name. The display name is built up from the four elements like this:

<name>, Version=<version>, Culture=<culture>, PublicKeyToken=<originator>

The display name may be used as input to the System.Reflection.Assembly.Load method, as follows:

Assembly a = Assembly.Load(@"MyAssembly, Version="1.0.0.0",                          
               Culture=neutral, PublicKeyToken=70e2f3b624e8bfa5");
    [ Team LiB ] Previous Section Next Section