Section 5.3. Strong Assembly Names


5.3. Strong Assembly Names

As discussed in the previous section, an assembly can be either private or shared. A private assembly resides in the client application directory, whereas a shared assembly resides in the GAC. Although private assemblies are straightforward and easy to use, there are two cases in which you should consider using shared assemblies. The first case is to support side-by-side execution of different versions of the same assembly. The second case is to share assemblies between multiple client applications. Sharing allows multiple applications to take advantage of an improved compatible version as soon as it's available, without patching up each application's private assemblies individually. Framework and class library vendors tend to use shared assemblies.

The client assembly can specify another location where its private assemblies are found using the .NET Configuration tool (presented later in this chapter).


The GAC is likely to contain assemblies from many vendors, so .NET must provide a way to uniquely identify shared assemblies. A friendly name such as MyAssembly isn't good enough, because multiple vendors might come up with identical friendly names for their assemblies. .NET must have a way to guarantee assembly uniqueness. There are a number of ways to produce uniqueness. COM used globally unique identifiers (GUIDs)unique 128-bit numbers assigned to each component. Using a GUID is simple enough, but it has a fatal flaw: any party can see it, duplicate it, and swap in a new, potentially malicious component that uses the copied GUID. COM GUIDs provided uniqueness, because at any point in time there could be only one registered component with a given GUID on any machine; however, GUIDs don't provide authenticity and integrity.

For both uniqueness and authenticity, .NET shared assemblies must contain unique proof of their creator and original content. Such proof is called a strong name. .NET uses a pair of encryption keys to create a strong name. The pair contains two keys, called the public and private keys. There is nothing special about the private key, other than its designation and the way you treat it. What is important about the public and private keys is that anything encrypted with one key can only be decrypted with the other. For example, anything encrypted with the private key can only be decrypted with the public key; the private key cannot decrypt its own encryption.

During compilation, the compiler uses the private key to encrypt the assembly's hash. (Recall that the manifest contains not only the assembly's friendly name and version number but also a hash of all the modules comprising the assembly.) The resulting encrypted blob is therefore a unique digital signature, ensuring both origin and content. The compiler then appends that signature and the public key to the manifest. Every client thus has access to the public key. The private key, on the other hand, should be kept inaccessible, under lock and key (literally). During compilation, a client referencing a strongly named assembly records in its assembly manifest a token representing the public key of the server assembly, in addition to the server assembly version number:

     .assembly extern MyServerAssembly     {       .publickeytoken = (22 90 49 07 87 53 81 9B )       .ver 1:2:3:4     } 

When the client assembly triggers .NET to try to find the server assembly, .NET starts its search algorithm (first in the GAC, then in the application folder). If an assembly with a matching friendly name is found, .NET reads its public key, computes its token, and compares that with what the client expects. .NET then decrypts the digital signature using the public key and compares the value of the assembly's hash captured in the digital signature with a hash of the assembly found. If the two match, then it has to be the assembly the client expects.

Because the private key is unique and is kept safe by the organization that created it, a strong name ensures that no one can produce an assembly with an identical digital signature. Using the encryption keys, .NET maintains both uniqueness and authenticity.

An assembly with only a friendly name can add a reference to any strongly named assembly. For example, all the .NET Framework classes reside in strongly named assemblies, and every client assembly can use them freely. The reverse, however, isn't true: a strongly named assembly can reference and use only other strongly named assemblies. The compiler refuses to build and assign a strong name to any assembly that uses types defined in assemblies equipped with only friendly names. The reason is clear: a strong name implies that the client can trust the assembly's authenticity and the integrity of the service provider. With a strong name, the client also assumes that it will always get this exact version. This cycle of trust is breached if the strongly named assembly can use other assemblies with potentially dubious origins and unverifiable versions. Therefore, strongly named assemblies can reference only other strongly named assemblies.

5.3.1. Signing Your Assembly

You can instruct Visual Studio 2005 to both create the encryption keys for you and sign the assembly. In the project properties, select the Signing pane (see Figure 5-3).

Figure 5-3. Signing the assembly


By checking the "Sign the assembly" checkbox, you instruct the compiler to sign the assembly with the key file specified. You can select to use a key from an existing file, or you can create a new file. The "Choose a strong name key file" combo box allows you to either create a new key or to browse to an existing file.

The Signing pane also contains the settings used to sign a ClickOnce application manifest using a certificate. Signing a ClickOnce application manifest is discussed in Chapter 12.


5.3.1.1 Generating a strong name key file

If you choose to create a new key, Visual Studio 2005 will bring up the Create Strong Name Key dialog, shown in Figure 5-4.

