Strength in naming

This article was originally published in VSJ, which is now part of Developer Fusion.
Re-using code is a fundamental part of the way we build software – long gone are the days of a standalone BASIC program in any area other than teaching. Instead we live in the age of the application, where executables invoke code from a large number of libraries, objects and assemblies.

In classic Win32 (and for that matter Win16), the connections to external (dynamic) libraries were made by supplying the name of the library and the ordinal or name of the function to be called. COM extended this paradigm by allowing an application to “look up” the appropriate DLL via the registry, but the underlying mechanism for loading code remained unchanged – all COM functionality was accessed through standard DLL entry points by the underlying COM subsystem. If the library file was overwritten on the disk, applications could end up calling code very different from that which was expected. This commonly happened when another application installed a DLL with the same name but with a different version of code in it. This led us to what became commonly known as “DLL Hell”, where installing a new application could cause older applications to crash for no obvious reason.

There was another more sinister possibility – a hacker or virus writer could replace important DLLs on your system with versions that compromised system security.

Windows File Protection was introduced to protect important Windows components, and the writers of installation programs have improved life substantially with features to avoid accidental DLL overwriting, but the underlying problem remains.

Dynamically loaded libraries still exist in .NET in the form of assemblies. It is possible to dynamically load any assembly using just the file name, and use reflection to explore, and even invoke, its contents. An application built using external assemblies is still potentially vulnerable to that assembly being replaced, if the assembly is referenced by just its filename, or loaded dynamically by filename:

Assembly.LoadFile(
	“C:\Project\myAssembly.dll” );

What’s in a name?

In the .NET world we have the option of giving an assembly a “strong” name. These assemblies are digitally signed before they are released, so their contents can be verified by the CLR when they are loaded.

Referring to an assembly by its strong name is a good way of ensuring that the assembly that is loaded is the one that you wanted – and that is exactly what was missing in Win32, and caused DLL Hell.

The strong name of an assembly is composed from the following elements:

  • Assembly name (same as filename with “.dll” removed)
  • The assembly version
  • The assembly culture
  • The public key token for the assembly
To load an assembly by strong name, we must specify all of this information. If an assembly matching the complete strong name is not found, an exception will be thrown. When an assembly with a strong name is referenced from a project, the strong name is automatically used to load it, and assemblies can be loaded dynamically by strong name:
Assembly.Load(“myAssembly,
	Version=1.0.0.1,
	Culture=neutral,
	PublicKeyToken=15bf6c59821b437d”);
If an assembly is substituted with a different version it will simply fail to load because the versions will not match. The exception will show the exact reason for the failure – an improvement on Win32, where your application would most likely crash at some fairly random point in time.

Cryptography in strong naming

An assembly with a strong name has a digital signature, which is created using asymmetric cryptography. This means that a private key known only to the developer is used to sign the assembly, and the signature may be verified using a public key that the developer can publish.

The public key is too large to use directly when specifying the assembly name, so instead a public key “token” derived from the public key (using a secure hashing algorithm) is used in references. The public key itself is stored in the assembly to enable verification of the assembly.

How strong is a strong name?

Using strong name ensures that an assembly with the correct name, culture and version is loaded. If another assembly with the same details but different content is created then it will still load correctly. This puts an onus on the developer to ensure that they do not use the same name for assemblies with different content, and that they are rigorous in updating version numbers.

Secondly, it ensures that the assembly has a valid digital signature, verified using a public key matching the public key token from the strong name. This is only really helpful to us in terms of enabling to trust the assembly if the following additional statements are true:

  • We believe that the cryptographic algorithms used in signing the assembly have not been compromised
  • We can verify that the public key token matches the expected token for the author source of the assembly
  • We trust the author of the assembly to provide safe code
  • The private key used to sign the assembly has been kept secure
If we are not confident in any of the above statements, then there are many scenarios in which the strong name may be complete and valid, but there may be a potential security problem associated with the loaded assembly (see the box “Strong Name Deception”).

Note that it is possible for an administrator to disable assembly signature verification on a machine, which will improve the load time for assemblies at the cost of removing the protection provided by verification.

Code Access Security

There are two principle ways in which strong naming interacts with CAS. The first is that policy can be configured to provide greater (or fewer) permissions to an assembly based on its public key token. For example, a company may use policy on its end users’ machines to give broad-ranging permissions to assemblies signed by the IT department (and thus with a specific public key token). The other way in which strong names interact with CAS is that strong names assembly can only be used from assemblies with full trust by default. This can create problems in some environments like ASP.NET where you may wish to invoke a signed assembly but the ASP.NET system does not run with full trust (e.g. in a shared hosting environment). You can remove this restriction by applying the “AllowPartiallyTrustedCallers” attribute (often APTCA for short) to your assembly. You should only do this if you understand the implications of doing so, and your assembly has been designed to ensure this does not introduce security loopholes. For more information see the MSDN documentation topic Using Libraries from Partially Trusted Code.

The Global Assembly Cache

In the .NET world the replacement for system directories full of DLLs is the Global Assembly Cache (or GAC). Assemblies must have a strong name in order to be installed in the GAC, which is a protected storage system rather than just a directory – there is no way to get an assembly into the GAC other than by installing it.

The main benefits of placing an assembly in the GAC is that it is available to all applications, and that multiple versions can exist side by side in the GAC (since the version is part of the strong name by which an assembly will be identified). An additional benefit in loading assemblies from the GAC is that they will load faster. This is because the signature is checked at installation time, and not at the point of execution. Signature checking is time consuming both because of the cryptographic computation required and because the entire assembly must be loaded into memory to check the signature. An assembly that can be executed without signature checking will be loaded into memory only as it is needed.

Even if you have never heard of the GAC before you have almost certainly used it on a regular basis, as this is where the .NET Framework assemblies (mostly in the System namespace) are loaded from by default.

The search for an assembly

In the Win32 world, a DLL reference was resolved by checking first in the application directory, and then in each directory referenced by the “PATH” environment variable. This tended to result in a soup of shared DLLs in certain system directories, which were frequently updated by new application installations. Loading an assembly is a little more complex in the .NET environment. The initial reference will usually either be a strong name or a partial name consisting of just the assembly dll name. This is then modified or expanded upon using configuration information found locally (in app.config), in publisher policy (in policy assemblies loaded into the GAC), or globally (in machine.config). This configuration information may determine the version that will be loaded, and may also provide a “codebase”, a folder location from which to load the assembly.

If the assembly required is not already loaded into memory and a full strong name is available, the CLR will next try to load the assembly from the GAC. If the assembly is not found in the GAC, the CLR will now “probe” the filesystem for the assembly. If a “codebase” location was supplied and the assembly is not found there, loading will fail. Otherwise the CLR will inspect a variety of local directories under the application path, in subfolders qualified by culture etc. If the name is not a strong name, only the application base directory will be checked, unless explicit directions to the contrary were supplied to a call to Assembly.LoadFrom.

This rather complex process is summarised in Figure 1.

Figure 1
Figure 1: Assembly Loading Process

Strong name keys

In order to create strong name assemblies, you need to have a key pair, consisting of a public and private key. You can create and manage keys using the “sn” tool that ships as part of the .NET Platform SDK, and is available from the Visual Studio command prompt (if you have Visual Studio installed). A new key can be created like this:
sn -k my_new_key.snk

Table 1 shows some of the other options available on this command.

Table 1: Useful options for the “sn” Tool

Command Function
sn –k out.snk Create a new key pair in out.snk
sn –p in.snk out.snk Extracts the public key from in.snk into out.snk
sn –t in.snk Display the public key token for in.snk
sn –V… Manage settings for skipping signature verification
sn –i in.snk container Install the key from in.snk into container
sn –d container Delete the specified container
sn –pc container out.snk Extract the key from container into out.snk
sn –R my.dll in.snk Re-sign my.dll using the key from in.snk
sn –Rc my.dll container Re-sign my.dll using the key from container
sn –vf my.dll Verify signature of my.dll is consistent with its public key
sn –V… Enable/Disable verification of signatures (various options)
sn –m… Set machine or user scope for key containers
sn –? Display all options for the “sn”command

Keys are usually created in the form of a file with the extension “.snk” as shown above, and may be used directly from this file or stored in a key “container”. Key containers are a feature of the Windows cryptographic services (or “CryptoAPI”), and are beyond the scope of this article. Essentially they allow secure storage of keys which can then be referenced by the name of the container and used in cryptographic operations, but can’t easily be extracted from the container (for example by someone trying to steal them).

Signing assemblies

A strong named assembly is created either by signing the assembly during the build process, or at some later point (known as “delay signing”). The mechanism used for signing the assembly as part of the build process varies according to the version of Visual Studio you are using. For most project types, Visual Studio 2003 (or the .NET 1.x compiler) automatically creates a file called AssemblyInfo.cs with some attribute placeholders in to help you with the process, including an AssemblyKeyFile attribute that can be used to specify a key file for signing. This is also where you will find version and culture attributes. If a key file is specified, Visual Studio will automatically sign the assembly:
[assembly:
	AssemblyKeyFile(
	“..\\..\\..\\..\\my_new_key.snk”)]
Alternatively, there is also a placeholder AssemblyKeyName attribute to specify the name of a key container.

In Visual Studio 2005 (or the .NET 2.0 compiler) these attributes have been deprecated for security and performance reasons. Instead, the command line flag “/keyfile” (or “/keycontainer”) are used. These can be accessed conveniently through the “Signing” tab of the project settings dialog, where a key file can be selected or created. VS2005 does not directly support the use of key containers, but does introduce support for the PFX file format (see box).

Delay signing assemblies

There are two reasons why signing an assembly as part of the build process might not be practical, or even possible. The first is that the key pair to be used in signing is not available, and the second is that the assembly to sign is not yet complete. In large organisations (not least Microsoft!) the key pair used for signing release assemblies may (rightly) be a closely guarded secret. This is important, because anyone who can get hold of that key pair can produce assemblies that are indistinguishable from those produced legitimately. Where this is the case, the final assemblies may be built by the development team and then passed on to another department for signing. It is less usual for the assembly to be incomplete, but this may be the case when external tools (often third party tools) are used to modify the assembly after compilation. The two examples of this I have come across are tools that add instrumentation for performance profiling, or tools that obfuscate the content of the assembly to make decompilation harder and protect intellectual property.

If you wish to delay sign an assembly, you can use the AssemblyDelaySign attribute or the “Delay Sign Only” check box (in Visual Studio 2003 and 2005 respectively) to build an assembly that is ready to be signed at a later date. You must still provide a key file containing the public key, as delay signed assemblies are complete in all respects except for the signature itself. When the time comes to sign a delay-signed assembly, you use the “sn –R “ command to “re-sign” the assembly.

Testing delay-signed assemblies

An assembly that has been built for delay signing cannot be used (executed or debugged) in the normal way until it is signed. This is because it is essentially a strong named assembly with an invalid signature, and thus it is in limbo until it is signed. The usual work-around for this it to disable strong name verification for the assembly using the sn –V… command. This allows you to use the full strong name of the assembly even though the signature is invalid. This is a machine-scope operation, and opens up a security hole because anyone can produce a spoof assembly with the same strong name in the absence of signature checking. This may be acceptable in a test and development environment, but some caution is required.

The .NET 2.0 environment adds a new option, which is to “Test Sign” the assembly, using a test key pair to generate the signature instead of the usual key. This means that the signature will not match the public key, so the assembly is still invalid by normal standards. However, there is a feature in the sn tool that enables you to configure the assembly for verification against a your test key pair. This is more secure than skipping verification, and the signature will be checked against the key it was signed with, even though this will not match the public key in the strong name. Test signed assemblies can be created from delay signed assemblies, and converted to standard strong name assemblies, using the test-sign and re-sign options of the sn tool. Of course another simple way to enable testing is to sign the assembly with a different key, then re-sign it later. However, this changes the strong name of the assembly, so in a multi-assembly application you will need to update or override all the strong name references at some point in the process, potentially a tricky and time-consuming task.

Authenticode signatures

Strong names are intended to work within the scope of the .NET framework to ensure that assemblies are not tampered with. The keys used for signing strong named assemblies however are usually just generated by the developer, and therefore if you wish to establish trust for a key, you must manually exchange key information with the developer.

Authenticode signatures are created using certificates rather than just key pairs. These certificates, just like SSL certificates, are issued by a root Certification Authority (CA) and can be traced back to that authority – they can also be revoked in the event that they are compromised. An assembly with an authenticode signature claiming to be from “Company X” can therefore be reliably traced back to prove if it did indeed come from there. Traceability of certificates to a root authority and the ability to revoke certificates are the principle reasons to add an authenticode signature in addition to a strong name. “Click Once” deployment requires the use of an authenticode signature for precisely these reasons – so the user can be confident in the software they are installing.

Authenticode signatures are really for the benefit of the end user, to give them confidence in software they download and install. Strong names are there to ensure that the right assemblies are loaded and that they have not been tampered with.

Conclusions

Strong names are an important security feature of the .NET environment, and can help to make your applications more robust too. It is good practice to use strong names for all of your assemblies, but it is essential that you have an appropriate strategy for keeping your private keys secure, and understand the limitations as well as the benefits of strong names.


Ian Stevenson has been developing Windows software professionally for 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works as a consultant in the software industry, and can be contacted at [email protected].

Strong Name Deception

The following is a scenario in which security can be compromised by loading an incorrect assembly in spite of using strong names if you do not apply appropriate trust rules to the process:

Mr Blackhat creates some sample code for a useful task. In the download package for developers he includes a “patch” for a core library “System.Useful.DLL”, with instructions to link to this from your application. The “patch” has all of the correct metadata for the original System.Useful.DLL including the name, manufacturer, date, version, etc. The code is copied from the original DLL with Mr Blackhat’s very own modifications, then signed with an arbitrary key.

Mr Naïve, the developer, downloads this sample code. He has heard of security, so he doesn’t trust Mr Blackhat and he carefully checks Mr Blackhat’s sample before incorporating it into his application. He uses Visual Studio to add the required reference to “System.Useful.DLL”, which is a system library so it must be safe, right? Mr Naïve now distributes his application to hundreds of users, and in all cases it works perfectly, because the public key token in the assembly matches the public key token that was in the assembly when the reference was added to the project. It also works perfectly for Mr Blackhat, as it installs a key-logger to capture passwords and credit card details.

The mistake that Mr Naïve has made is to trust an assembly from an untrusted source. It may be that Mr Whitehat follows the same practice and redistributes a legitimate patch (ignoring licensing issues for now). Had Mr Naïve checked that the public key token of the reference in his project matched the published public key token of its purported provider, he would not have fallen into this trap.

PFX Certificate Files

The .NET 2.0 environment can use “.pfx” files as an alternative almost anywhere an snk file can be used. The compilers, visual tools, and command line tools such as sn have all been updated (although sn can only read pfx files and not write to them).

The pfx file format is used by Windows to store encrypted certificates protected by a password. Since these files contain a certificate (some additional attributes) rather than just a key pair they are larger and more flexible than snk files. Notably, pfx files can be used for purposes other than signing assemblies, such as encrypting e-mails or encrypting NTFS filesystems.

If you create a key using the “signing” page of the Visual Studio 2005 project dialog, snk files are generated if the password option is turned off, and pfx files are generated if the password option is turned on. Visual Studio will “remember” passwords for created and imported pfx files for the current user, but request them when the project is executed from a new machine or account.

In theory pfx files are somewhat more secure than snk files since a password is required to access them, but more convenient than using a key container because they can easily be copied from machine to machine in their encrypted form.

Coding for Strong Names

Most developers will never have to look beyond the support for strong names provided in Visual Studio and the “AssemblyName” class. However, if you do need to manipulate strong names directly, there is an unmanaged API that enables this. The key functions in this API are:
  • StrongNameGetPublicKey
  • StrongNameTokenFromPublicKey
  • StrongNameSignatureGeneration
  • StrongNameSignatureSize
Information on all of these functions is available in the MSDN Library, and there is an excellent sample (a managed implementation of the “sn.exe” tool) showing how to use them from managed code on this MSDN security blog.

You might also like...

Comments

About the author

Ian Stevenson United Kingdom

Ian Stevenson has been developing Windows software professionally for 10 years, in areas ranging from WDM device drivers through to rapid-prototyping of enterprise systems. Ian currently works a...

Interested in writing for us? Find out more.

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Java is to JavaScript what Car is to Carpet.” - Chris Heilmann