CLR Inside Out
Using Strong Name Signatures
A Short Refresher
Why Use Strong Names?
What Strong Names Can’t Do
Working with Strong Names
Test Key Signing
Managing the Private Key
Working with Authenticode
Strong name signatures (and signing in general) are a key facet of Microsoft® .NET Framework security. But regardless of how well designed .NET signatures may be, they won’t offer the maximum benefit if you don’t know how to use them properly. This installment of CLR Inside Out talks about strong names, strong name signatures, and how to use them.
A Short Refresher
Digital signatures are used to verify the integrity of data being passed from the originator (the signer) to a recipient (the verifier). The signatures are generated and verified using public key cryptography. The signer of a message has a pair of cryptographic keys: a public key, which everyone in the world knows, and a private key, which is kept secret by the signer. The verifier knows only the public key, which is used to verify that the signer knew the private key and the message.
In some cases, when some additional infrastructure is in place, digital signatures can also be used to reliably learn the name of the signer, and to ensure some chunk of data (a message, some code, or so on) has not been modified after the signer created the signature for the data.
Various mechanisms are used to implement digital signatures. The current implementation of strong names in the .NET Framework relies on the RSA public key algorithm and the SHA-1 hash algorithm.
Strong names offer a powerful mechanism for giving .NET Framework assemblies unique identities. The strong name for an assembly consists of five parts: a public key, a simple name, a version, an optional culture, and an optional processor architecture. The public key is an RSA public key. The simple name is just a text string—usually the name of the file (without the extension) that contains the assembly. The version is a four-part version number, in the form of Major.Minor.Build.Revision (for example, 126.96.36.199).
To get a valid strong name, an assembly is strong-name signed during the build process. This is done using the private key that corresponds to the public key in the strong name. The strong name signature can then be verified using the public key. There are a few different techniques for creating a strong name signature and for managing the private key used in signing. I’ll discuss these various techniques later in the column.
When one assembly references a strong-named assembly, the referring assembly captures the strong name information for the referenced assembly. When the .NET Framework loads a strong-named assembly, it verifies the strong name signature. If the strong name signature of the assembly cannot be verified, the .NET Framework will not load the assembly.
One exception to this process has to do with strong-named assemblies that come from the Global Assembly Cache (GAC). These are not verified each time the .NET Framework loads them. This is because assemblies in the GAC are verified when installed in the GAC. Since the GAC is a locked-down, admin-only store, assemblies located here do not need to be verified each time they are loaded.
Why Use Strong Names?
Strong names prevent spoofing of your code by a third party (that is, of course, as long as you keep the private key secure). As mentioned, the .NET Framework verifies the signature either when loading the assembly or when installing it in the GAC. Without access to the private key, a malicious user cannot modify your code and successfully re-sign it.
Given that strong names prevent spoofing, they can be used to make certain security decisions. An enterprise, for example, could choose to trust all code that comes from the enterprise’s intranet and has been signed with the enterprise’s strong name key. But strong name signatures do not contain any reliable information about the publisher, so while it is safe to trust keys you control, it is dicier to trust keys from other organizations unless you have a secure channel to get their public key.
It is also important to note that strong names do not have any revocation mechanism that can be used if the private key is compromised. It goes without saying that if you plan to use your enterprise’s strong name for trust, keep the private key private! (I’ll have tips for this later in the column.) It’s a good idea to scope the trust of a strong name to a particular location (such as your enterprise’s application server) or at least a zone (such as your intranet zone). This provides some mitigation in case your private key is ever compromised or a buggy version of a signed assembly makes its way to some bad guys and they try to lure users into running it from outside the intranet.
What Strong Names Can’t Do
Strong-name signing is useful for preventing a malicious user from tampering with an assembly and then re-signing it with the original signer’s key. It cannot do anything, however, to prevent a malicious user from stripping the strong name signature entirely, modifying the assembly, re-signing it with his own key, and then passing off the assembly as his own code. Digital signatures in general and strong name signatures in particular cannot help with this problem. Watermarking is a more appropriate solution for protecting against this scenario.
Strong names are secure only when the strong name private key is kept secure. As I mentioned earlier, they have no revocation capabilities and they don’t expire. Nor do strong names inherently provide any way to securely map a public key to a particular publisher. Thus, strong names should be used very cautiously when making security decisions. Ideally, these security decisions should only be made when you can be sure the strong name public key corresponds to a particular publisher (for example, when the public keys are generated by your organization’s IT department). You should note that more sophisticated signing schemes (for example, Authenticode®) are available that do offer revocation and publisher verification capabilities.
Working with Strong Names
Strong-name signing is a good idea for most applications, especially those that are deployed over a network or any medium not fully controlled by the deployer. The anti-spoofing and anti-tampering benefits are quite valuable. However, there are some challenges to be aware of when using strong-name signing.
First, all assemblies referenced by a strong-named assembly must also be strong-named. If you reference an assembly written by a third party that is not strong-name signed, you cannot strong-name sign your assembly.
Second, strong-name signing makes servicing more complicated. Under current versioning policy, an assembly will always attempt to load the exact version of the assembly it was built against. If, for example, your application was built against version 188.8.131.52 of a strong-named assembly and you fix a bug in the assembly, bumping the version number to 184.108.40.206, the existing application will not find the updated assembly.
There are a few ways to deal with this situation. You can rebuild the application against the new assembly. Obviously, this is an annoying process for just picking up a bug fix and it may not be an option in some situations. Still, it works fine when your code is not widely deployed or the assembly being serviced is not widely shared across applications.
Another option is if the assembly is installed in the GAC, you can use publisher policy to redirect loads for version 220.127.116.11 to version 18.104.22.168. However, publisher policy is complicated.
Yet another option is to fix the bug and not change the assembly version number, so existing applications can still find the assembly. This is the approach the .NET Framework uses for bug fixes, although it’s not appropriate for new features or anything that breaks compatibility. If you are adding new features, you should bump the assembly version up and have applications rebuild to opt in to the new features.
Needless to say, strong-name signing adds some complexity to your development and build processes. But this is necessary to keep the private key secure. I’ll discuss how to deal with this complexity in a moment.
Now let’s take a quick look at a simple signing scenario: creating a random key pair and storing it in a file. I’m only using this particular approach as a way to introduce you to how strong-name signing works. Obviously, storing the private key in a file next to your code is not a good way to protect your key so you generally should not use this approach in production.
If you are using command-line tools, you can create a key pair with sn.exe in the SDK. Then you pass that key pair to the compiler when compiling your code. The following commands create a new random key pair using sn.exe and build a strong-name signed assembly with csc.exe:
sn -k mykey.snk csc /keyfile:mykey.snk myapp.cs
This produces a strong-name signed application named myapp.exe. You can do the same thing with Visual Studio® 2005. The Signing pane on the project properties page controls strong-name signing for your project (see Figure 1). To create a simple key file, select the "Sign the assembly" option, and create a new key file that is not password protected. Visual Studio will add the key file to your project and use it to sign your application.
Figure 1** Signing the Assembly **
As I mentioned, storing the private key in a file next to your code is not a secure way to protect your private key. Fortunately, Visual Studio allows you to password-protect the key file. When you create a new strong name key for your project, just leave the "Protect my key file with a password" option selected and enter a password for your key file (see Figure 2). Visual Studio adds a personal certificate, a PFX file, to your project. This file includes your encrypted private key, protected by the password you selected.
Figure 2** Creating a Key **
While password-protecting your key files is a much better solution than storing them in the clear, it is still not ideal. You would still have to distribute the PFX file to all of your developers, and they would all have to know the password for the PFX file. Secrets that are widely shared like this do not tend to stay secret for very long. Ideally, you should not have to distribute the private key to build and test your code during development.
This is where delay signing enters the picture. Delay signing allows you to generate a partial signature during development with access only to the public key. The private key can be stored securely out of the hands of the developers and used to apply the final strong name signature just before shipping your code.
To use delay signing, follow these steps:
- Extract the public key from the key pair. The public key can be distributed to developers in your organization without any security risk.
- Use the public key to delay sign your assemblies during development. Because the assemblies are not fully signed yet, you’ll also have to configure your development machines to skip strong name signature verification for your key—otherwise, the .NET Framework will not allow you to load the delay-signed assemblies.
- When you are ready to ship your code, use the (well guarded) private key to apply the final strong name signature to your delay-signed assemblies.
For step one, you can use the sn.exe tool to extract the public key from a key pair. For example, say you have your key pair stored in a securely configured key container named EnterpriseKey on a locked-down machine. You would use the following command to extract the public key to the EnterprisePublicKey.pk file:
sn -pc EnterpriseKey EnterprisePublicKey.pk
You can then distribute EnterprisePublicKey.pk to the developers in your organization. You can usually use the same public key for multiple assemblies because each will have a different simple name.
For step two, you use compiler options to delay sign your assemblies with the public key when building them. The example that follows creates a delay-signed application using options for the C# compiler:
csc /delaysign+ /keyfile:EnterprisePublicKey.pk myapp.cs
Now if you try to run myapp.exe, it will not run because it does not have a valid full strong name signature. You must configure the .NET Framework to skip verification for the delay-signed assemblies using your public key. To do this, you use the sn.exe tool again. The following example configures the .NET Framework to skip strong name signature verification for the myapp.exe assembly on your development machines:
sn -Vr myapp.exe
You can also use the sn.exe tool to skip signature verification for all assemblies signed with a particular key. For example, if you want to configure your machine to skip all assemblies delay signed with the same key as your application, you first do the following on your development machines:
sn -T myapp.exe
This prints out the value of the public key token, a shortened version of the public key that looks something like this:
Microsoft (R) .NET Framework Strong Name Utility Version 2.0.50727.42 Copyright (c) Microsoft Corporation. All rights reserved. Public key token is b03f5f7f11d50a3a
(Note that the actual value of the public key token will vary depending on your public key.) You then execute the following command to skip strong name verification for any assembly using that public key token:
sn -Vr *,b03f5f7f11d50a3a
Any assembly delay signed with that public key will now skip strong name signature verification and run on your development machine. Note that skipping strong name signature verification is something that should only be done on development machines. It should never be done on production computers because it opens up those machines to assembly spoofing attacks.
Finally, for step three, you use the real private key to generate the final full strong name signature before shipping your code. Again, you use the sn.exe tool to do this. For example, if your key pair is stored in a key container named EnterpriseKey on your signing computer, you would use the following command to sign a delay-signed assembly:
sn -Rc myapp.exe EnterpriseKey
Now the assembly has a full signature. One of the nice things about delay signing is that it does not require any of the assemblies that use the delay-signed assembly to be rebuilt. Any assemblies that refer to the delay-signed assembly had access to its public key and were therefore able to create a full assembly reference, even though the assembly did not have a full signature.
Test Key Signing
There is a new feature in the .NET Framework 2.0 called test key signing. This is similar to delay signing. With test key signing, you sign your assembly with an alternate private key during development and then configure your development computers to verify the signatures against the alternate public key that corresponds to the alternate private key. Test key signing is valuable when you want almost the exact same behavior as full signing for testing purposes. For example, with delay signing, assemblies are not verified at all. This could hide performance issues if your application loads many strong-named assemblies because none of the strong name signatures would be verified during test runs when delay signing is turned on.
Test key signing involves the following steps:
- Delay sign your assembly.
- Create a test key pair with which to sign your assemblies. You can do this using sn.exe with the -k option.
- Test sign your assembly. You can do this using sn.exe and either the -TS or -TSc option. This will generate a signature for the assembly using the private key from your test key pair.
- Configure your development machines to use the test public key to verify the test key signed assemblies. You can use the –Vr option on sn.exe to do this.
When you are ready to ship your assemblies, you sign them with the real private key using sn.exe with either the -R or -Rc option, just as you would for a delay-signed assembly. The following series of commands creates a test key pair, test key signs a delay-signed assembly, extracts the test public key, and configures a development machine to verify the assembly against the test key:
sn -k TestKey.snk sn -TS myapp.exe TestKey.snk sn -p TestKey.snk TestPublicKey.pk sn -Vr myapp.exe TestPublicKey.pk
When using test key signing, you should still protect the test private key, but you do not have to guard it as closely as the real private key. It is also a good idea to change the test key pair periodically.
Managing the Private Key
It is much easier to keep your private key secure if you are using delay signing or test key signing. You only have to have the private key installed on one or possibly a small number of locked-down signing computers. You can install the private key in a key container using restrictive access control lists (ACLs) to prevent unauthorized access to the key container. Alternatively, you can store the private key on a smart card or other separate hardware device. The sn.exe tool allows you to use a different cryptographic service provider (CSP) for signing. Take a look at the –c option in the documentation for sn.exe.
Working with Authenticode
Authenticode signatures provide more security semantics than strong name signatures. To generate an Authenticode signature, you have to obtain a code-signing certificate from a trusted certificate authority (CA). This can either be a commercial CA or a CA established by your IT department. The CA provides some level of assurance that the person or organization with the certificate is who they say they are, giving you some confidence that the software really came from the publisher listed in the signature. Authenticode also supports expiration and revocation, making it more flexible than strong name signatures. However, like strong name signatures, the presence of an Authenticode signature does not guarantee that you can trust the code. Authenticode simply provides some assurance about who published the code and that the code has not been tampered with since the publisher signed it.
Authenticode signatures can co-exist with strong name signatures. However, you must strong-name sign your assembly first, before applying the Authenticode signature. Adding the Authenticode signature will not invalidate a strong name signature.
Authenticode signatures can be used to trust applications deployed using the new ClickOnce model in the .NET Framework 2.0. You can use the tools in Visual Studio 2005 or in the SDK to apply an Authenticode signature to the deployment manifest of your ClickOnce application. You then install the certificate used to create the Authenticode signature in the trusted publisher certificate store on the client computers that will run your application; this can be done using Group Policy, Systems Management Server (SMS), or any other deployment mechanism available to you. Any ClickOnce application signed with this Authenticode certificate will run without any prompting. This is a convenient way to trust your enterprise’s applications.
Strong name signatures serve a valuable role in providing a form of identity for managed assemblies. Just remember that private key security is critical for strong names. If you do decide strong name signatures are appropriate for your organization, be sure to use some of the techniques discussed in this column to manage and protect the security of your private key.
Send your questions and comments to firstname.lastname@example.org.
Mike Downen is the program manager for security on the CLR team. He works on Code Access Security, the cryptography classes, and the ClickOnce security model. You can read Mike’s blog and contact him at blogs.msdn.com/CLRSecurity.