Monday, June 1, 2009

.NET Assembly

What is a .NET assembly?
An assembly is the primary building block of a .NET application and can take the form of a dynamic link library (DLL) or executable file (EXE). An assembly is a collection of functionality that is built, versioned, and deployed as a single implementation unit.

What does an assembly contain?
A .NET assembly may contain the following elements:

Assembly Manifest - Metadata that describes the assembly and its contents (see below)
Source Code - Compiled into Microsoft intermediate language (MSIL)
Type Metadata - Defines all types, their properties and methods, and most importantly, public types exported from this assembly
Resources - Icons, images, text strings and other resources
The assembly manifest is required; the other elements are optional.

What is an assembly manifest?
An assembly manifest is metadata inside an assembly that describes everything there is to know about the assembly and its contents. The manifest contains:

Strong Name - The assembly's name, version, culture, optional processor architecture, and public key (for shared assemblies)
File Contents - Name and hash of all files in the assembly
Type List - Types defined in the assembly, including public types that are exported from the assembly
Resource List - Icons, images, text strings and other resources contained in the assembly
Dependencies - Compile-time dependencies on other assemblies
Security - Permissions required for the assembly to run properly



What is a multi-file assembly?
An assembly can consist of one or more files called modules. Exactly one of these modules contains the assembly manifest. Note that the files in a multi-file assembly can reside in separate locations and are linked together with the assembly manifest.

Multi-file assemblies are rare, and Visual Studio doesn't directly support their creation. The most common reason for multi-file assemblies is when a single assembly combines code from multiple programming languages. (more)

What is the difference between a private and shared assembly?
A private assembly is used only by a single application and is stored in that application's installation folder (or subfolder therein). The name of a private assembly name must be unique within the application that uses it.

A shared assembly is used by multiple applications and is typically stored in a global folder known as the Global Assembly Cache (GAC). When building an assembly, a developer must specifically choose to build it as a shared assembly by giving it a cryptographically strong name. For example, the .NET Framework is a collection of shared assemblies.

What is the difference between an assembly and a namespace?
Namespaces are logical, whereas assemblies are physical.

A namespace is a logical naming scheme to group related types. Namespaces can contain other namespaces to form a hierarchy. The "fully qualified name" of a type is its namespace followed by its type name, separated by a period (for example, System.Windows.Forms.Button). Type names must be unique within a namespace, but the same type name can be used in different namespaces.

An assembly is a physical deployment scheme to group related types. An assembly can contain one or many namespaces. A namespace can exist in one or many assemblies.

What are assembly attributes?
Assembly attributes are values (typically set by the developer) that provide additional information about a .NET assembly. Assembly attributes are grouped as follows:

Identity Attributes - Determine the identity of an assembly: name, version, culture and flags.
Informational Attributes - Provide additional information about an assembly: company name, product name, copyright, trademark, and file version.
Manifest Attributes - Provide information in the assembly manifest: assembly title, description, alias, and configuration (such as debug or release).
Strong Name Attributes - Used to help set an assembly's strong name, including key file, key name, and whether delay signing is used.
How do I set assembly attributes?
There are two ways to set the attributes for an assembly in your development project. Using Visual Studio 2005:

Option 1: AssemblyInfo File
In the Visual Studio Solution Explorer, navigate to the Properties folder, then open the AssemblyInfo.cs file. You can directly edit the attributes in the AssemblyInfo file:


// General information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle( "MyAssembly" )]
[assembly: AssemblyDescription( "Description of MyAssembly" )]
[assembly: AssemblyConfiguration( "" )]
[assembly: AssemblyCompany( "MyCompany" )]
[assembly: AssemblyProduct( "MyProduct" )]
[assembly: AssemblyCopyright( "Copyright (c) 2007 MyCompany Inc" )]
[assembly: AssemblyTrademark( "MyProduct is a trademark of MyCompany Inc." )]
[assembly: AssemblyCulture( "" )]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible( false )]

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid( "b7bfd909-f048-4d5b-9f33-1d0642398e4f" )]

// Version information for an assembly consists of the following four values:
// Major Version
// Minor Version
// Build Number
// Revision
[assembly: AssemblyVersion( "1.0.0.*" )]
[assembly: AssemblyFileVersion( "1.0.0.0" )]



Option 2: Project Properties
Select your assembly project in the Visual Studio Solution Explorer. Click the Properties button. Select the Application tab. Then click the Assembly Information button. The following dialog will appear, allowing you to edit the assembly attributes:



How do I access a loaded assembly?
When working with assemblies, be sure to include the Reflection namespace:

using System.Reflection;
To access your application's main assembly (i.e., the executable file):

Assembly asm = Assembly.GetExecutingAssembly();
To access an external assembly loaded by your application, call GetAssembly with a type defined in the external assembly:

Assembly asm = Assembly.GetAssembly( typeof( MyType ) );
How do I retrieve assembly attributes programmatically?
Once you have the Assembly object as shown above, you can obtain its identity attributes from its associated AssemblyName object:

AssemblyName asmName = asm.GetName();
Console.WriteLine( "Name={0}, Version={1}, Culture={2}, ProcessorArchitecture={3}",
asmName.Name, asmName.Version, asmName.CultureInfo, asmName.ProcessorArchitecture );For all other assembly attributes, you can load the attribute directly with the GetCustomAttributes method:

// assembly description attribute
string asmDesc = ((AssemblyDescriptionAttribute)asm.GetCustomAttributes(
typeof( AssemblyDescriptionAttribute ), false )[0]).Description;
// assembly title attribute
string asmTitle = ((AssemblyTitleAttribute)asm.GetCustomAttributes(
typeof( AssemblyTitleAttribute ), false )[0]).Title;
// etc.

To view all attributes for an assembly:

object[] attributes = asm.GetCustomAttributes( true );
foreach (object obj in attributes)
{
Console.WriteLine( obj.ToString() );
}

What is a strong name?
A strong name is a .NET assembly name combined with its version number and other information to uniquely identify the assembly. This allows multiple versions of the same assembly to peacefully co-exist in the global assembly cache, where shared assemblies are typically stored.

A strong name consists of five parts:

Simple Name - Usually the name of the file (without the extension) that contains the assembly
Public Key - RSA cryptographic public key that helps verify the assembly's authenticity
Version - Four-part version number, in the form of Major.Minor.Build.Revision
Culture - Target audience for the assembly, such as "neutral" (default audience), "en-us" (English - United States) or "fr" (France) etc.
Processor Architecture - Defines the assembly's format, such as MSIL (intermediate language) or x86 (binary for Intel x86 processors)
An example strong name is "Mini-Launcher, Version=0.3.612.24542, Culture=neutral, PublicKeyToken=ffa52ed9739048b4, ProcessorArchitecture=MSIL".

Why use strong names?
Strong names are required to store shared assemblies in the global assembly cache (GAC). This is because the GAC allows multiple versions of the same assembly to reside on your system simultaneously, so that each application can find and use its own version of your assembly. This helps avoid DLL Hell, where applications that may be compiled to different versions of your assembly could potentially break because they are all forced to use the same version of your assembly.

Another reason to use strong names is to make it difficult for hackers to spoof your assembly, in other words, replace or inject your assembly with a virus or malicious code.

What is a strong name key file?
A strong name key file has a .snk extension and contains a unique public-private key pair. You use the strong name key file to digitally sign your assembly (see below). Note that this type of file is not secure, as the private key in a .snk file can be easily compromised.

For added protection, Visual Studio can encrypt a strong name key file, which produces a file with the .pfx (Personal Information eXchange) extension. The .pfx file is more secure because whenever someone attempts to use the encrypted key, she will be prompted for the password.

How do I create a strong name key file for a .NET assembly?
Visual Studio 2005 makes it easy to create a strong name key file:

Select your assembly project in the Visual Studio Solution Explorer.
Click the Properties button. The project properties will appear in the main window.
Select the Signing tab:
Check the Sign the assembly checkbox.
In the Choose a strong name key file drop-down, select New. The "Create Strong Name Key" dialog appears:
In the Key file name text box, type the desired key name. Typically this is the name of your assembly but can be anything. Visual Studio will automatically append the proper file extension.
If desired, you can protect the strong name key file with a password. To do so, check the Protect my key file with a password checkbox, then enter and confirm the password.
Click the OK button.
Now when you compile your project, Visual Studio will automatically sign your assembly with the new strong name key you have just created.

Or if you prefer to use the command-line, you can create a key pair file with the strong name utility sn.exe in the .NET SDK, for example:

sn -k MyKey.snk

Then you reference that key file to when compiling your code with the C# compiler csc.exe:

csc /keyfile:MyKey.snk MyCodeFile.cs