Figure 5-4. Creating a strong name key file


Strong name key files come in two flavors: plain and password-protected. If you uncheck the "Protect my key file with a password" checkbox, Visual Studio 2005 will generate a file with the name specified and with the .snk (Strong Name Key) extension. However, keeping the key in this raw format carries a great liability: since the strong name uniquely identifies a component vendor, any malicious party who compromises the private key will be able to produce components and pass them off as coming from that vendor. To reduce the risk, I strongly recommend always choosing to protect the key using a password. Visual Studio 2005 insists that the password specified has six or more characters. If you check the "Protect my key file with a password" checkbox, Visual Studio 2005 will generate a file with the name specified and with the .pfx (Personal Information Exchange) extension. The .pfx file is more secure than an .snk file, because whenever another user tries to use the file for the first time, that user will be prompted for the password. Once the user provides the correct password, Visual Studio 2005 extracts the key from the .pfx file, stores it in a certificate container, and uses the container from that point onward, which means that the user does not have to provide the password again.

5.3.1.2 Selecting an existing strong name key file

When you choose to browse for an existing key, Visual Studio 2005 will bring up a dialog letting you browse for either .snk or .pfx files. Once you have selected a file, Visual Studio 2005 copies that file to the project folder. There is no way to share an existing file; every project must have its own physical copy of the file.

Solution Info File

Consider a .NET application comprised of multiple projects, all part of a single solution. Often, you want all the projects in the solution to have the same version number. Several of the assembly attributes commonly found in the AssemblyInfo.cs file (such as the company name and the copyright statement) are relatively static and are usually identical across projects in the same solution. However, the version number is typically a dynamic value. If you want to enforce a uniform version number, by default you have to update that value in every assembly. This, of course, is tedious, not to mention error-prone.

Fortunately, there is an easy workaround. First, factor the solution-wide attributes into a SolutionInfo.cs file:

     [assembly: AssemblyCompany("MyCompany")]     [assembly: AssemblyProduct("MyProdcut")]     [assembly: AssemblyCopyright("Copyright © MyCompany                                                         2005")]     [assembly: AssemblyTrademark("MyTrademark")]     [assembly: AssemblyVersion("1.2.3.4")] 

The SolutionInfo.cs file contains the solution-wide version number, the company's name and copyright notice, and a trademark (if any). Place that file in the root of the solution. To add it to the solution, right-click on the solution and select Add Add Existing Item from the pop-up menu. Then select Solution Items in the solution and place the SolutionInfo.cs file in it. Next, edit each project's AssemblyInfo.cs file so that it contains only project-specific attributes, such as the assembly title, description, and culture:

     [assembly: AssemblyTitle("MyAssembly")]     [assembly: AssemblyDescription("Some Description")]     [assembly: AssemblyConfiguration("")]     [assembly: AssemblyCulture("")] 

Finally, you need to link the projects to SolutionInfo.cs. In each project, select Add Add Existing Item... from the projects pop-up context menu. Browse to the root of the solution, and highlight the SolutionInfo.cs file. Do not double-click on it, because that will simply make a copy of the file and add it to the project. Instead, click the drop-down arrow to the right of the Add button, and select Add As Link. This will add a link to the SolutionInfo.cs file to the project. The code in a linked file is part of the project, while the file itself is not.

Note that when using a SolutionInfo.cs file to hold the common attributes, you cannot use the Assembly Information dialog to edit individual projects' assembly information. Doing so will add duplicate assembly attributes to the individual AssemblyInfo.cs files.

For consistency's sake, you can move the links to SolutionInfo.cs to each project's Properties folder. The solution should now look like Figure 5-5. Note the shortcut symbol in the lower-left corner of the linked files' icons.


5.3.1.3 Handling large organizations' keys

