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 17, "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.

Note

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.

If Sarah sends a mail to Julian, and Sarah wants to make sure that no one else but Julian can read the mail, she uses Julian's public key. The message is encrypted using Julian's public key. Julian opens the mail and can decrypt it using his secretly stored private key. This guarantees that no one else except Julian can read Sarah's mail.

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

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 eight bytes of a hash of the public key, and that 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 in so far as the shared assembly comes from the expected publisher.

Figure 15-30 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 15-30

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 than 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 15-31. 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 16, ".NET Security").

image from book
Figure 15-31

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 15-31).

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

image from book
Figure 15-32

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 15-33), the assembly can be installed in the global assembly cache with each successful build.

image from book
Figure 15-33

Then you can use the Global Assembly Cache Viewer to check the version of the shared assembly, and check 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: using the Project 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(string[] args) { 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 15-34), 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.

image from book
Figure 15-34

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 15-35.

image from book
Figure 15-35

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. However, without using a key, you cannot test the assembly and install it in the global assembly cache; but 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 comp0061ny 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 checkbox 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

The 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" 



Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

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