What does it mean to sign an assembly?
.NET uses digital signatures to verify the integrity of an assembly. The signatures are generated and verified using public key cryptography, specifically the RSA public key algorithm and SHA-1 hash algorithm. The developer uses a pair of cryptographic keys: a public key, which everyone can see, and a private key, which the developer must keep secret.




To create a strong-named assembly, the developer signs the assembly with his private key when building the assembly. When the system later loads the assembly, it verifies the assembly with the corresponding public key.

How do I sign an assembly?
When you compile your assembly with a strong name key file, the compiler digitally signs the assembly:

The compiler calculates the cryptographic digest (a hash) of your assembly contents. This is known as the compile-time digest. Modifying just a single byte of your assembly will change this hash value.
The compiler encrypts the digest using the 1024-bit private key from your public-private key pair file.
The compiler then stores the encrypted digest and public key into the assembly.
How does the system verify a signed assembly?
Sometime later, when an application attempts to load your signed assembly:

The .NET assembly loader calculates the cryptographic digest of the current assembly contents. This is known as the run-time digest.
The loader extracts the stored compile-time digest and public key from the assembly.
The loader uses the public key to decrypt the compile-time digest.
The loader then compares the run-time digest with the decrypted compile-time digest to ensure they match. If not, then the assembly has been modified since you compiled it, and the assembly load fails.
This process is different when loading shared assemblies from the GAC. Because assemblies are verified when they are first installed into the GAC–and they cannot be modified while in the GAC–the .NET assembly loader does not verify an assembly when loading it from the GAC. This can improve the startup speed of your application if you load many shared assemblies.

What is delay signing?
Delay signing is signing an assembly with its strong name public key, which is freely distributable, instead of using the private key as usual. This allows developers to use and test a strong-named assembly without access to the private key. Then at a later stage (typically just before shipping the assembly), a manager or trusted keyholder must sign the assembly with the corresponding private key. (more)

How do I protect my private keys?
Private keys must remain secret. A hacker with your private key could spoof your signed assemblies by replacing or injecting them with a virus or other malicious code. There are a few strategies you can use to protect your private keys:

Password Protection. As shown above, Visual Studio will allow you to protect your strong name key file with a password.
Delay Signing. As mentioned above, delay signing enables your development team to build and test your assembly without access to the private key.
Cryptographic Container. One of the most secure ways to protect your strong name key is to store it in a secure cryptographic container (see sidebar "Protecting Your Keys" in this article).
How many private keys should I have?
There are three main strategies for how many private keys a developer should use:

One private key for all your applications and assemblies
One private key for each application (an application may have multiple assemblies)
One private key for each assembly
Which option to use depends on your security situation and risk tolerance. With option 1, it's easier to keep a single key secure, but if your one private key is compromised, then all of your assemblies are compromised. With option 3, there are more keys to manage and hence lose, but if one key is compromised, then only one of your many assemblies is compromised. I recommend option 2 or 3 to reduce your overall exposure.

Are there problems with using strong names?
Strong names are not perfect. There are some issues to consider when using strong names:

Requires Exact Match. If you use strong names, your application or library must load the assembly with the exact strong name that you specify, including version and culture. Note that you can bypass this requirement with a publisher policy (to be discussed in a future article).
Cannot Lose Private Key. If your private key is lost or stolen, the security of your assembly is compromised. You will be forced to re-issue a new assembly signed with a new public-private key pair.
Cannot Stop Full Replacement. Strong names cannot prevent a hacker from removing the strong name signature, maliciously modifying your assembly, re-signing it with his own key, and then passing off his assembly as yours. The user must have some way to ensure the public key they have from your assembly is valid and truly came from you. Note that you can use more sophisticated signing schemes (such as Authenticode) to help with this issue.


Where are shared assemblies stored?
A shared assembly is used by multiple applications. You can store shared assemblies pretty much anywhere. However, the challenge is to ensure that all dependent applications can find the shared assembly. The recommended way to ensure this is to store shared assemblies in the Global Assembly Cache.

What is the Global Assembly Cache (GAC)?
The Global Assembly Cache is a system folder (typically C:\Windows\assembly) that contains .NET shared assemblies. Companies that wish to share assemblies with others or even just among their own applications typically store these shared assemblies in the GAC. All of the .NET framework libraries are stored in the GAC.

Why should I store my shared assemblies in the GAC?
You should install assemblies in the GAC only when necessary. As a general guideline, assemblies should be kept private and stored in the application's folder unless you explicitly need to share them. There are some benefits to storing shared assemblies in the GAC:

Global Location

