Delay Signing

Most people know about the delay signing feature of the CLR.  (For those who don't check out MSDN's Delay Signing an Assembly for more details).  Basically, delay signing allows a developer to add the public key token to an assembly, without having access to the private key token.  Since the public key token is part of an assembly's strong name, it allows assemblies under development to carry the same identity as they will have when they are actually signed, however it does not require every developer to have access to the private keys.

For instance, at Microsoft, in order to get an assembly signed, we have to submit it to a special signing group, which are the only people that have access to the full Microsoft key pair.  Obviously we don't want to go through this process for every daily build of the framework, let alone for each developer's private builds (imagine the debugging process if you had to wait for a central key group to sign each and every build you created).  Instead of going through all of this overhead, we simply delay sign our assemblies until we get ready to make a release to the public, at which point we go through the formal signing process.

A delay signed assembly contains only the public key token of the signing key, not an actual signature.  (Since, the person producing the delay signed assembly most likely doesn't have access to the private key necessary to create a signature).  Inside the PE file produced, a delay signed assembly has space reserved for a signature to be placed in the future, but that signature is actually just a block of zeros until the real signature is computed.  Because this block is not likely to be the actual signature value of the assembly, these assemblies will all fail to verify upon loading.  (since their signatures are incorrect).

Obviously, it wouldn't be very useful if a delay signed assembly were completely unable to load.  To work around this problem, assemblies need to be added to the skip verification list.  This is done with the sn command.  The specific command line is:

sn -Vr assembly [users]

Where assembly is the name of the assembly to skip.  You can also use the format *,publicKeyToken to skip verification for all assemblies with a given public key token.  Users is a comma separated list of users that verification will be skipped for.  If this part is left out, verification is skipped for all users.

The Problem

What this command does is tell the runtime to not verify the signature on an assembly that has the given public key token (if you use the *,publicKeyToken format), or just on a specific assembly.  However, when you stop to think about it, this is a gigantic security hole.

Public key tokens are easily read from any assembly that you have access to.  By running ILDasm on System.dll, inside the manifest, I find the line:

.publickey = (00 00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 )

Which corresponds to the public key assigned to any assembly standardized by ECMA / ISO.  The token can be easily computed from this value, however an easier way to get it would be to look at ILDasm on any assembly that references mscorlib.  For instance, looking at the manifest of System.Xml.dll under ILDasm shows me the line:

.assembly extern System
{
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
  .ver 1:0:5000:0
}

Which shows the ECMA / ISO public key token.  Now, as a malicious developer, its very easy for me to write an assembly that is named System.dll, with an assembly version of 1.0.5000.00, and put the public key I just extracted from System.dll into my assembly.  I won't be able to compute a valid signature, since I don't have access to the ECMA / ISO private key pair, but that hardly matters to me, since you've turned off strong name verification for this particular public key token.  All I have to do is install this assembly in place of System.dll in your GAC, and I now own your machine.

For this reason, it's vital to only skip verification for assemblies that you are developing yourself, and be extra careful about what code gets downloaded onto your machine that may claim to be from your organization.

Protection

There was some confusion about this issue on the public newsgroups recently.  Even if you take precautions inside your company, how can you be sure that someone external to your company does not get into a situation where they disable checking the strong name on your assemblies and yours gets swapped with an evil counterpart?

The short answer to this is you can't.  The skip verification list is stored in the registry under HKLM\Software\Microsoft\StrongName\Verification\<asmName,publicKeyToken>, which is protected by an ACL such that anyone can read it, but only administrators can write to it.  So if a malicious developer has managed to write a public key token into your user's skip verification list, this means that one of two things have happened

  1. Someone has modified the ACL, allowing more write access to this key than usual
  2. The malicious developer is already an administrator on the machine

If 1 is true, then the ACL should be reverted to only allow administrators to write to the key, thus closing the hole.  If 2 is true, then the malicious developer already owns your machine.  As an admin, they could conceivably replace the CLR with a hacked version that doesn't verify assembly signatures, or perhaps doesn't implement CAS.  If you've gotten into situation #2, game over.

Delay signed assemblies serve to increase security in development shops, by reducing the number of people that need access to an organization's private keys.  However, the requirement that delay signed assemblies need to be registered in the skip verification list means that developers machines are open to various forms of attack.  Making sure that your developers are aware of the situation, in combination with not overusing your skip verification list will help to make your machines more secure in these environments.