Creating Shared Assemblies


Assemblies can be isolated for use by a single application - not sharing an assembly is the default. When using private assemblies, it’s not necessary to pay attention to any requirements that are necessary for sharing.

This section explores the following:

  • Strong names as a requirement for shared assemblies

  • Creating shared assemblies

  • Installing shared assemblies in the global assembly cache

  • Delayed signing of shared assemblies

Shared Assembly Names

The goal of a shared assembly name is that it must be globally unique, and it must be possible to protect the name. At no time can any other person create an assembly using the same name.

COM solved the first problem by using a globally unique identifier (GUID). The second problem, however, still existed because anyone could steal the GUID and create a different object with the same identifier. Both problems are solved with strong names of .NET assemblies.

A strong name is made of these items:

  • The name of the assembly itself.

  • A version number. This allows it to use different versions of the same assembly at the same time. Different versions can also work side by side and can be loaded concurrently inside the same process.

  • A public key guarantees that the strong name is unique. It also guarantees that a referenced assembly cannot be replaced from a different source.

  • A culture. Cultures are discussed in Chapter 20, “Localization.”

Important 

A shared assembly must have a strong name to uniquely identify the assembly.

A strong (shared) name is a simple text name accompanied by a version number, a public key, and a culture. You wouldn’t create a new public key with every assembly, but you’d have one in your company, so the key uniquely identifies your company’s assemblies.

However, this key cannot be used as a trust key. Assemblies can carry Authenticode signatures to build up a trust. The key for the Authenticode signature can be a different one from the key used for the strong name.

Tip 

For development purposes, a different public key can be used and later be exchanged easily with the real key. This feature is discussed later in the section “Delayed Signing of Assemblies.”

To uniquely identify the assemblies in your companies, a useful namespace hierarchy should be used to name your classes. Here is a simple example showing how to organize namespaces: Wrox Press can use the major namespace Wrox for its classes and namespaces. In the hierarchy below the namespace, the namespaces must be organized so that all classes are unique. Every chapter of this book uses a different namespace of the form Wrox.ProCSharp.<Chapter>; this chapter uses Wrox.ProCSharp.Assemblies. So, if there is a class Hello in two different chapters, there’s no conflict because of different namespaces. Utility classes that are used across different books can go into the namespace Wrox.Utilities.

A company name commonly used as the first part of the namespace is not necessarily unique, so something more must be used to build a strong name. For this the public key is used. Because of the public/private key principle in strong names, no one without access to your private key can destructively create an assembly that could be unintentionally called by the client.

Public Key Cryptography

If you already know about public key cryptography, you can skip this section. This is just a simple introduction to keys. For encryption you have to differentiate between symmetric encryption and public/private key encryption.

With a symmetric key, the same key can be used for encryption and decryption, but this is not the case with a public/private key pair. If something is encrypted using a public key, it can be decrypted with the corresponding private key, but it is not possible with the public key. This also works the other way around: if something is encrypted using a private key, it can be decrypted by using the corresponding public key but not the private key.

Public and private keys are always created as a pair. The public key can be made available to everybody, and it can even be put on a Web site, but the private key must be safely locked away. Following are some examples where these public and private keys are used to explain encryption.

If Alice sends a message to Bob (see Figure 16-17), and Alice wants to make sure that no one else but Bob can read the message, she uses Bob’s public key. The message is encrypted using Bob’s public key. Bob opens the message and can decrypt it using his secretly stored private key. This key exchange guarantees that no one but Bob can read Alice’s message.

image from book
Figure 16-17

There’s one problem left: Bob can’t be sure that the mail comes from Alice. Eve can use Bob’s public key to encrypt messages sent to Bob and pretend being Alice. We can extend this principle. Let’s start again with Alice sending a mail to Bob. Before Alice encrypts the message using Bob’s public key, she adds her signature and encrypts the signature using her own private key. Then she encrypts the mail using Bob’s public key. Therefore, it is guaranteed that no one else but Bob can read the mail. When Bob decrypts the message, he detects an encrypted signature. The signature can be decrypted using Alice’s public key. For Bob it’s not a problem to access Alice’s public key, because this key is public. After decrypting the signature, Bob can be sure that Alice has sent the message.

The next section looks at how this public/private key principle is used with assemblies.

Integrity Using Strong Names

