Dr. Strongname, or: How I Learned to Stop Worrying and Love the URL

One of the problems with the Trustworthy Computing initiative is that many of our products have become harder to use as a result, either due to configuration changes or documentation changes. For example, Windows Server 2003 now ships with pretty much everything turned off by default, but customers that just want to "plug it in and go" get frustrated because now they have to configure everything that used to just be turned on automatically.

For VSTO, the problem is more to do with the way we document / blog about / evangelise the product to customers. Whereas in the past the documentation might have told customers the easiest way to accomplish something and left the more secure method to a side note or appendix, nowadays we focus on the more secure methods first and this can also be frustrating for customers who are willing to make the usability / security trade-off in favour of usability.

So, customers hear about VSTO, they hear about its strict security requirements, and they hear about this thing called "strong naming" and conclude that they need to strongname their code in order for it to work. It doesn't help that every time someone asks a question about VSTO security, we usually say "Well, you could just trust the folder, but that's not really recommended - you should use a strongname to be more secure!" But that's pretty much the responsibility we have with Trustworthy Computing; we need to tell people the "better" way of doing things rather than the "easier" way.

Anyway, the problem with strongnames is that they weren't really designed to be used as security principals -- they were designed to uniquely identify assemblies and to detect tampering or spoofing attacks against assemblies. In order to ensure that two assemblies with the same name get different identities, strongnames use the same kind of public-key digital signature technology that Authenticode signing does. This means that, cryptographically speaking, the kinds of guarantees that the system can make about an assembly are the same for both strongnames and Authenticode signatures. Couple this with the fact that anyone can generate a strongname keypair for free with sn -k mykey.snk (versus setting up a certificate server or dropping cold hard cash to Verisign or another 3rd party CA) you can see why strongnames are a popular way to trust assemblies.

But strongnames are first and foremost are about guaranteeing the identity of an assembly, and one of the properties of an assembly's identity is its version number. When one assembly Budget.dll is dependent on a strongnamed assembly Helper.dll, the information baked into Budget's manifest includes the version number of Helper as part of the reference information. If, at runtime, the CLR cannot find the exact version of Helper that Budget was originally compiled against, it will fail to load the assembly and probably cause the application to die horribly as a result. (Publishers can supply version-redirection information with their assemblies to modify this behaviour, but that's another complication I don't want to get into right now).

So let's say that you build an Excel-based VSTO solution with Helper.dll version 1.0.0.0 and Budget.dll version 1.0.0.0, you put them both on a share, and people start using them. Someone reports a bug in the application, and you track it down to a problem in Helper.dll, which you re-build as Helper.dll version 1.1.0.0 and upload it to the share, over-writing the old version.

But now you get calls from people saying that the solution is broken, so you run the app in the debugger and determine that it is because Budget.dll is looking for version 1.0.0.0 of Helper.dll, but only version 1.1.0.0 is available. Since you probably didn't know about <bindingRedirect> and/or you don't have time to investigate further, you take the easy way out -- just change the version of Budget.dll back to 1.0.0.0, re-build it, and ship out the "new" version of the assembly to the share again.

Ah-ha, but now something else goes wrong. You get calls from a different set of users who claim that you haven't fixed the bug at all -- it is still happening on their machines. You double-check to make sure that the "right" version of Budget.dll version 1.0.0.0 is on the share, and sure enough it is. You fire up the VSTO solution on your desktop and verify that, sure enough, the fixed version of the assembly is loading. Just to be sure, you make some additional changes to the code (like, say, putting MessageBox.Show("This is the fixed version!") in the constructor), re-build it, copy it to the share, and run the solution again.

Hmmm, that's weird -- the message box never popped up. You do a bit more poking around and add the following line to the assembly:

  MessageBox.Show(this.GetType().Assembly.Location);

Now when you run the solution, you see that the assembly is running from some seemingly random location in your profile folder -- not from the network share where you would expect it to be loading from:

c:documents and settingsjoe_userlocal settingsapplication dataassemblydl2a7dn830f.ad2aofhwldc.vc072d679a88368c70e_93af78bchelper.dll

What's happening? Well, when you open the project, the CLR loads Budget.dll and sooner or later it tries to use a type defined in Helper.dll. Fusion is asked to load the assembly, and since it has a strong name with the version 1.0.0.0, Fusion looks in the local download cache to see if it already has that assembly -- why download it again over a slow network link when you can load it right off the local disk (and Windows may already have it mapped into memory, too)? Of course Fusion sees that it does already have a local copy of version 1.0.0.0 of Helper.dll, so it loads that without going out over the network to download the other copy -- after all, you strongnamed the assembly so it's guaranteed to be the right one. If it had been changed, the digital signature wouldn't match.

But of course it's the "wrong" copy of version 1.0.0.0, because you made changes to it but didn't update the version number. You're lying to Fusion about the version of an assembly, and then you're wondering why the system isn't working as expected. (As an aside, the first set of users who complained about getting assembly load failures due to the wrong version number would have been the ones who had never previously downloaded the buggy version of Helper.dll, so Fusion didn't have a copy in the cache).

So now you're really in a bind:

·You have to use strongnames, because you need to be secure

·But if you use strongnames, you have to keep updating the version numbers to play by Fusion's rules

·And if you update the version numbers, your applications break

·But the only way to bypass Fusion's cache lookup is not to use strongnames

·
And you have to use strongnames!

·Argh! What's a poor developer to do?!?

Well, take a step back; as I noted in a recent post, security is all about managing risk. If your efforts to be secure cause more problems than they solve, it may be time to re-think your strategy.

We recommend that users rely on some form of cryptographic evidence such as a strongname or Authenticode signature because it provides a high degree of assurance that the assembly you are running really is the one you think you are running -- even if a malicious user can gain write-access to the location of your assembly, they can't modify its contents or replace it with an entirely different assembly without breaking the signature and thereby causing the CLR to reject any requests to load the assembly. Only if the malicious user has access to your private key (ooops!) and they have write access to the assembly's location can they cause damage in this scenario.

But if you have good controls over access to the assemblies' location (ie, you ensure that only trusted parties can upload or modify files on the server) then you may be comfortable with just using URL-based evidence for your VSTO solutions. Besides, in a typical scenario there are likely to be other things on that server that a bad guy could take advantage of if they gained write-access to it, so you're really no less secure than you were before by relying only on URLs.

So, if you're running a controlled environment inside the firewall, maybe you can drop the strongnames (and the versioning issues that come with them) and just rely on publishing your assemblies to a trusted server location. For information on how to set up policy based on URLs, you can consult the VSTO documentation.