Code must be uniquely identified to be shared. The Global Assembly Cache (GAC) does not support an assembly that does not have a strong name. You should seriously consider giving even your private assemblies a strong name that uniquely identifies your code. A strong name provides a unique identifier to a set of code that is statistically impossible to be duplicated. In other words, a strong name is a means of uniquely identifying this code as yours. A strong name is part of an AssemblyName that consists of four parts:
Name—Visual Studio assigns a name to an assembly that is the name of the output DLL, minus the DLL suffix. This name can be any name that you choose.
Publisher Key—The actual public key stored in the assembly is about 160 bits. When the full name of the assembly is referenced, you see a key token. Because the full public key takes so many bytes to represent, Microsoft hashes the public key and takes the last eight bytes of the hashed value to form what is known as a key token. This eight-byte string is unique.
Version—This four-part string identifies the version of the assembly. It refers to three logical parts: the assembly version, build number, and revision number. Physically, the assembly version consists of a major and a minor version number. Assuming that it is a policy for a company to build an assembly every day, the build number should increment with every build. If it is necessary for two builds to occur in a day, then the revision should be incremented. If a version attribute is specified with a wildcard such as [assembly: AssemblyVersion("1.2.*")], then Visual Studio takes care of incrementing the build and revision number for you.
Culture—This is a string of the form xx-yy, where xx is the language and yy is the country. Specifying only xx is known as language neutral, and not specifying anything or specifying a blank culture string is known as a neutral culture. Chapter 18, “Globalization/Localization,” goes into more detail about culture. An assembly's culture can be assigned at link time using the al.exe utility (al /c[ulture]:<xx-yy>) or with an assembly attribute such as [assembly: AssemblyCulture("xx-yy")]. Assemblies lacking MSIL code that have just resource information are known as satellite assemblies. These satellite assemblies are important in building international applications.
If the assembly is missing the public key or the public key token, then it does not have strong name. Figure 6.1 summarizes the important information that is contained in an AssemblyName.
When any portion of the AssemblyName is different, the runtime considers it a different assembly. Most commonly, you see differences in the version (to reflect bug fixes, updates, and so on) and culture (to reflect support for different cultures). After an assembly is uniquely identified with a strong name, the strong name can be further used to enforce runtime version checks, enforce binding policy, provide security evidence, and prevent spoofing or tampering. These topics are covered later in this chapter.
Figure 6.2 shows the process of linking the main assembly with a referenced assembly.
Particularly notice that the main assembly has the version and a key token for the referenced assembly. The first benefit that this gives you is security against spoofing or tampering. When an assembly is given a strong name, the entire contents of the file are hashed with the private key. To replace a file or even slightly modify it successfully, the perpetrator would need to know the private key to successfully generate a hash for the file and the embedded public key.
Note
You can modify the file's contents to prove to yourself that adequate security is available. To complete this demo, the file was simply modified with a binary editor. You could modify the file in other ways. You could convert the file to IL, edit it, and recompile the IL. You could also use reflection to modify the string table. Another method could be to use the set of unmanaged APIs detailed in Chapter 4 to modify portions of the file containing the assembly. You could also memory map the filewith write permission and modify it that way. The way you decide to prove that a hash is indeed being generated is your choice. You could even try all of these suggestions to see if you are able to thwart the hash security.
If you try to modify only one character of the assembly, tools such as PEVerify and ILDasm work fine. After the assembly has been generated, use ILDasm to look at the edited version. Listing 6.4 illustrates the modified IL.
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 17 (0x11) .maxstack 1 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ldstr "I am the neutraf version of Foo." IL_000b: call void [mscorlib]System.Console::WriteLine(string) IL_0010: ret } // end of method Foo::.ctor |
Notice the misspelling of neutral. Nothing so drastic has been done to make the file unreadable. However, the assembly fails to load with an exception:
Unhandled Exception: System.IO.FileLoadException: Strong name validation failed for assembly 'FooLib'.
Now when a hash is computed, it is different because of the single character change that was made to the file. If you edit this DLL and change the letter back, the assembly loads without error. Giving an assembly a strong name has more benefits than just being able to share the assembly. It is recommended that all assemblies be given a strong name. Specifically, the following presents some additional considerations for strongly naming an assembly:
After an assembly is signed, it can be shared and redistributed.
Runtime version checking is possible after an assembly has a strong name.
Runtime policy checking is possible with strong-named assemblies.
If the strong-named assembly is shared, you can take full advantage of side-by-side deployment.
You have learned the benefits of giving an assembly a strong name. You will now learn a step-by-step approach to developing and deploying an assembly with a strong name:
18.223.119.17