Working with Strong Names

for RuBoard

Working with Strong Names

We have previously discussed strong names and their uses (see Chapter 9, "Understanding the Concepts of Strong Naming Assembles"), and now it's time to look at the nitty-gritty details of actually applying them to your project. There are several aspects to this:

  • Creation of a public/private key pair

  • Adding the strong name to your assemblies

  • Optionally full signing or re-signing assemblies during the latter stages of building/creating setup kits

  • Managing keys within your development environment

We'll cover these topics in detail in the following sections.

Strong Name Key Pair Generation

Strong names are based on the use of a public/private key pair. The public key provides a well known identity ( roughly corresponding to a publisher), while the private key remains a secret used to generate a digital signature.

The first step in strong naming an assembly is to create such a key pair. It is wise to bear in mind that this key pair is long lived; because the public portion provides identity, regenerating the key pair will change the name of any assembly bearing that strong name. At that point, any dependent assemblies (your own or a third party's) that have references to the changed assembly will need to be recompiled at the very least (the act of rebuilding automatically updates early bound assembly references with the new name). If assemblies are dependent on yours in a late bound manner (that is, they use methods such as Assembly.Load to force explicit loads), actual source code changes will be required (because the code needs to explicitly provide public key information in such cases). Additionally, other changes, such as security policy updates, may be required (if your strong name is used as a basis of granting trust; this is particularly important, because it implies changes on customer machines).

Basically, the public key is a highly visible part of your assembly (there are even managed APIs to read it), so changing it should be considered a breaking change on par with updating public classes and methods (if not more so).

The key pair itself is a stream of binary data that can be considered opaque (as we shall see later in this section, there are limited operations that can be performed on this data, such as extracting the public key). The data is not random; the keys are related to each other and have certain mathematical properties. Consequently, key creation is non-trivial and requires external utilities.

The first possibility is to use the SN utility provided with the .NET Framework SDK. SN is a command-line “based strong name utility, providing many simple management operations. In this case, we're interested in the “k option (note that all SN options are case sensitive). The following will create a key pair in the file KeyPair.snk :

 SN k KeyPair.snk 

There is no requirement for a specific extension on strong name files, although snk is common. However, be aware that files containing both key pairs and individual public keys are commonly created. So either use different extensions for the two or choose a descriptive filename, as just demonstrated. (A quick tip here: in V1 of the .NET Framework, key pair files will be 596 bytes long, public key files will be only 160 bytes, allowing for quick identification by hand.)

Using SN in this manner will cause the Windows Cryptographic API (CAPI) to call into a Cryptographic Service Provider (CSP) to actually generate the key pair. For V1 of the .NET Framework, only one key type is supported ”the algorithm must be RSA signing ( CALG_RSA_SIGN in CAPI terms) with a key length of 1024 bits. SN will attempt to use the default CSP providing RSA services; if no specialized hardware/software CSPs have been installed for this purpose, Microsoft provides a default implementation that will work just fine.

Certain CSPs (particularly those based on cryptographic hardware) will not export a full key pair for security reasons (they never expose private keys outside of their implementation, which reduces the risk of the key being compromised). If you have configured such a CSP as the default RSA provider, SN will refuse to generate a key pair for you.

This leads us on to the second method of key pair creation. Because the file format used by the .NET Framework for key pairs is exactly that output by a CSP (from the CAPI API CryptExportKey , for example), you can create them yourself either by writing code that directly talks to CAPI or through the use of management utilities that may have come with the CSP/crypto hardware. Such topics are beyond the scope of this book (and very dependent on the actual software/hardware in use).

In general, the key pairs provided by Microsoft's default software-based CSP are satisfactory (mathematically speaking, they are as hard to compromise as key pairs from any other provider). But because the full key pair is exported to disk (and must be kept around, to sign each new build of your assemblies), it is difficult to guarantee that the private key is not compromised. If the private key becomes known, there can be a number of problems:

  • It may not be immediately obvious that the key has been compromised because the key data can be passively scanned.

  • The attacker can now create assemblies that claim to be published by you. This allows maliciously altered versions of your own assemblies to be distributed (with some sort of Trojan virus added perhaps), or allows the individual to take advantage of security policy that grants additional permissions to assemblies based on strong name.

  • The cost to repair the damage to a compromised private key is high. A new key pair will have to be generated and, as explained previously, this will result in a renaming of all of your assemblies and the corresponding incompatibilities with existing assemblies.

For additional security, it is advisable to use a hardware CSP that generates key pairs within itself and refuses to export them (such devices often have tamper proof implementations that will destroy the keys if any attempt to physically access them is made). When using this approach, you cannot use SN to create the key pair (at least in Version 1 of the .NET Framework). Again, you will need to either call CAPI directly (look at the API CryptGenKey ) or refer to utilities/documentation that came with the cryptographic hardware.

CAPI generalizes the handling of key pairs that can't be directly expressed outside the CSP by using named key containers. The key container name serves as a way to reference a given key pair within a CSP. So you (through CAPI) can ask a CSP to encrypt an array of bytes using the key pair in container Foo . This way, the CSP never needs to reveal the private key; it takes the plain text data in, encrypts it internally, and passes the encrypted data back out again.

For this reason, most of the strong name management supported in the .NET Framework can reference key pairs via either filenames or key container names.

When the key pair is kept highly secret (locked away in a piece of hardware) as previously described, it often becomes a problem to sign assemblies during the day-to-day development of assemblies. Typically, there will be only one machine capable of performing the signing operation, and it may well be placed well away from the normal development environment (for security reasons and to protect it from environmental dangers that could lead to the loss of the keys contained).

This is the reason that delay signing is supported under the .NET Framework. You'll recall that this involves setting the identity of the assembly as normal (using the public key) but deferring the generation of a digital signature until much later in the build (or product) cycle.

So in delay signing scenarios, we won't be dealing with a full key pair, but rather just the public key. Public keys are always represented in files; the runtime does not support the use of key containers for such entities. You can use SN to generate public key files from key pairs; the syntax differs slightly depending on how the key pair is stored.

If the key pair was exported to a file (such as when using SN “k KeyPair.snk ), the following command will generate a public key into the file PublicKey .snk :

 SN p KeyPair.snk PublicKey.snk 

If, however, your key pair resides in a key container (called MyContainerName for instance), use the following instead:

 SN pc MyContainerName PublicKey.snk 

Note that the file format of public key files is not exactly the same as the CAPI output format, so it's advisable to always use SN to manipulate these files. Sadly, the file formats for public keys and key pairs are not self-describing (at least in V1 of the .NET Framework). So careful naming of the key files is advisable to avoid confusion (many utilities will give strange errors when handed the wrong format file). Again, you can use the file sizes to differentiate the two by hand (596 bytes for key pairs, 160 bytes for public keys).

Building Strong Names into Your Assemblies

Now you have a key pair or a public key; how do you apply this to your assembly and form a strong name?

When building an assembly from source code, the answer is custom attributes. In one of your source files, place the following assembly level attribute near the top of the file (this is using C# syntax):

 [assembly: System.Reflection.AssemblyKeyFile("KeyFile.snk")] 

where KeyFile.snk is the filename of your key pair or public key. If you're actually using a key pair file, that's all you need to do. The compiler will extract the data and sign the assembly during compilation. If you're using a public key file, however, add the following attribute as well:

 [assembly: System.Reflection.AssemblyDelaySign(true)] 

This informs the compiler that the file referenced in the previous attribute contains only a public key, and the full signing process should not be attempted.

If you have neither a key pair nor a public key file, but instead have the full key pair in a key container, use the following attribute in place of the previous two:

 [assembly: System.Reflection.AssemblyKeyName("MyContainerName")] 

One problem with the use of custom attributes in this manner is that build environment “specific information (that is, filenames) needs to be placed in the source code. One solution is to use a build script to generate a source file on-the-fly containing only the necessary attributes. The following is an example Perl script that relies on the presence of an environment variable named BUILDROOT to identify the root directory of your source tree. It generates a file called StrongNameAttributes.cs in the current directory, which can then be tagged onto the list of source files to be compiled for your assembly.

 my $RootDir = $ENV{ "BUILDROOT"}  or     die("Environment variable BUILDROOT needs to be set\ n"); my $KeyPairFile = "$RootDir\ \ BinaryFiles\ \ KeyPair.snk"; my $SourceFile = "StrongNameAttributes.cs"; open(OUTFILE,">", $SourceFile) or     die("Failed to create $SourceFile, $!\ n"); print(OUTFILE "[assembly: System.Reflection.AssemblyKeyFile(@\ " $KeyPairFile\ ")]\ n"); close(OUTFILE); 

Other utilities that create assemblies may take command-line attributes instead. For example, TlbImp uses one of the following forms (for delay sign, full sign from file, and full sign from key container, respectively):

 TlbImp /publickey:PublicKey.snk /delaysign Foo.tlb TlbImp /keyfile:KeyPair.snk Foo.tlb TlbImp /keycontainer:MyContainerName Foo.tlb 

ILASM , on the other hand, uses the following scheme, all based on the use of the /key option. The argument to /key is taken as a filename unless it begins with the @ character, in which case it's taken as the name of a key container (minus the @ ). If given a filename, ILASM attempts to guess whether the contents represent a public key or key pair by looking at the contents and then delaying signing or full signing as appropriate. The method it uses to make this guess is heuristic, but given the current implementation of the .NET Framework and CAPI, it should be right 100 percent of the time. The ILASM versions of the previous TlbImp examples would look like the following:

 ILASM /key:PublicKey.snk Foo.il ILASM /key:KeyPair.snk Foo.il ILASM /key:@MyContainerName Foo.il 

The final example we'll look at is adding a strong name to an assembly generated via managed code (reflection emit). The technique followed varies depending on whether a public key or key pair is being provided, but in all cases, we modify the System.Reflection.AssemblyName passed to System.AppDomain.DefineDynamicAssembly .

First, let's look at delay signing an assembly. This is triggered by adding a public key (represented as a byte array) to the AssemblyName . Because you probably have the public key stored as a file on disk, you'll need to read this into memory first. Listing 25.11 gives an example implementation.

Listing 25.11 Delay Signing an Assembly via Reflection Emit
 using System; using System.Reflection; using System.Reflection.Emit; using System.IO; public class StrongNameEmit {     public static void Main(String[] args)     {         // Build a new assembly name.         AssemblyName name = new AssemblyName();         // Add simple name.         name.Name = "NewAssembly";         // Read the public key from disk. The file format is exactly         // the same as the in memory format, so just slurp the entire         // file into a byte array.         FileStream fs = File.OpenRead("PublicKey.snk");         Byte[] key = new Byte[fs.Length];         fs.Read(key, 0, key.Length - 1);         fs.Close();         // Add the public key to the name (since we're not supplying         // a private key, the assembly will be only delay signed at         // this stage).         name.SetPublicKey(key);         // Define a dynamic assembly with the given name.         AssemblyBuilder ab;         AppDomain domain = AppDomain.CurrentDomain;         ab = domain.DefineDynamicAssembly(name,                                           AssemblyBuilderAccess.Save);         // Add a single module (this is a single file assembly).         ModuleBuilder mb = ab.DefineDynamicModule("ModuleOne",                                                   "NewAssembly.exe ");         // Persist the assembly to disk.         ab.Save("NewAssembly.exe ");     } } 

Fully signing an emitted assembly is similar, but you will need to construct a System.Reflection.StrongNameKeyPair object to encapsulate the form in which the key pair will be made available to the runtime.

StrongNameKeyPair has three different constructors:

  • Given a FileStream , the entire contents of the file are read into memory and assumed to represent a key pair.

  • Given a byte array, the array is directly treated as a key pair.

  • Given a string, the string is assumed to be the name of the key container holding a key pair.

After the StrongNameKeyPair has been constructed , it can be attached to the AssemblyName using the KeyPair property. The modified code for full signing ( assuming FileStream input of the key pair) is given in Listing 25.12.

Listing 25.12 Full Signing an Assembly via Reflection Emit
 using System; using System.Reflection; using System.Reflection.Emit; using System.IO; public class StrongNameEmit {     public static void Main(String[] args)     {         // Build a new assembly name.         AssemblyName name = new AssemblyName();         // Add simple name.         name.Name = "NewAssembly";         // Open the key pair file on disk. The StrongNameKeyPair         // constructor will read the file contents for us (though         // we are still responsible for closing the stream).         FileStream fs = File.OpenRead("KeyPair.snk");         // Create a StrongNameKeyPair to describe the key pair input         // method.         StrongNameKeyPair keypair = new StrongNameKeyPair(fs);         fs.Close();         // Add the key pair to the name (since we're supplying a full         // kwy pair, the assembly will be fully signed).         name.KeyPair = keypair;         // Define a dynamic assembly with the given name.         AssemblyBuilder ab;         AppDomain domain = AppDomain.CurrentDomain;         ab = domain.DefineDynamicAssembly(name,                                           AssemblyBuilderAccess.Save);         // Add a single module (this is a single file assembly).         ModuleBuilder mb = ab.DefineDynamicModule("ModuleOne",                                                   "NewAssembly.exe ");         // Persist the assembly to disk.         ab.Save("NewAssembly.exe ");     } } 

Note that when reading a key pair from a file, the file read operations are performed during the construction of the StrongNameKeyPair object, not during the strong naming of the assembly (which occurs during the AssemblyBuilder.Save method call). Therefore, the FileStream object may be safely closed directly after the StrongNameKeyPair object is created. When passing a byte array, the array is buffered in the StrongNameKeyPair , so the original array may be safely modified before the call to Save .

Coping with Signature Invalidation During the Build Process

One problem that may crop up when building strongly named assemblies is accidental invalidation of the signature (leading to failures when trying to load the assembly or install it into the GAC). Just about any change to the assembly data will cause an invalidation, the only exceptions being the following:

  • Any data in the image security directory (this allows Authenticode certificates to be added to a strongly named assembly without problems)

  • The file header checksum (to allow updates such as we've just described)

  • The signature blob itself (obviously we can't include this data when we're trying to compute the signature value in the first place)

All files in a multifile assembly are included in the signing process. This includes non-code files, such as linked resources.

The common reasons for modifying the image after the build step include stripping debugging information, rebasing images, and adding/modifying resource data.

There are several strategies that may be employed to work around such issues:

  • Avoid the modifications entirely ” Rebasing, for example, is generally less effective for a purely managed assembly that contains only a single unmanaged reference for the operating system loader to fix up (managed code handles code fixups in a different manner to the OS).

  • Full sign as the last stage of the build/setup process ” Generate delay signed images during compilation, perform any assembly post-processing steps, and then full sign the assemblies as a final step. This fits in well with a model where access to the private key involves heavyweight process and full signing is performed rarely (that is, day-to-day development uses delay signed assemblies).

  • It is possible to re-sign already fully signed assemblies as many times as desired (providing access to the private key is possible) ” This works well if access to the private key is cheap.

SN can be used to fully sign a delay signed assembly (or re-sign a previously fully signed assembly). If you have the key pair in a file, use the following syntax:

 SN R MyAssembly.dll KeyPair.snk 

Recall that SN options are case sensitive, so it's important to use “R rather than “r . If your key pair resides within a key container, use the following instead:

 SN Rc MyAssembly.dll MyContainerName 

Using Delay Signed Assemblies

If your development environment is going to use delay signed assemblies (perhaps because access to the private key is secured and full signing is a heavyweight process), your target execution environments need some preparation.

This is because delay signed assemblies look identical to fully signed assemblies that have been tampered with as far as the .NET Framework is concerned . Not having had a signature computed is the same as having a bad signature. For such assemblies to be successfully loaded or installed in the global assembly cache, the runtime must be forewarned that the signature is expected to be bad.

This is achieved by creating special entries in the registry listing assemblies and groups of assemblies that will have their strong name signature verification skipped . The easiest way to create these entries is via the use of SN . SN has a set of operations (all starting with “V ) that allow management of the verification entries.

To register an assembly to be skipped, use SN “Vr :

 SN Vr MyAssembly.exe 

The full path to the assembly must be given. However, the assembly file is used only to discover the two pieces of information SN is interested in ”the simple string name of the assembly (probably MyAssembly in the previous example) and the public key (actually its condensed form, the public key token). You can move the assembly after registration and verification skipping will continue to work.

Alternatively, you can instruct SN to enable verification skipping for all assemblies using a given public key token. Use the notation *,public key token , where the public key token is given as a hexadecimal string:

 SN Vr *,b03f5f7f11d50a3a 

Note there are no spaces around the , character.

The previous commands may be followed by a comma-separated list of usernames (in fully qualified domain\username format). This restricts the operation of the settings to the specified users. That is, signature verification will only be skipped for the named assemblies when run under the account of one of the listed usernames.

 SN Vr Foo.dll domain1\ john,domain2\ bill 

To find out which assemblies are currently covered by your settings, use SN “Vl :

 SN Vl Microsoft (R) .NET Framework Strong Name Utility  Version 1.0.3612.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Assembly/Strong Name                  Users =========================================== *,B03F5F7F11D50A3A                    All users Foo,1E7130EED55A0CC0                  domain1\ john domain2\ bill MyAssembly,3D61F69268DA82E1           All users 

To unregister an assembly, use SN “Vu , specifying the assembly name the same way as you would for SN “Vr :

 SN Vu MyAssembly.exe SN Vu *,b03f5f7f11d50a3a SN Vu Foo.dll 

There is never any need to specify a list of usernames when unregistering assemblies. There can be only one entry for a given assembly name, so the unregister request is unambiguous. To quickly remove all settings, use SN “Vx :

 SN Vx SN Vl Microsoft (R) .NET Framework Strong Name Utility  Version 1.0.3612.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. No verification entries registered 

One point to bear in mind is that the .NET Framework runtime reads these settings from the registry at process startup and will not see any updates after that point. For a managed program to start using new settings, the process will need to be stopped and restarted.

Obviously, switching off strong name verification in such a manner has serious security ramifications . It is not advisable to turn off checking for images you've received from untrusted sources (such as the Internet). The runtime won't be able to tell the difference between an assembly from the advertised publisher and one that just claims to be from that publisher. Conversely, you should never ship delay signed assemblies to customers or expect them to configure their machines to run your assemblies in the ways described in this section. You should always fully sign strong named assemblies prior to shipping.

Setting up verification skipping entries is allowed by Administrators only (the settings in the registry are ACL protected), which prevents malicious, untrusted code from changing the settings for your machine under your feet.

for RuBoard


. NET Framework Security
.NET Framework Security
ISBN: 067232184X
EAN: 2147483647
Year: 2000
Pages: 235

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