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:
/// <summary>
/// Check an assembly to see if it has the given public key token
/// </summary>
/// <remarks>
/// Does not check to make sure the assembly's signature is valid.
/// Loads the assembly in the LoadFrom context.
/// </remarks>
/// <param name='assembly'>Path to the assembly to check</param>
/// <param name='expectedToken'>Token to search for</param>
/// <exception cref='System.ArgumentNullException'>If assembly or expectedToken are null</exception>
/// <returns>true if the assembly was signed with a key that has this token, false otherwise</returns>
public static bool CheckToken(string assembly, byte[] expectedToken)
{
if(assembly == null)
throw new ArgumentNullException("assembly");
if(expectedToken == null)
throw new ArgumentNullException("expectedToken");
try
{
// Get the public key token of the given assembly
Assembly asm = Assembly.LoadFrom(assembly);
byte[] asmToken = asm.GetName().GetPublicKeyToken();
// Compare it to the given token
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)
{
// couldn't find the assembly
return false;
}
catch(BadImageFormatException)
{
// the given file couldn't get through the loader
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:
// check the signature first
bool notForced = false;
bool verified = StrongNameSignatureVerificationEx(assembly, false, ref notForced);
Console.WriteLine("Verified: {0}\nForced: {1}", verified, !notForced);
// check to see if it is a Microsoft assembly
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");
Comments
Anonymous
June 08, 2004
is it possible to use any other functions from mscorsn.dll:
i.e.
StrongNameSignatureVerificationFromImage,
StrongNameSignatureVerification
and what do the flags:
1)SN_INFLAG_FORCE_VER
2)SN_INFLAG_INSTALL
3)SN_INFLAG_ADMIN_ACCESS
4)SN_INFLAG_USER_ACCESS
5)SN_INFLAG_ALL_ACCESS
6)SN_INFLAG_RUNTIME
7)SN_OUTFLAG_WAS_VERIFIED
some of the flags are self describing but I am not sure about 2-6
Thanks for the info and keep up the good work.
Dave.Anonymous
June 09, 2004
Hi Dave,
Sure, you can use anything exported from mscorsn.dll (just remember that mscorsn is going away in Whidbey ... these functions will be moving to mscorwks, and should also have redirecting stubs in mscoree). As for what those functions do:
StrongNameSignatureVerificationFromImage: This function verifies the signature on a module already loaded into memory.
StrongNameSignatureVerification: This performs almost the same job as StrongNameSignatureVerificationEx. The difference is that StrongNameSignatureVerificationEx handles the flags you mention for you. Calling StorngNameSignatureVerificationEx is roughly equivilant to calling StrongNameSignatureVerification with a combination of SN_INFLAG_INSTALL, SN_INFLAG_ALL_ACCESS and possibly SN_INFLAG_FORCE_VER depending on if you set fForceVerification. It also decodes the output flags and sets pfWas Verified if necessary.
As for the flag constants, by sticking to StrongNameSignatureVerificationEx, you generally won't need to use them. The only ones that are useful to external callers are SN_INFLAG_FORCE_VER and SN_INFLAG_INSTALL, both of which effectively do the same thing (force verifiication, even if the registry key to disable verification is set). SN_OUTFLAG_WAS_VERIFIED translates to the pfWasVerified flag of StrongNameSignatureVerificationEx.
The SN_INFLAG_xxx_ACCESS flags can be used by fusion when its storing assemblies in the GAC, but shouldn't be used by external callers.
-ShawnAnonymous
June 15, 2004
Hi Shawn
Won't the verification happen when the loader loads the assembly , won't it verify that the assembly has a valid signature?
thanks
neerajAnonymous
June 16, 2004
Hi Neeraj,
The loader will generally check that the signature is valid, however it’s possible for an admin on the machine to disable strong name verification by using Sn –Vr (or directly modifying the registry.) Additionally, assemblies added to the GAC are verified at installation time, not at load time. These assemblies should not be able to be tampered with, but if they have been, they won't fail verification.
The sample code lets you enforce a signature check even if it has been disabled by an admin. If you’d rather not always force the signature check, then you can just let the loader do the verification for you.
-ShawnAnonymous
May 16, 2005
How to do the same of kind of checks for referenced assemblies? Is it there a need to do the strong name validation check(Integrity check)?Anonymous
March 15, 2007
I have a problem with a life annuity claim after my dad's death. The Co. says his signature on a changing beneficiaries form does not match the one they have on file. Do you know where I could send several copies of notarized or witnessed signatures to have his last signature authorized as being authentic? Thank you for any help.Anonymous
December 02, 2009
Thanks, worked a treat for me in .Net 2 and added peace of mind for my plugin architecture.