A public/private key pair must be used to create a shared component. The compiler writes the public key to the manifest, creates a hash of all files that belong to the assembly, and signs the hash with the private key, which is not stored within the assembly. It is then guaranteed that no one can change your assembly. The signature can be verified with the public key.

During development, the client assembly must reference the shared assembly. The compiler writes the public key of the referenced assembly to the manifest of the client assembly. To reduce storage, it is not the public key that is written to the manifest of the client assembly, but a public key token. The public key token consists of the last 8 bytes of a hash of the public key and is unique.

At runtime, during loading of the shared assembly (or at install time if the client is installed using the native image generator), the hash of the shared component assembly can be verified by using the public key stored inside the client assembly. Only the owner of the private key can change the shared component assembly. There is no way a component Math that was created by vendor A and referenced from a client can be replaced by a component from a hacker. Only the owner of the private key can replace the shared component with a new version. Integrity is guaranteed insofar as the shared assembly comes from the expected publisher.

Figure 16-18 shows a shared component with a public key referenced by a client assembly that has a public key token of the shared assembly inside the manifest.

image from book
Figure 16-18

Creating a Shared Assembly

In the next example, you create a shared assembly and a client that uses it.

Creating shared assemblies is not much different from creating private assemblies. Create a simple Visual C# class library project with the name SharedDemo. Change the namespace to Wrox.ProCSharp .Assemblies.Sharing and the class name to SharedDemo. Enter the following code. In the constructor of the class, all lines of a file are read into a StringCollection. The name of the file is passed as an argument to the constructor. The method GetQuoteOfTheDay() just returns a random string of the collection.

  using System; using System.Collections.Generic; using System.IO; namespace Wrox.ProCSharp.Assemblies.Sharing {    public class SharedDemo    {       private List<string> quotes;       private Random random;       public SharedDemo(string filename)       {          quotes = new List<string>();          Stream stream = File.OpenRead(filename);          StreamReader streamReader = new StreamReader(stream);          string quote;          while ((quote = streamReader.ReadLine()) != null)          {             quotes.Add(quote);          }          streamReader.Close();          stream.Close();          random = new Random();       }       public string GetQuoteOfTheDay()       {          int index = random.Next(1, quotes.Count);          return quotes[index];       }    } } 

Create a Strong Name

A strong name is needed to share this assembly. You can create such a name with the strong name tool (sn):

 sn -k mykey.snk 

The strong name utility generates and writes a public/private key pair, and writes this pair to a file; here the file is mykey.snk.

With Visual Studio 2005, you can sign the assembly with the project properties by selecting the Signing tab, as shown in Figure 16-22. You can also create keys with this tool. However, you don’t need to create a key file for every project. Just a few keys for the complete company can be used instead. It is useful to create different keys depending on security requirements (see Chapter 19, “.NET Security”).

image from book
Figure 16-22

Setting the signing option with Visual Studio adds the /keyfile option to the compiler setting. Visual Studio also allows you to create a keyfile that is secured with a password. Such a file has the file extension .pfx (see Figure 16-19).

image from book
Figure 16-19

After rebuilding, the public key can be found inside the manifest. You can verify this using ildasm, as shown in Figure 16-20.

image from book
Figure 16-20

Install the Shared Assembly

With a public key in the assembly, you can now install it in the global assembly cache using the global assembly cache tool gacutil with the /i option:

 gacutil /i SharedDemo.dll 

By configuring a post-build event command line with Visual Studio (see Figure 16-21), the assembly can be installed in the global assembly cache with each successful build.

image from book
Figure 16-21

Then you can use the Global Assembly Cache Viewer to check the version of the shared assembly and see if it is successfully installed.

Using the Shared Assembly

To use the shared assembly, create a C# console application called Client. Change the name of the namespace to Wrox.ProCSharp.Assemblies.Sharing. The shared assembly can be referenced in the same way as a private assembly: by using the Project image from book Add Reference menu.

Important 

With shared assemblies the reference property Copy Local can be set to false. This way the assembly is not copied to the directory of the output files but will be loaded from the global assembly cache instead.

Here’s the code for the Client application:

  using System; namespace Wrox.ProCSharp.Assemblies.Sharing {    class Program    {       static void Main()       {           SharedDemo quotes =               new SharedDemo(@"C:\ProCSharp\Assemblies\Quotes.txt");           for (int i=0; i < 3; i++)           {              Console.WriteLine(quotes.GetQuoteOfTheDay());              Console.WriteLine();           }       }    } } 

