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");
|