An organization's strong-name private key should be kept under lock and key. If an organization's private key is compromised, less reputable parties can impersonate that organization and distribute their own components as originals. The security and reputation (as well as potential legal liability) implications cannot be underestimated. Therefore, access to the private key of a large organization has to be restricted, preferably to just the build team (with a backup copy in the senior manager's vault). However, this raises a few questions. How can you perform your intermediate internal builds without the private key? And how can the client assembly reference your assembly? To address these issues, .NET provides delay signing. When you check the "Delay sign only" checkbox on the Signing pane (see Figure 5-3), the compiler will embed the public key in the assembly's manifest, but will not generate the digital signature. This allows you to install the assembly in the GAC and have client assemblies reference it (which requires only the public key), so you can carry out your internal builds and testing cycles. Merely delay-signing the assembly will allow you to build the assembly and its clients, but not to run it, because the strong name verification process will fail during load time.

For delayed signing, you can use the -p switch of the SN.exe utility to extract the public key from a file containing both public and private keys:

     SN.exe -p  MyKeys.pfx MyPublicKey.pfx 

Figure 5-5. Sharing a version number across the solution


You can now freely distribute the public key file within the organization. To run the delay-signed assembly, you will need to turn off the digital signature verification of the assembly, using the -Vr switch:

     SN.exe -Vr  MyAssembly.dll 

You still need to sign the assembly with the actual private key before shipping it, though. You can either uncheck the "Delay sign only" checkbox and provide the complete key file, or use the -R switch of the SN.exe utility to resign the assembly using both keys:

     SN.exe -R  MyAssembly.dll MyKeys.pfx 

5.3.2. Strong Names and Private Assemblies

.NET distinguishes between strongly named private assemblies and private assemblies that have only friendly names. If a private assembly has only a friendly name, .NET records the private assembly version in the client's assembly manifest, as shown earlier:

     .assembly extern MyServerAssembly     {       .ver 1:2:3:4     } 

However, in reality .NET ignores version incompatibility between the client application and the private assembly, even though the version number is available in the client's manifest. If an application references a private assembly with a friendly name only, that private assembly is always the one used, and .NET doesn't look in the GAC at all. For example, imagine a client application that was compiled and deployed with version 1.0.0.0 of a private server assembly that doesn't have a strong name. Later, version 2.0.0.0 (an incompatible version, according to .NET's strict versioning rules) becomes available. If you copy version 2.0.0.0 to the client application directory, it overrides version 1.0.0.0. At runtime, .NET will try to load the new version. If the new version is backward-compatible the client application will work just fine, but if version 2.0.0.0 is incompatible with 1.0.0.0 the client application may crash. However, unlike with DLL Hell, the problem will be confined to just that client application and won't affect other applications with their private copies of other versions of the assembly. .NET behaves this way because it assumes that the client application administrator knows about versions and compatibility and has judged that the flexibility of being able to copy version 2.0.0.0 is worth the risk.

On the other hand, if the private assembly does contain a strong name, .NET zealously enforces its version-compatibility policy. .NET records in the client's manifest the token representing the public key of the private assembly and insists on a version match. Going back to the example just discussed, in this case the assembly resolver will attempt to look up a compatible version in the GAC. If no compatible version is found in the GAC, .NET will throw an exception because the private version 2.0.0.0 will be considered incompatible. The important conclusions from this are:

  • Private assemblies with only friendly names must be backward-compatible.

  • Private assemblies with strong names don't need to be backward-compatible, because the GAC can still contain an older compatible version.

  • Even if a private assembly with a strong name is backward-compatible (with respect to content), if the version number isn't compatible, it results in an exception (unless the GAC contains an older compatible version).

  • The private assembly deployment model is intended to work with friendly names only.

Interop Assemblies and Strong Names

