Strong Names
Strong names allow you to grant full trust to only those assemblies that your organization (or other organizations that you trust) created. Confusion abounds about what exactly "strong names" are, what they are for, and how they work.
Back in the old days of "DLL hell," dynamically linked libraries were loaded based on filename and location. This approach has an inherent fundamental security problem: Attackers can name their evil DLLs system32.dll or oleaut32.dll, too. Attackers could try to trick you into loading their code rather than the code that you want to load by taking advantage of this weakness in the naming system.
The traditional DLL system also suffers from other technical problems, such as versioning. When you load oleaut32.dll, which version are you getting? Writing the code to figure it out is not rocket science, but it is not as easy as it could be.
Strong names mitigate these weaknesses. The purpose of a strong name is to provide every assembly with a unique, hard-to-forge name that clearly identifies its name, version, and author. When you load an assembly based on its strong name, you have extremely good evidence that you are actually loading the code you expect to be loading, not some hostile version that some other author managed to slip onto your machine.
Creating a Strong-Name Code Group
Because strong names identify the customization's author, you could set a policy that states that any code by a particular author is fully trusted. Suppose you have a strong-named assembly and you want to set a policy that says that all assemblies by this author are to be fully trusted. Again, create a new code group as a child of the location code group created before, but this time select the Strong Name membership condition shown in Figure 19-7.
Figure 19-7. Creating a code group with a strong-name membership condition.
Import the public key from the strong-named assembly, and you have created a policy that trusts all assemblies by that author. (As the dialog notes, you can further strengthen the policy by trusting only certain names or even only certain versions.) But what exactly is a "public key" and what does it have to do with the code's author?
How Strong Names Are Implemented
Strong naming works by using public key cryptography. The mathematical details of how public key cryptosystems work would take us far off-topic, but briefly it goes something like this: An author generates two keys, appropriately called the public key and the private key. Assemblies can be signed with the private key, and the signature can be verified with the public key.
Therefore, if you have a public key and an assembly, you can determine whether the assembly was signed with the private key. You then know that the person who signed the assembly possessed the private key. If you believe that the author associated with that public key was not careless with the private key, you have good evidence that the assembly in question really was signed by the author.
The signing process is highly tamper-resistant. Changing so much as a single bit of the assembly invalidates the signature. Therefore, you also have good evidence that the assembly has not been changed post-release by hostile attackers out to get you.
Why Create a Child Code Group?
You might have wondered why we recommended that you create your Strong Name code group as a child code group of the location-based code group discussed earlier. And come to think of it, in the out-of-the-box Machine policy level, the Microsoft Strong Name code group is a child of the Local Machine Zone code group. Why is that? Surely if having a strong name is sufficient to grant full trust, it should be sufficient no matter where the code came from.
Code groups with membership conditions based on some fact about the assembly itself should always be children of location-based code groups. Here is why: Suppose you trust Foo Corporation. For the sake of argument, we assume that this trust is justified; Foo Corporation really is not hostile toward you. Consider what would happen if your Enterprise policy level grants assemblies signed with Foo Corporation's key full trust, period, with a level-final code group. You impose no additional location-based requirement whatsoever.
Foo Corporation releases version 1.0 of their FooSoft library, and no matter where foosoft.dll is located, all members of your enterprise fully trust it. Foo Corporation releases version 2.0, then version 3.0, and so on. Everything is fine for years.
But one day, some clever and evil person discovers a security hole in version 1.0. The security hole allows partially trusted codesay, code from a low-trust zone such as the Internetto take advantage of FooSoft 1.0's fully trusted status to lure it into using its powers for evil.
Even if that flaw does not exist in the more recent versions, you are now vulnerable to it. Your policy says to trust this code no matter where it is, no matter what version it is. Evil people could put it up on Web sites from now until forever and write partially trusted code that takes advantage of the security hole, and you can do nothing about it short of rolling out new policy.
If, on the other hand, you predicate fully trusting FooSoft software upon the software being in a certain location, that scopes the potential attack surface to that location alone, not the entire Internet. All you have to do to mitigate the problem is remove the offending code from that location and you are done.
That explains why the Microsoft Strong Name code group is a child of the My Computer Zone code group. Should an assembly with Microsoft's strong name ever be found to contain a security flaw, the vulnerability could be mitigated by rolling out a patch to all affected users. If the out-of-the-box policy were "trust all code signed by Microsoft no matter where it is," there would be no way to mitigate this vulnerability at all; the flawed code would be trusted forever, no matter what dodgy Web site hosts it.
This best practice for strong name code groups also applies to other membership conditions that consider only facts about the assembly itself, such as the hash and publisher certificate membership conditions. |
Now that we have a child code group that grants full trust to code that is both strong-named and in a trusted location, we can reduce the permission set granted by the outer "location" code group to nothing. That way, only code that is both strong named and in the correct location will run.
Implementing Strong-Named Assemblies
So far we have been talking about the administrative problem of trusting a strong-named assembly after you have one. What about the development problem of creating the strong-named assembly in the first place? The process entails four steps:
1. |
Designate a signing authority (that is, some highly trusted and security-conscious person in your organization who can ensure the secrecy of the private key).
|
2. |
Create a key pair and extract the public key from the key pair. Publicize the public key, and keep the private key a secret.
|
3. |
Developers doing day-to-day work on the assembly should delay-sign it with the public key.
|
4. |
When you are ready to ship, the signing authority signs the assembly with the private key.
|
Let's take a look at each of these steps in detail.
Designate a Signing Authority
A strong name that matches a particular public key can be produced by anyone who has the private key. Therefore, the best way to ensure that only your organization can produce assemblies signed with your private key is to keep the private key secret. Create a small number (preferably one) of highly trusted people in your organization as signing authorities and make sure that they are the only people who have access to the private key file.
Create a Key Pair
When you need a key pair for your organization, the signing authority should create a private key file to keep to themselves, and a public key file for wide distribution. The strong-name key generation utility is sn.exe and it can be found in the bin directory of your .NET Framework SDK:
> sn.exe -k private.snk Microsoft (R) .NET Framework Strong Name Utility Version 2.0 Copyright (C) Microsoft Corporation. All rights reserved. Key pair written to private.snk > sn.exe -p private.snk public.snk Microsoft (R) .NET Framework Strong Name Utility Version 2.0 Copyright (C) Microsoft Corporation. All rights reserved. Public key written to public.snk
The private.snk file contains both the public and private keys; the public.snk file contains only the public key. Do whatever is necessary to secure the private.snk file: burn it to a CD-ROM and put it in a safety deposit box, for example. The public.snk file is public. You can e-mail it to all your developers, publish it on the Internet, whatever you want. You want the public key to be widely known, because that is how people are going to identify your organization as the author of a given strong-named assembly.
Developers Delay-Sign the Assembly
Developers working on the customization in Visual Studio will automatically get their User policy level updated so that the assembly that they generate is fully trusted. But what if they want to test the assembly in a more realistic user scenario, where there is unlikely to be a User-level policy that grants full trust to this specific customization assembly? If users are going to trust the code because it is strong named, developers and testers need to make sure that they can run their tests in such an environment.
But you probably do not want to make every developer a signing authority; the more people you share a secret with, the more likely that one of them will be careless. And you do not want the signing authority to sign off on every build every single day, because pre-release code might contain security flaws. If signed-but-flawed code gets out into the wild, you might have a serious and expensive patching problem on your hands.
You can wriggle out of this dilemma in two ways. The first is to create a second key pair for a "testing purposes only" strong name for which every developer can be a signing authority. Your test team can trust the test strong name, making the tests more realistic. Because it is unlikely that customers ever will trust the test-only public key, there is no worry that signed-but-buggy pre-release versions that escape your control will need to be patched.
That is considerably better than real-signing every daily build, but we can do better still; another option is to delay-sign the assembly. When the signing authority signs the assembly, the public key and the private-key-produced signature are embedded into the assembly; the loader reads the public key and ensures that it verifies the signature. By contrast, when a developer delay-signs the assembly, the public key and a fake signature are embedded into the assembly; the developer does not have the private key, and therefore the signature is not valid.
To delay-sign a customization, right-click the project in the Solution Explorer and select Properties. In the Properties pane, click Signing, and then choose the public key file, as shown in Figure 19-8.
Figure 19-8. Delay-signing a customization.
If the signature is invalid, won't the loader detect that the strong name is invalid? Yes. Therefore, developers and testers can then set their development and test machines to have a special policy that says "skip signature validation on a particular assembly":
> sn.exe -Vr ExpenseReporting.DLL
Skipping signature validation on developer and test machines makes those machines vulnerable. If an attacker can deduce what the name of your customization is and somehow trick a developer into running that code, the hostile code will then be fully trusted. Developers and testers should be very careful to not expose themselves to potentially hostile code while they have signature verification turned off. Turn it back on as soon as testing is done. |
You can turn signature validation back on with
> sn.exe -Vu ExpenseReporting.DLL
or use Vx to delete all "skip validation" policies.
Really Sign the Assembly
Finally, when you have completed development and are ready to ship the assembly to customers, you can send the delay-signed assembly to the signing authority. The signing authority has access to the file containing both the private and public keys:
> sn.exe -R ExpenseReporting.DLL private.snk
Public Keys and Public Key Tokens
One more thing about strong names, and then we'll move on. A frequently asked question about strong names is "what's the difference between a public key and a public key token?"
The problem with public keys is that they are a little bit unwieldy. The Microsoft public key, for example, when written out in hexadecimal is as follows:
002400000480000094000000060200000024000052534131000400000100010007D1FA 57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5 DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4 E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF 0FC4963D261C8A12436518206DC093344D5AD293
That's a bit of a mouthful. It is easier to say, "I read Hamlet last Tuesday and quite enjoyed it," than "I read a play that goes like this. Bernardo says, 'Who's there?'" and finishing up four hours later with "'Go, bid the soldiers shoot,' last Tuesday and quite enjoyed it."
Similarly, if you want to talk about a public key without actually writing the whole thing out, you can use the public key token. The public key token corresponding to the public key above is b03f5f7f11d50a3a, which takes up a lot less space. Note, however, that just as the title Hamlet tells you nothing about the action of the play, the public key token tells you nothing about the contents of the public key. It is just a useful, statistically-guaranteed-unique 64-bit integer that identifies a particular public key.
Public key tokens are usually used when you write out a strong name. For example, the strong name for the VSTO 2005 runtime is this:
Microsoft.VisualStudio.Tools.Applications.Runtime, Version=8.0.1200.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, ProcessorArchitecture=MSIL