The GAC is the known standard location for .NET shared assemblies. When an application attempts to load an assembly, the GAC is one of the first places it looks. If there's any chance that an application outside your control may someday require access to your shared assembly, you should install your assembly in the GAC so the application is sure to find it.

Security
The GAC is a system folder typically protected by administrator rights. Once an assembly is installed in the GAC, it cannot be easily modified. Also, assemblies stored in the GAC must be signed with a cryptographic key. These protections make it difficult to spoof your assembly, in other words, replace or inject your assembly with a virus or malicious code.

Version Management
.NET allows multiple versions of the same assembly to reside in the GAC so that each application can find and use the version of your assembly to which it was compiled. This helps avoid DLL Hell, where applications that may be compiled to different versions of your assembly could potentially break because they are all forced to use a single version of your assembly.

Faster Loading
The system verifies assemblies when they are first installed in the GAC, eliminating the need to verify an assembly each time it is loaded from the GAC. This can improve the startup speed of your application if you load many shared assemblies.

Why would I avoid the GAC?
The GAC should contain "global" shared assemblies only, so there are many instances when you would NOT install an assembly in the GAC:

The assembly is private to your application and not to be shared with other applications.
You want to use XCOPY or FTP copy to install a .NET application to a single folder. This eliminates the need to access the Registry and GAC and does not require administrator rights.
The assembly is not strong-named or you do not want tight version control.
COM interop and unmanaged code do not require the GAC.
How do I see assemblies installed in the GAC?
The .NET Framework includes an Assembly Cache Viewer. Open Windows Explorer, enter %windir%\assembly in the address bar, and all global assemblies will appear in a special view that shows the assembly name, version, culture, public key token, and processor architecture.



Can I install multiple versions of the same assembly in the GAC?
Yes. Normally you would not be able to have two files with the same name in a Windows folder, but the GAC is a special folder that stores its contents by strong name. Hence, two assemblies with the same name but different versions or cultures may coexist in the GAC.

How do I add/remove assemblies from the GAC?
Assemblies added to the GAC must be signed with a strong name. There are multiple ways to add/remove assemblies from the GAC:




Windows Installer
The preferred way to add/remove assemblies from the GAC is with Microsoft Windows Installer 2.0. Visual Studio includes a limited version of Windows Installer, and most major setup programs such as InstallShield also use Windows Installer. There are benefits to using Windows Installer:

Windows Installer provides a simple interface for developers to add/remove shared assemblies in the GAC and can handle private assemblies as well.
Installer provides a familiar interface and setup experience for the user.
Installer can also install application shortcuts and supporting files such as ReadMe and license agreements and can run other installation programs and scripts.
Installer registers and tracks references to assemblies installed in the GAC to determine which assemblies are still required.
Installer can repair and patch assemblies and rollback unsuccessful installations.
Installer can install assemblies on-demand as they are needed by applications.
GAC Utility
The .NET developer's kit includes a command line utility GACutil.exe to interact with the GAC. This utility is intended for use in a development environment only and should not be used to install assemblies on a client PC because:

The GACutil license agreement states that it is not freely distributable.
GACutil is part of the .NET SDK, which may not be installed on many target PCs.
GACutil lacks many important features found in Windows Installer such as assembly repair and rollback.
Assembly Cache Viewer
Using the Assembly Cache Viewer shown above, you can drag & drop assemblies from any folder into the GAC and also delete assemblies installed in the GAC.

.NET Framework Configuration Administrative Tool
To access the .NET Framework Configuration Tool:

Click Start > Control Panel.
Double-click on Administrative Tools.
Double-click on Microsoft .NET Framework 2.0 Configuration.
Ensure My Computer is selected in the tree.
In the Tasks group, click the Manage the Assembly Cache link.
Two links appear, enabling you to view and add assemblies in the GAC.
How do I access the GAC programmatically?
You can access the GAC from code with the fusion.dll library. Here is an excellent C# wrapper for the GAC.

You are strongly advised NOT to access the GAC from code unless you are creating an administrative or setup tool. The Fusion APIs expose your application to the inner workings of assembly binding and may cause your application to fail on future .NET versions.

How do I add my shared assembly to the Visual Studio "Add Reference" dialog?
If you add an assembly to the GAC, it will NOT automatically appear in the Visual Studio "Add Reference" dialog; instead you must add your assembly manually.

How do I move the GAC?
When installing .NET, you cannot configure the GAC location, however you can move the GAC after it is installed. See this article and scroll down to "Relocating the GAC."

No comments: