Creating Shared Assemblies

 
Chapter 8 - Assemblies
bySimon Robinsonet al.
Wrox Press 2002
  

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.

In this section, we shall explore:

  • 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 may another person create an assembly using the same name .

COM solved only the first problem by using a globally unique identifier (GUID). The second problem, however, still existed as 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 makes it possible 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 can't be replaced from a different source.

  • A culture . We already talked about cultures when doing localization. Cultures are useful for private assemblies too.

    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 the company, so the key uniquely identifies your company's assemblies.

However, this key this 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.

For development purposes a different public key can be used, and later exchanged easily with the real key. We will look at this feature in the Delayed Signing of Assemblies section.

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. < Description >; 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 that usually is 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. For the rest of you, this is a simple introduction to keys. For encryption, we 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 using the corresponding private key, but not with the public key. This also works the other way around: if something is encrypted using a private key, it can be decrypted 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. Let's look at 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 way guarantees that no one else other than Julian can read Sarah's mail.

There's one problem left: Julian can't be sure that the mail is 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 Julian it's no problem to access Sarah's public key, because this key is public. After decrypting the signature, Julian can be sure that Sarah sent the mail.

Next we will look at how this public/private key principle is used with assemblies.

Integrity Using Strong Names

When creating a shared component, a public/private key pair must be used. The compiler writes the public key to the manifest, creates a hash of all files belonging to the assembly, and signs the hash with the private key. The private key is not stored within the assembly. This way it is 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 is the last eight bytes of a hash of the public key, and that is unique.

At run time, 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 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 that the shared assembly is from the expected publisher:

click to expand

Creating a Shared Assembly

In our example, we will create a shared assembly and a client that uses it.

Before we create the shared assembly we start with a simple Visual C# Class Library project. I'm changing the namespace to Wrox.ProCSharp.Assemblies.Sharing , and the class name to SimpleShared . This class just reads all the lines of a file that's passed inside the constructor in a StringCollection at creation time, and returns a random string of this collection in the GetQuoteOfTheDay() method:

   using System;     using System.Collections.Specialized;     using System.IO;     namespace Wrox.ProCSharp.Assemblies.Sharing     {     public class SimpleShared     {     private StringCollection quotes;     private Random random;     public SimpleShared(string filename)     {     quotes = new StringCollection();     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

To share this component we first need to create a strong name. To create such a name we can use the strong name utility ( 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 . Now we can set the AssemblyKeyFile attribute in the wizard-generated file Assemblyinfo.cs . The attribute must be either set to an absolute path to the key file, or the key file must be addressed relatively from the %ProjectDirectory%\obj\ < configuration > directory, so ../../mykey.snk references a key in the project directory. When starting a build of the project, the key is installed in the Crypto Service Provider ( CSP ). If the key is already installed in the CSP, it's possible to use the AssemblyKeyName attribute instead.

Here are our changes to AssemblyInfo.cs . The attribute AssemblyKeyFile is set to the file mykey.snk :

   [assembly: AssemblyDelaySign(false)]     [assembly: AssemblyKeyFile("../../mykey.snk")]     [assembly: AssemblyKeyName("")]   

After rebuilding, the public key can be found inside the manifest when looking at the assembly using ildasm :

click to expand

Install the Shared Assembly

With a public key in the assembly, it's now possible to install it in the global assembly store using the global assembly cache tool gacutil with the / i option:

  gacutil /i SimpleShared.dll  

We 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 we will now create a C# Console Application called Client . Instead of adding the new project to the previous solution, you should create a new solution so that the shared assembly doesn't get rebuilt when rebuilding the client. I'm changing the name of the namespace to Wrox.ProCSharp.Assemblies.Sharing , and the name of the class to Client .

We reference the assembly SimpleShared in the same way as we are referencing private assemblies: use the menu Project Add Reference , or use the context menu in Solution Explorer . Then select the Browse button to find the assembly SimpleShared . Because the SimpleShared assembly is shared, the CopyLocal property of the reference is automatically set to false , so that the shared assembly is not copied to the client directory; the assembly installed into the global assembly store will be used.

Here's the code for the client application:

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

When viewing the manifest in the client assembly using ildasm we can see the reference to the shared assembly SimpleShared : .assembly extern SimpleShared. Part of this referenced information is the version number we will talk about next, and the token of the public key.

click to expand

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, sn Tp shows the token, and the public key. Pay attention to the use of the uppercase T !

The result of our program with a sample quotes file could now look like this:

click to expand

Delayed Signing of Assemblies

The private key of a company should be safely stored. Most companies don't give all the developers access to the private key; just a few security people have access to it. That's why the signature of an assembly can be added at a later time, 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, we can't test the assembly and install it in the global assembly cache. However, we 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:

  • Firstly we have to 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

  • Next we can extract the public key to make it available to developers. The option p extracts the public key of the keyfile . The file mypublickey.snk only holds the public key.

    sn p mykey.snk mypublickey.snk

  • All developers in the company can use this keyfile mypublickey.snk and set the AssemblyDelaySign and AssemblyKeyFile attributes in the file AssemblyInfo.cs :

       [assembly: AssemblyDelaySign(true)]     [assembly: AssemblyKeyFile("../../mypublickey.snk")]   
  • Before distribution the assembly can be resigned with the sn utility. The R option is used to resign previously signed or delayed signed assemblies.

    sn R MyAssembly.dll mykey.snk

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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