views:

769

answers:

3

From the description of sn.exe utility and this question I see that a copy of the public key is added to every assembly signed with the strong name. This is enough to validate that the assembly binary has not been altered.

But how does one verify that given assembly was really signed with some given keypair and compiled by a given company? Anyone could generate his own keypair, produce some assembly and sign it with his keypair. Do I need to publish the public key so that those who want to verify the assembly origin could compare the public keys? if so, what is the best way to do so?

+3  A: 

I've always understood the purpose of signing an assembly to be a runtime check to ensure that upgrades to the code come from a trusted source. Effectively they are trying to avoid this kind of scenario:

I purchase a encryption library from Company A, shortly afterwards Company B gets hold of my email details and sends me a free upgrade to the assembly pretending to be a major security bug fix from Company A when it really injects a hidden method into my code that sends all the data I was trying to encrypt off to company B's servers. Assuming that I'm an idiot and blindly add this new dll into my application's bin directory the fact that this wasn't signed with the same private key as the old version will be picked up at runtime causing an exception and protecting my data.

So, I see no reason that you would publish the public key outside of the assembly since it isn't supposed to guarantee that the original file comes from a specific vendor, only that all subsequent versions come from the same place as the first.

Wikipedia says much the same thing (only in significantly fewer words).


Edited to add further information in an attempt to make this clearer...

I think the thing that needs to be clarified first is that the pairs of public and private keys are unique. That means that knowing the public key is not enough information to rebuild the assembly in a way that it will pass the same hash checks.

So User Z who is using an encryption library from Company A which is in his applications bin folder. To do this he is referencing the DLL as follows:

Encryption, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bcd6707151635d07"

The purpose of the public key is to provide us with three benefits which I'll handle one at a time.

Firstly, it provides a unique name for the assembly - this gives us no extra security but does stop C style dll Hell where two different companies could release two different libraries called Encyption version 1.0.0.0 and you wouldn't be able to store them in the same directory since there is no way to differentiate them.

Secondly, it prevents the scenario that I outlined in my original post. It is impossible for Company B to create another version of the Encryption library which is also version 1.0.0.0 and has the same public key (so that the whole name would match and you'd call their code instead of Company A's). They can't do this because if their public key matched then the private key must also match (since each pair is unique). The only way they can get the private key to match is by compromising Company A's security.

Finally it ensures the integrity of the file (either changed through corruption or malicious code injection). The dll is hashed at runtime (when it is private and in the bin folder of the application) or install time (when you put it in the GAC) using the public key and that hash is compared to the hash that was encrpyted by the private key and stored in the assembly. This page has some diagrams and more details. Once again the only way to fake this hash is to know the private key.

So, to cover the specific scenario that I described above, pretend that I am Company B trying to supply User Z with a malicious version of the encryption dll. My first attempt is to simply make my own dll with the name Encryption and Version 1.0.0.0 and sign it with my own key pair. Unfortunately I can't change the reference code in User Z's application so it fails the full name check and doesn't get loaded. "Okay," says I while twirling my moustache, "I'll simply change the public key of my assembly to match that of Company A's". Once I've done this the name check passes, but the hash check will fail because User Z's app won't be able to decrypt the hash stored in the assembly (which was encrypted with Company B's private key) with the public key (Company A's) that has been supplied. Therefore the only way for Company B to create a library that pretends to be Company A's is to know Company A's private key. None of these security checks are reliant on Company A publishing the public key anywhere else but in the original release of the assembly.

Note also that all of these features don't ensure (and don't claim to ensure) that the original assembly came from Company A, that is handled by other systems such as Verisign or Microsoft's Authenticode service, they only ensure that once you have referenced the assembly only Company A can make changes to that code.

Martin Harris
That's totally unclear and not mentioned in the linked article explicitly. In order to do all this the runtime needs to cache the hashes of each and every assembly and verify that when loading the assembly. Does it really do this?
sharptooth
According to "CLR via C#" by Jeffery Richter it does when the assembly isn't in the GAC. Quoting from the book: "When strongly named assemblies are loaded from a location other than the GAC, the CLR compares hash values when the assembly is loaded. In other words a hash of the file is performed every time an application executes and loads the assembly. This performance hit is a tradeoff for being certain that the assembly file's content hasn't been tampered with."
Martin Harris
I'll also add this quote from "C# via the CLR" (highly recommended book by the way): "Important: This mechanism ensures only that a file's content hasn't been tampered with. The mechanism doesn't allow you to tell who the publisher is unless you are absolutely positive that the publisher produced the public key that you have and you're sure that the publisher's private key was never compromised." So, even if they do publish the public key separately from the assembly it still doesn't truly allow you to verify the publisher.
Martin Harris
That quote is about verifying the hash - the hash from the assembly is compared to the has computed for that assembly at the load time. It says nothing about storing previously computed hashes anywhere else. So I don't get how this would protect against Company B replacing the assembly on the disk. The newly placed assembly will be signed with another key, but still have valid hash for that other key.
sharptooth
Does this work simply because the reference to the library includes the "PublicKeyToken" into the calling code?
sharptooth
Yes. Though to add a slight further complication the public key token is itself a hash of the public key (which is much longer and stored in the assembly) and therefore Company B wouldn't need to have the exact same key pair for their assembly, only one that the public half hashed to the same value. The security implications are the same though.
Martin Harris
Since the "PublicKeyToken" is the key thing I suggest that you edit the answer so that it focuses on this and omits all the other mostly unnecessary stuff. With the "PublicKeyToken" the whole idea becomes simple and clear.
sharptooth
I think that the details in this answer are useful and I don't want to remove them, but I can see how they cloud getting to the root concept that clarified the answer to your specific question. I'll post a second, simpler answer that gets to the nub of the question quicker.
Martin Harris
+2  A: 
  1. You can go to some 3d party certificate provider (e.g. VeriSign) and purchase a certificate from them (Code Signing At Verisign).
  2. You use given certificate that has your company name, URL, etc. on it to sign your code.
  3. I download you app, and look at the list of certificates your app was signed with.
  4. I use your certificate, go back to VeriSign and verify that the certificate has been indeed issued to MyCompany, LLC.
  5. I look at the certificates that has been used to issue your certificate, and verify that VeriSign is one of them (Windows comes with few trusted certificates installed).

Summary:
You not only verify that code has not been tinkered with, but also was signed with a certificate that was issued by a party you trust.

I understand all this and this is what we actually do. But this has nothing to do with strong names and .snk files, doesn't it?
sharptooth
It was a while since I did that. But IIRK you use the public key of your certificate to sign your code. I.e. .snk is produced/exported from the .cer file you get from VeriSign using makecert.exe. Such that signing code actually does two things 1) prevents code from being tinkered with (protection) 2) authenticates the publisher.
+2  A: 

No, you don't need to publish your public key outside of the assembly since it is hashed and stored as a token alongside the reference inside the client's application:

AssemblyName, Version=1.0.0.0, Culture=neutral, PublicKeyToken=bcd6707151635d07"

This gives a method to ensure that all future versions of the assembly are compiled against the same key pair and therefore from the same publisher.

More details about how having this information stops another source from pretending to be you can be found in my other answer.

Martin Harris