.NET assemblies can call methods of legacy COM components by generating an interoperation (interop) assembly. The interop assembly contains metadata about the COM types, and at runtime .NET is responsible for bridging the two component technologies transparently (from the .NET client's perspective). To generate an interop assembly, use the COM tab on the Add Reference dialog in Visual Studio 2005. However, the dialog generates an interop assembly with only a friendly name. If the .NET client is a strongly named assembly, it won't be able to use that interop assembly. To address that problem, use the /keyfile: switch of the TLBImp command-line utility to specify the name of a key file to use to assign a strong name to the interop assembly:

     tlbimp <COM TLB or DLL name> /out:<interop assembly name>     /keyfile:<key file name> 


5.3.3. Friend Assemblies and Strong Names

Chapter 2 introduced the InternalsVisibleTo attribute, used to designate a friend client assembly. A friend assembly can access all internal types and internal members in the server assembly. However, a server assembly without a strong name can only designate as a friend assembly a client assembly that also lacks a strong name, by specifying the name of the assembly in question to the InternalsVisibleTo attribute:

     [assembly: InternalsVisibleTo("MyClient")] 

Obviously, this is not a very secure or safe way of exposing your internal types, because all a third-party assembly has to do to access the server assembly internals is change its friendly name. Use of the InternalsVisibleTo attribute should be restricted to assemblies developed in conjunction with the server assembly.

To provide an additional degree of security to the InternalsVisibleTo attribute, a server assembly with a strong name can only designate as a friend assembly a client assembly that also has a strong name. You must include both the name of the client assembly in question and the token of its strong name to the InternalsVisibleTo attribute:

     [assembly:InternalsVisibleTo("MyClient,PublicKeyToken=745901a54f88909b")] 

Doing so indicates that the server assembly grants permission to access its internals only to client assemblies with a matching friendly and strong name, and the client-side compiler enforces that restriction.

5.3.4. Installing a Shared Assembly

Once you assign a strong name to an assembly, you can install it in the GAC. The GAC is located in a special folder called assembly under the Windows folder. There are a number of ways to view and manipulate the GAC. .NET installs a Windows shell extension that displays the assemblies in the GAC using the File Explorer. You can navigate to the GAC and simply drag and drop a shared assembly into the GAC folder. You can also use the File Explorer to remove assemblies from the GAC. The second option is to use a command-line utility called GACUtil that offers a number of switches. You typically use GACUtil in your application's installation program. The third option for managing the GAC is to use a dedicated .NET administration tool called the .NET Configuration tool. You will see this tool used later in this chapter to configure custom version-binding policies and in Chapter 12 for specifying security policies. The .NET Configuration tool is a Microsoft Management Console snap-in. After a normal .NET installation, you can find it at <Windows>\Microsoft.NET\framework\<version>\mscorcfg.msc or as a Control Panel applet under the Administrative Tools folder. Figure 5-6 shows the .NET Configuration tool.

Figure 5-6. The .NET Configuration tool for managing the GAC


After launching the .NET Configuration tool, click on the Assembly Cache item in the lefthand tree pane, and then click on "Add an Assembly to the Assembly Cache" in the righthand pane. This brings up a file locator dialog. Browse to where the assembly is located, and select it. This adds the assembly to the GAC. You can now view the assemblies in the GAC using the .NET Configuration tool. Select "View List of Assemblies in the Assembly Cache" in the righthand pane. Figure 5-7 shows a typical view of the assemblies in the GAC. The view contains each assembly's friendly name, version number, locale, and public-key token. You would typically use this view to look up the version numbers of assemblies in the GAC, to troubleshoot some inconsistency, or just to verify that all is well.

Figure 5-7. The GAC view of the .NET Configuration tool


Only members of the system administrators group can add assemblies to or remove assemblies from the GAC.


5.3.4.1 Verifying shared assembly mode

For some development and debugging purposes, it's useful to programmatically verify that the server assembly is being used as a shared assembly. You can take advantage of the Assembly type's GlobalAssemblyCache Boolean property to do this. GlobalAssemblyCache is set to true when the assembly is loaded from the GAC. You

can also use the Location property to inform the user where the assembly is actually loaded from. Example 5-1 shows the implementation and use of the AssertSharedAssembly( ) helper method, which verifies whether a given assembly is indeed loaded from the GAC. If not, the method alerts the user and specifies where the assembly is actually loaded from.

Example 5-1. The AssertSharedAssembly( ) method
     using System.Reflection;            static void AssertSharedAssembly(Assembly assembly)     {        bool shared = assembly.GlobalAssemblyCache;        Debug.Assert(shared);        if(shared == false)        {           string message = @"The assembly should be used as a shared assembly.                              It was loaded instead from: ";           string currentDir  = assembly.Location;           MessageBox.Show(message + currentDir);        }     }     Assembly assembly = Assembly.GetExecutingAssembly(  );     AssertSharedAssembly(assembly); 

Writing code that depends on the assembly's deployment mode or location is wrong. Methods such as AssertSharedAssembly( ) should be used only for troubleshooting during developmentthat is, when you're trying to analyze what's going on and why the assembly isn't being used as a shared assembly.


5.3.4.2 Side-by-side execution

The GAC can store multiple versions of an assembly with the same name. This is because although it uses the Windows filesystem, the GAC is not a flat structure. For each assembly added to the GAC, .NET creates a set of folders whose path is comprised of the assembly name, its version number, and a token of the assembly's public key. For example, such a path could be:

     C:\WINDOWS\assembly\GAC_MSIL\MySharedAssembly\1.0.0.0_  _745901a54f88909b\     MySharedAssembly.dll 

As a result, two different versions of the same assembly will be placed in two different folderstwo assemblies with the same friendly name and version number but from different vendors will be placed in different folders because the public-key tokens will be different. Since each client manifest contains the referenced assembly's friendly name, its version number, and a token, that information is sufficient for the assembly resolver to locate the correct assembly in the GAC and load it. This enables side-by-side execution: two clients that reference different versions of the same assembly can coexist on the same machine, because each client application will get the assembly it expects out of the GAC.



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net