MEF: Component authentication
I am building a Windows (Service) application that, in short, consists of a "bootstrapper" and an "engine" (an object loaded by the bootstrapper, which transfers control to it, and then performs the actual tasks of the application). The bootstrapper is a very basic startup routine that has few features that are likely to change. But the engine itself could be subject to upgrades after installation, and I am implementing a mechanism so that it can upgrade itself - by contacting a "master server" and checking its version number against a "most current" version. If there is a newer version of the engine available, it will download it into a designated folder and call a method in the bootstrapper to "restart".
So, whenever the bootstrapper starts up, it uses MEF to "scan" the appropriate directo开发者_如何学运维ries for implementations of IEngine, compares their bootstrapper compatibility numbers and picks the newest compatible engine version. Then it transfers control to the engine (which then, in turn, performs the update check etc). If there are no eligible IEngines - or MEF fails during composition - it falls back on a default, built-in implementation of IEngine.
This application will be running on a remote server (or several), and the whole rationale behind this is to keep manual application maintenance to a minimum (as in not having to uninstall/download new version/reinstall etc).
So, the problem: Since the bootstrapper effectively transfers program execution to a method on the IEngine object, a malicious IEngine implementation (or impersonator) that somehow found its way to the application's scanned folders could basically wreak total havoc on the server if it got loaded and was found to be the most eligible engine version.
I am looking for a mechanism to verify that the IEngine implementation is "authentic" - as in issued by a proper authority. I've been playing around with some home brewn "solutions" (having IEngine expose a Validate function that is passed a "challenge" and has to return a proper "Response" - in various ways, like having the bootstrapper produce a random string that is encrypted and passed to the engine candidate, which then has to decrypt and modify the string, then hash it, encrypt the hash and return it to the bootstrapper which will perform a similar string modification on its random string, then hash that and compare that hash to to the decrypted response (hash) from the candidate etc), but I'm sure there are features in .Net to perform this kind of verification? I just looked at Strong Naming, but it seems it's not the best way for a system that will be dynamically loading yet unthought-of dlls..
Input will be greatly appreciated.
Assemblies can be digitally signed with a private key. The result is called a strong named assembly.
When a strong named assembly is loaded, .NET automatically checks whether its signature matches the embedded public key. So when a strong named assembly has been loaded, you have the guarantee that the author posseses the private key that corresponds to that public key.
You can get the public key by calling Assembly.GetName().GetPublicKey() and then compare it to the expected one, i.e. yours.
You can scan over the plugin assemblies, create an AssemblyCatalog
for each one with the right public key (rejecting the others), finally aggregating them into an AggregateCatalog
and building a CompositionContainer
with it.
This is basically what Glenn Block also explained in this thread. (Best ignore the blog post linked there by Bnaya, his interpretation of StrongNameIdentityPermission
is not correct.)
edit with responses to the wall of comments:
To get that public key, I make the console application output the public key byte array to somewhere. I embed the byte array in my host application, and subsequently use that to compare against the public keys of plugin candidates. Would that be the way to do it?
Yes, but there is a simpler way to extract the public key. Look at the -Tp
option of sn.exe.
Does this mechanism automatically prevent a malicous plugin assembly from exposing a correct, but "faked" public key? As in, is there some mechanism to disqualify any assembly that is signed, but has a mismatch between its exposed public key and it's internal private key, from being loaded/run at all?
As far as I know, the check happens automatically. A strong named assembly cannot be loaded (even dynamically) if its signature is wrong. Otherwise the strong name would be useless. To test this, you can open your strong named assembly in a hex editor, change something (like a character in a const string
embedded in the assembly) and verify that the assembly can no longer be loaded.
I guess what I was referring to was something akin to the type of hack/crack described here: http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-security/407/Signed-assemblies-easily-cracked and here: Link
[...snip more comments...]
However, this can - apparently - be bypassed by simple tampering (as shown in first link, > and explained more here): grimes.demon.co.uk/workshops/fusionWSCrackOne.htm
The "attacks" you refer to fall in three categories:
- removing the strong name altogether. This does not break the authentication, the assembly will no longer have a public key and so you will reject it.
- disabling the strong name check, which requires full access to the machine. If this was done by an attacker, then it would mean that the attacker already owns your machine. Any security mechanism would be meaningless in such a context. What we are actually defending against is an attacker between the machine and the source of the assemblies.
- a real exploit made possible by a bug in .NET 1.1 that has since been fixed
Conclusion: strong names are suitable to use for authentication (at least since .NET 2.0)
I've written a blog post with source code for a catalog which only loads assemblies with keys that you specify: How to control who can write extensions for your MEF application
精彩评论