A brewing debate in the .Net community recently came to a boil when Brendan Forster attempted to address an open issue for the Octokit library, Signed Octokit Releases, Yay or Nay? It is an interesting debate with many strong opinions. Unfortunately I think the debate is down in the weeds and I’d like to add a bit of a longer term perspective to the discussion.
I will be the first to admit that I have a fairly limited understanding of the implementation details of this subject, so I’ll only attempt to give a high level synopsis of the problem before throwing my opinions around.
The debate centers around whether libraries should assign “Strong names” to their assemblies. Microsoft’s definition of a strong name is,
A strong name consists of the assembly’s identity—its simple text name, version number, and culture information (if provided)—plus a public key and a digital signature.
I’m quite sure someone is going to do a great job of summarizing all the pros and cons that have been debated in the comments to that Github issue. I’m not going to attempt to do that. What I want to focus on is the first part of that Microsoft definition, the part that says a strong name consists of an assembly’s identity.
Why do I care about identity?
Having an identity for a chunk of code should be enable me to do several things:
- guarantee that the bytes in the assembly are exactly the bytes that the publisher of the assembly intended to be in the assembly.
- know who the publisher is to help me decide if I trust their code.
The problem with chunks of code is that over time, they change. We fix bugs, we add features, we refactor. Generally we use a version number to distinguish between these evolutions. The challenge with strong naming is that the version number is embedded into the identity of the assembly. When you change the version number you have created a chunk of code with a completely new identity. Any existing applications that have a dependency on the old version are unable to use the new version, at least without doing some extra magic.
Sometimes this is a good thing, sometimes its a bad thing. If the new version fixes a serious bug then using the new version would be awesome. If the new version completely refactors the public interfaces then it would likely be very bad for existing code to automatically take a dependency on the new version.
Considering that we have stated that we want to be able to guarantee the bytes in the assembly, it seems to make sense that we include the version in the identity. The bytes change between versions, so the identity should too. But if the identity changes, how can we take advantage of new versions without having to manually change dependency references
Binding redirects and the love/hate relationship
The .net framework has a mechanism called binding redirects. It lets an application declare that it is happy to reference a range of versions of an assembly. This allows you to create new versions of assemblies without having to re-specify the dependency in the consuming application.
The problem it seems, is getting these binding redirects right can be painful. In fact the whole strong naming process can be fairly painful. It starts with the fact that you need to generate public/private key pairs. Whenever, you ask developers to deal with crypto based security it gets messy. It’s one of those situations where, once you understand it, it’s not so bad. However, getting to that point is hard. Developers need to understand that strong naming an assembly is called signing an assembly, but that’s not the same as signing a manifest, or code signing an exe, and that one only requires .snk file, but the other requires a pfx file, or is it a p12? They both have public and private keys, which one do I have to do delay signing for? Can I create my own .snk, or do I need to go to a certificate authority? I’m quite sure I said several technically incorrect things in the last three sentences.
Sure, I’m mixing issues here, but these are all mechanism that .net developers use for assigning identity to their chunks of code.
So lets throw away strong naming!
A growing number of people, especially in the OSS community are calling for developers to just stop using strong naming. .Net does not require assemblies to be strong named. For most scenarios, it just isn’t needed.
The problem is, once an organization decides that its application will be strong named, for whatever its reasons, there is a requirement that all dependencies are also strong named. This means that if an OSS library doesn’t strong name its assemblies, it cannot be used by anyone who wants to strong name their stuff. This requirement makes sense considering our original requirement of ensuring we are running exactly the bytes we think we should be running. Having a dependency on a set of mystery bytes defeats that purpose.
The other major problem with throwing away strong naming is when you have applications that have extensibility models. Consider Visual Studio. It supports all kinds of extensions. If ExtensionA and ExtensionB both use libraryX. The publisher of libraryX releases a new version, and ExtensionA decides that it is going to update to that new version. Without strong naming, the .net framework cannot load both the old and the new version of libraryX in process at the same time. This means if the new version of LibraryX is incompatible with ExtensionB, then ExtensionB is going to break. This is DLL hell, all over again.
Stuck between a rock and a hard place
Hence we have the proponents and the opponents. The problem is that the debate seems to be focused around should we have “Strong Naming” or not. I think that is the wrong question. I think it is obvious that there are flaws in the way that strong naming and assembly binding goes about identifying chunks of code. I don’t think with the current implementation constraints we are going find a solution that makes everyone happy.
I think a better question to ask is whether people see value in having a provable way to identify chunks of code. Considering the current state of security concerns on the internet, I think everyone will agree this has value.
If we can agree on the value of having code identity, then maybe we can debate how it could be done better in future versions of the .net framework and we can live with our current pain in the short term.
What do other platforms do?
I’ve heard it mentioned a few times that other platforms don’t have this pain. I’d love to see discussions on how other platforms address this code identity issue. I’m not nearly the polyglot that I would like to be so I can’t provide a whole lot of insight here, but I can cite one other example and do some extrapolation from that.
In the Go programming language, third party dependencies are specified by pointing to a source code repository. The interesting thing about this approach is that the code dependency is identified using a URL. URLs tend to be fairly good at identifying things. They are certainly a whole lot prettier than the fully qualified assembly name syntax we have in .net. Go is very different than .net in that it builds statically linked executables from the source, so the comparison can only go so far. However, the use of a URL to reference code dependencies has some other interesting properties.
URI as a code identifier
By using a https URL we get certificate based guarantee of the origin of our code. We could enable de-referencing the URL to get all kinds of meta data like the publisher, the hash for the assembly, links to newer available versions, and who knows what else. This might eliminate the need to store this metadata in the assembly itself.
Curiously, we already have URIs that identify many pieces of .net code. Consider the following URL,
https://www.nuget.org/packages/Microsoft.Net.Http/2.2.20
If I were able to build an application that took a dependency on the assemblies identified by this URL, I can imagine there being infrastructure in place to guarantee that the bytes I am running are the ones that have actually been published by Microsoft. This is probably not something you would want to do at load time for performance reasons, but why not build something like this into Windows Defender, or some other background process. A big complaint about the current strong naming implementation is by default, an assembly is not hashed to ensure its integrity. So although the security mechanism is in place, it is turned off by default for performance reasons.
One problem with the URL that I showed above is that it is for a Nuget package, not an assembly. .Net specifies dependencies at the assembly level. However, it is not hard to imagine a URL such as the following:
https://www.nuget.org/packages/Microsoft.Net.Http/2.2.20#System.Net.Http
This allows me to reference a specific assembly. You will also notice that it references an assembly that belongs to a specific version of the package. However, it is also possible that I could chose to reference the latest version of the package:
https://www.nuget.org/packages/Microsoft.Net.Http#System.Net.Http
This would allow me to make the statement that my application is prepared to bind to the System.Net.Http assembly in any version of the Nuget package. That’s probably a fairly bold thing to do. We could take a more conservative stance by adopting the conventions of semantic versioning to declare the following:
https://www.nuget.org/packages/Microsoft.Net.Http/2.2#System.Net.Http
Now with this, I’m constraining the versions to those with the same major and minor values. New patches will be happily accepted.
How does this help?
The result of using this mechanism to specify dependencies is that I get a globally unique identifier for my assembly. Tooling can dereference the URL to get publisher information, available versions, assembly hashes. As a developer, I no longer need deal with signing/delay signing assemblies, I get an easy to understand identifier for my references, my metadata is in one place, on a nuget server, not embedded into every single deployed assembly.
I also don’t see any reason why this same mechanism can’t be used for code signing executables. To an extent, this method is similar to the way click-once provides deployment manifests and application manifests. ClickOnce sucks because you have to manually generate those manifests and and sign them in your development/build environment. That is completely unnecessary as a nuget server could easily be enhanced to deliver this information dynamically. With mechanisms like Chocolatey and the new MS efforts in this space, nuget feeds are becoming a viable option for application delivery also. Does everyone really need to have their own code signing certificate when we can deliver our applications from our nuget account using HTTPS?
URIs are proven to be effective at identifying things. HTTPS provides us with crypto based guarantee of provenance. Most of our code is distributed via the web in the first place. Why not use the tools it provides us to identify the code we are running?
Maybe ignorance is bliss
I’m no security expert. Maybe I’m completely missing some obvious reason why this wouldn’t work. However, at the moment, I don’t code sign my stuff. I avoid signing executables unless it is absolutely necessary, and when it is, I fight my way through it by piecing together bits of blog posts that guide me through the process. Who knows how many security related blunders I am making in the process?
One thing I do know, if I find this hard, I can assure you that there are lots of other developers who find it hard. We need to make this easier. We are not going to get there by by having a binary debate on strong naming, yay or nay.
Maybe my idea is completely impractical. That’s ok. I’d rather have that discussion and hear from other people about how they would solve this problem given the chance to start fresh. Let’s start there and then worry about how we are going to get from where we are now to where we want to be.
Credit: Boxing photo https://flic.kr/p/8uQwPu
Credit: Sunrise photo https://flic.kr/p/8A4W9f