Looking at the manifest in the client assembly using ildasm (see Figure 16-22), you can see the reference to the shared assembly SharedDemo: .assembly extern SharedDemo. Part of this referenced information is the version number, discussed next, and the token of the public key.

The token of the public key can also be seen within the shared assembly using the strong name utility: sn –T shows the token of the public key in the assembly, and sn –Tp shows the token and the public key. Pay attention to the use of the uppercase P!

The result of your program with a sample quotes file is shown in Figure 16-27.

image from book
Figure 16-27

 "We don't like their sound. And guitar music is on the way out." – Decca Recording, Co., in rejecting the Beatles, 1962 "The ordinary 'horseless carriage' is at present a luxury for the wealthy; and although its price will probably fall in the future, it will never come into as common use as the bycicle." -- The Literary Digest, 1889 "Landing and moving around the moon offer so many serious problems for human beings that it may take science another 200 years to lick them", Lord Kelvin (1824-1907) Press any key to continue ...

Delayed Signing of Assemblies

The private key of a company should be safely stored. Most companies don’t give all developers access to the private key; only a few security people have it. That’s why the signature of an assembly can be added at a later date, such as before distribution. When the global assembly attribute AssemblyDelaySign is set to true, no signature is stored in the assembly, but enough free space is reserved so that it can be added later. Without using a key, you cannot test the assembly and install it in the global assembly cache; however, you can use a temporary key for testing purposes, and replace this key with the real company key later.

The following steps are required to delay signing of assemblies:

  1. Create a public/private key pair with the strong name utility sn. The generated file mykey.snk includes both the public and private key.

     sn –k mykey.snk 

  2. Extract the public key to make it available to developers. The option –p extracts the public key of the keyfile. The file mykeypub.snk only holds the public key.

     sn –p mykey.snk mykeypub.snk 

    All developers in the company can use this keyfile mykeypub.snk and compile the assembly with the /delaysign+ option. This way the signature is not added to the assembly, but it can be added afterward. In Visual Studio 2005, the delay sign option can be set with a check box in the Signing settings.

  3. Turn off the verification of the signature, because the assembly doesn’t have a signature:

     sn –Vr SharedDemo.dll 

  4. Before distribution the assembly can be re-signed with the sn utility. Use the –R option to re-sign previously signed or delayed signed assemblies.

     sn –R MyAssembly.dll mykey.snk 

Important 

The signature verification should only be turned off during the development process. Never distribute an assembly without verification, because it would be possible for this assembly to be replaced by a malicious one.

References

Properties lists a reference count. This reference count is responsible for the fact that a cached assembly cannot be deleted if it is still needed by an application. For example, if a shared assembly is installed by a Microsoft installer package (.msi file), it can only be deleted by uninstalling the application, but not by deleting it from the global assembly cache. Trying to delete the assembly from the global assembly cache results in the error message “Assembly <name> could not be uninstalled because it is required by other applications.”

A reference to the assembly can be set using the gacutil utility with the option /r. The option /r requires a reference type, a reference ID, and a description. The type of the reference can be one of three options: UNINSTALL_KEY, FILEPATH, or OPAQUE. UNINSTALL_KEY is used by MSI where a registry key is defined that is also needed with the uninstallation. A directory can be specified with FILEPATH. A useful directory would be the root directory of the application. The OPAQUE reference type allows you to set any type of reference.

The command line

 gacutil /i shareddemo.dll /r FILEPATH c:\ProCSharp\Assemblies\Client "Shared Demo" 

installs the assembly shareddemo in the global assembly cache with a reference to the directory of the client application. Another installation of the same assembly can happen with a different path, or an OPAQUE ID like in this command line:

 gacutil /i shareddemo.dll /r OPAQUE 4711 "Opaque installation" 

Now, the assembly is in the global assembly cache only once, but it has two references. To delete the assembly from the global assembly cache, both references must be removed:

 gacutil /u shareddemo /r OPAQUE 4711 "Opaque installation" gacutil /u shareddemo /r FILEPATH c:\ProCSharp\Assemblies\Client "Shared Demo" 

Important 

To remove a shared assembly, the option /u requires the assembly name without the file extension DLL. On the contrary, the option /i to install a shared assembly requires the complete file name including the file extension.

Tip 

Chapter 15, “Deployment,” deals with deployment of assemblies, where the reference count is being dealt with in an MSI package.




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

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