I'd just like to share some interesting points during my further preparation of the resources I mentioned earlier. For the second part (the last I'll prepare today since I have a rather busy day tomorrow) of the presentation I'm working on the facts about sharing assemblies using the GAC. I hope some of the blog readers already have been doing this, but let's give a quick review on the facts about this.
In .NET assemblies are self-describing and therefore do not need registration in the registry anymore. This is the reason why the xcopy deployment (or zero touch installation) in the .NET Framework works the way it does: just copy the files to a folder and the app will work smoothly. Now, this is great for several reasons: no more DLL hell (finally :-)), support for multiple versions of an assembly living together, simple installation, etc. However, there are situations where you do want to share assemblies across multiple applications running on the machine. For this reason, there is a component of the .NET Framework installation called the Global Assembly Cache or GAC. You can see the GAC as a sort of central 'repository' where the assemblies are stored that should be accessible by any application on the box (e.g. the System.*.dll assemblies live in the GAC). To avoid problems of the DLL hell, the way to put assemblies in the GAC is not analogous to the way of installing COM dll's on the system by using regsvr32.exe. Time to investigate this a little further...
First of all, we can't afford to be tricked by the same mouse trap again as in the COM world, that is the infamous 'version conflict' problem. Thus, it should be possible to install two versions of the same assembly on the system without having a risk on non-backward compatibility problems. This is why the .NET Framework requires that shared assemblies have a strong name, that is a name which consist of
- the name of the assembly
- the version
- a public key (in fact, a hash of the public key in 64 bits, called a 'token')
- the culture
To create a strong name for an assembly (that is, to generate a key pair), you'll have to use the sn.exe (abbreviation for 'strong name') tool of the .NET Framework. The most used switch is the -k switch that is used to create the pair of keys (private and public) and store it in a specified file (size of 596 bytes). What is of most interest for the 'assembly signing part' is the public key (and the token in particular) of the key pair, which can be extracted from the file using the -p switch (result of 160 bytes). To find out the toke, you can run the sn.exe -tp command which will extract the public key from the given file with the key pair and display the public key token that will be used to sign the assembly.
The next step is to tell the compiler that it should use the key to sign the assembly which can be done in the AssemblyInfo.cs (or .vb) file using the 'AssemblyKeyFile' attribute in the file (although it can be placed in any code file that is part of the compiled unit). My demo file looks as follows:
public static void Main()
The interesting part happens when the file is compiled and is examined using a hex editor again. During compilation the PE file (I mentioned earlier) is hashed using SHA-1 and signed using the private key (this explains why the specified file needs to have a key pair):
error CS1548: Cryptographic failure while signing assembly 'c:\Documents and
Settings\Administrator\test.exe' -- 'Key file 'pub.key' is missing the
private key needed for signing'
This hash results in an RSA signature that is stored inside the file together with the public key as a part of the file's manifest. The hex editor will show you the sign parts in the file (you'll see the string 'RSA' somewhere in the byte stream as well as the original name of the file containing the key). Furthermore, the other metadata can be recognized as well (it's stored in Unicode format), for example the version string. To visualize things somewhat better, you can use the ildasm.exe tool (IL disassembler) using the /BYTES switch to view the public key in hex format as well (take a look in the MANIFEST part).
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$..............
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........
EB 52 95 2E 19 32 81 86 D8 E9 09 CA 24 30 74 1A // .R...2......$0t.
A7 C9 C4 A3 73 50 21 B9 D0 E7 84 9B 5C A5 EA 73 // ....sP!.....\..s
39 09 09 76 27 0E 27 A4 43 97 42 32 7D E1 99 A8 // 9..v'.'.C.B2}...
C3 17 29 00 DF 68 FA 4A 99 C6 D8 24 79 02 E3 43 // ..)..h.J...$y..C
A2 C1 F0 CB B2 A9 78 9E E0 8A 1E D7 C8 63 A6 80 // ......x......c..
E4 1A 01 EE D1 81 55 39 80 C4 E5 57 A3 B5 8E 15 // ......U9...W....
C9 B2 51 B9 18 59 EA F7 5F 72 18 F9 65 6C 11 86 // ..Q..Y.._r..el..
4D 98 F4 8B 3B D0 74 80 22 F9 C4 FF BC 00 99 D9 ) // M...;.t.".......
A pretty cool trick to view more info on an assembly is the use of ildasm.exe /adv (uncommented AFAIK). When you're using this option, you'll get another series of options in the view menu to:
- show the COR header (consisting of the PE header - see earlier post - and the CLR header)
- statistics of the file (how much PE headers etc)
- meta info (using CTRL-M, an option that is available in any single mode, except that it is not visible in the menu when running without the /adv flag)
Now you can put the assembly in the GAC by using the gacutil.exe tool with the i option (or u to uninstall). Gacutil will definitely be your friend if you're developing and using the GAC quite often. One thing to keep in mind, different version numbers will result in different copies of an assembly in the GAC (so it might be useful to disable versioning inside the AssemblyInfo file by replacing the * symbols in the version string by static numbers). When the assembly is installed in the GAC, it will appear in the 'folder' %windir%\assembly (hidden as a real folder by an explorer shell extension), or you can visualize the files on the DOS prompt in %windir%\assembly\GAC (every assembly 'name' has a dedicated folder that contains subfolders for any version/key token pair of the assembly).
A nice part of the demo is the one that shows that a signed file only can be executed when it is tamper-free, so nobody has changed the contents of the file. When you change one byte (e.g. by changing the load address in the PE header) in the test.exe file that was compiled using the key, you'll end up with an exception being thrown by the CLR when the file is tried to be loaded:
Unhandled Exception: System.IO.FileLoadException: Strong name validation failed
for assembly 'test.exe'.
File name: "test.exe"
The same tampering in a non-signed file does not result in an error. However, when you plan to change the file after compilation before shipping it, you may consider to use delayed signing. Another reason is the availability of the private key to sign the file (a private key should be private and thus should not be shared with tons of developers!) and thus delayed/partial signing can help. When you're doing this, you only need the public key to sign the file but as a side-effect you loose the tamper-protection (which is in fact an advantage during development time). To do the delayed signing, you only have to specify the [assembly:AssemblyDelaySignAttribute(true)] attribute. Okay, so far so good. As you'll see, the file can be compiled using the public key file only (generated and shared through sn.exe -p); this is the source code:
However, the assembly can't be installed in the GAC right now. The output of the gacutil.exe tool will look as follows:
C:\Documents and Settings\Administrator>gacutil.exe -i test.dll
Microsoft (R) .NET Global Assembly Cache Utility. Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.
Failure adding assembly to the cache: Strong name signature could not be verifie
d. Was the assembly built delay-signed?
To avoid this problem, we should tell the system to skip the verification on the assembly prior to the installation in the GAC. This can be done using sn.exe as well, using the -Vr switch (and by specifying the file name). By doing this, a verification entry will be stored on the system for the specified assembly-token pair:
Verification entry added for assembly 'test,0E4FC5331591798C'
When you want to drop the entry for verification skipping you can use the -Vu (drop entry for one assembly) and -Vx (drop all entries) switches of sn.exe.
Although delayed signing is useful when you're developing the product, it should not be used in production since we're skipping the verification of the assembly (therefore loosing advantages of the sign process). To roll the assembly in production, the responsible developer for the development and management of the deployment of the application should re-sign the assembly using the key pair (this developer is one of the people who have access to that key!) and the sn.exe -R switch.
Enough for the blog now I'll just finalize some presentation stuff on how the CLR determines which version of a referenced assembly to load and some configuration hooks to help the CLR to find the right assemblies and to change the default behavior. And last but not least, I have to prepare the al.exe stuff as well. Will be late again :-(.Del.icio.us
| Digg It