Checking For A Valid Strong Name Signature
Recently a question came up from someone who was trying to have a plugin architecture for their application, but wanted to do some checks before loading a plugin. Specifically, they wanted to ensure that the plugin was signed with a specific public key. An initial attempt at this code might produce something like this:
public static bool CheckToken(string assembly, byte[] expectedToken)
{
if(assembly == null)
throw new ArgumentNullException("assembly");
if(expectedToken == null)
throw new ArgumentNullException("expectedToken");
try
{
Assembly asm = Assembly.LoadFrom(assembly);
byte[] asmToken = asm.GetName().GetPublicKeyToken();
if(asmToken.Length != expectedToken.Length)
return false;
for(int i = 0; i < asmToken.Length; i++)
if(asmToken[i] != expectedToken[i])
return false;
return true;
}
catch(System.IO.FileNotFoundException)
{
return false;
}
catch(BadImageFormatException)
{
return false;
}
}
The problem here is that this code only gets the public key token, it doesn't check for a valid signature. Anyone could hack their assembly and modify the reported public key token, but you'd be unable to discover this without checking to see if the assembly passes verification.
There's no managed API to verify that an assembly's strong name is valid, so you must P/Invoke out to StrongNameSignatureVerificationEx (located in mscorsn.dll in v1.0 and 1.1 of the framework, mscorwks.dll in Whidbey ... both versions provide a forwarding stub in mscoree.dll). The P/Invoke signature to use looks like:
[DllImport("mscoree.dll", CharSet=CharSet.Unicode)]
static extern bool StrongNameSignatureVerificationEx(string wszFilePath, bool fForceVerification, ref bool pfWasVerified);
The parameters can be little confusing, so let me provide a short explanation. wszFilePath is, as you would expect, the path to the file that is to be checked. fForceVerification is a boolean flag that tells StrongNameSignatureVerificationEx if it should set the pfWasVerified output flag. The return value from the API is true if the strong name is valid, false otherwise.
fForceVerification and pfWasVerified work together to allow you to use the skip verification list. (More details on this can be found in my post on delay signing). If you pass false to fForceVerification, StrongNameSignatureVerificationEx will consult the skip verification list, and the return value of the API call will be true if the assembly was on the list. However, since no verification was actually performed, the pfWasVerified flag will be set to false. pfWasVerified is only set if fForceVerification is set to false, if you pass true to this parameter, pfWasVerified will be meaningless.
It's a little easier to think of it this way. StrongNameSignatureVerificationEx will by default consult the skip verification list, and return true if the assembly is on that list. If you really want to force the issue, pass true into fForceVerification in order to force the API into actually doing the signature check on the input assembly.
Here's some scenarios that might help to clarify:
| fForceVerification | fWasVerified | Return Value |
| Strongly Named Assembly | true | true | true |
| Strongly Named Assembly | false | true | true |
| Strongly Named and Tampered With | true | false | false |
| Strongly Named and Tampered With | false | false | false |
| Delay Signed, Skip Verification | true | false | false |
| Delay Signed, Skip Verification | false | false | true |
Using that information, combined with the code from the beginning of the post, it's pretty easy to check the validity of signatures on an assembly (and check that you know who signed them). For instance, here's some code that checks for assemblies that ship with the .NET Framework:
bool notForced = false;
bool verified = StrongNameSignatureVerificationEx(assembly, false, ref notForced);
Console.WriteLine("Verified: {0}\nForced: {1}", verified, !notForced);
byte[] msClrToken = new byte[] { 0xb7, 0x7a, 0x5c, 0x56, 0x19, 0x34, 0xe0, 0x89 };
byte[] msFxToken = new byte[] { 0xb0, 0x3f, 0x5f, 0x7f, 0x11, 0xd5, 0x0a, 0x3a };
bool isMsAsm = CheckToken(assembly, msClrToken) || CheckToken(assembly, msFxToken);
if(isMsAsm && verified && notForced)
Console.WriteLine("Microsoft signed assembly");
else if(isMsAsm && verified && !notForced)
Console.WriteLine("Microsoft delay signed assembly");
else if(isMsAsm && !verified)
Console.WriteLine("Microsoft assembly, modified since signing");
else
Console.WriteLine("Not a Microsoft assembly");