A kingdom for a strong name
Thursday, 16. Jul 2020
We all have become increasingly paranoid when dealing with computers and software in any way and most of us run some kind of firewall and antivirus software to feel safer on the internet. I won’t go into details on this very wide subject, but I want to elaborate on what we as the developers of software can do about it. I’ll stick to my little .NET world to demonstrate how we can achieve higher security with applications. As already hinted at by the title, today’s topic is strong name signing.
Quick refresher on strong-naming
A good resource is the Microsoft page on strong-named assemblies, and you should definitely read that. But I’m going to outline the matter any ways so read on if you wish.
Strong-naming an assembly basically means signing the compiled binary using a cryptographic key pair where a digest algorithm creates a checksum of the file contents. This checksum is then encrypted with the private key and added to the file together with the public key. A small part of the public key is used as the ‘Public Key Token’ which is added to the assembly name to make it globally unique. Validation works the other way around where the public key is used to decrypt the checksum which in turn is compared to a recalculated checksum of the file contents. If public key and token and both checksums match, the file has not been tampered with. Otherwise, the file was manipulated and the running application should abort.
Why a strong name?
There are quite a few good reasons for why you should strong-name your assemblies, and you should read that Microsoft page if you want to know the whole story. For now, I’m going to focus on just two of them.
First, most companies have a policy of strong-naming the binaries of all their products before deployment or release. While the individual reasons for doing so may differ, the impact for you is the same in all cases. A strong-named binary can only ever load strong-named binaries and will fail to load unsigned ones. So if you want your libraries to be consumable by as many as possible, you are going to have to strong-name them or otherwise lose potential customers.
The second aspect is increased security, as already hinted at in the first paragraph. If done right, a consumer of your binaries will be protected against tampering of your files. The reason for this is the already mentioned ‘Public Key Token’ that is added to every reference when dealing with such assemblies. If an evil-doer manipulates a strong-named assembly after deployment, the validation will fail on attempting to load it.
Security of strong-naming
Strong-naming by itself doesn’t add any security unfortunately. There is a number of ways to get around the security checks at runtime and a strong-named library’s signature isn’t even checked by default. It’s on the consumer of your library to make sure that the executing environment is doing the checks and that it can’t be tampered with either.
But there is a first line of defence, and we as developers are responsible for laying the grounds that will eventually enable any consumer to do the right thing. We have to ensure a strong cryptographic chain when strong-naming an assembly because a weak link will eventually ruin it all and create a false sense of security.
The good news is that Microsoft has a decent tutorial on how to sign an assembly with a strong name, and you can create a strong name key file directly in Visual Studio using the wizard. It will even let you chose whether you would like the key pair to be encrypted which is a good thing.
The bad news is that the result won’t be quite as good as it can be or as good as our paranoia may want it to be. The generated private keys have a length of just 1024 bits and the digest algorithm used is sha1 which has been deemed insecure for over 15 years. That is a very long time in computer security.
Choose a strong private key and keep it secret
It’s obviously a good idea to keep the private key a secret so an encrypted strong name key file does have a nice ring to it. The one created by Visual Studio is really just an archive file that contains an X.509 private key, and its corresponding X.509 public key certificate. The first tool that comes to mind when working with certificates is OpenSSL, which comes in many flavours. I’m using OpenSSL Win32 on my Windows rig, but you could just as well use a Linux system if you have one available since all variants of OpenSSL should have the same feature set.
Using OpenSSL to create an encrypted strong name key file isn’t that hard, but there are a few quirks that need to be watched out for. The following commands will create a strong private key with a length of 4096 bits and a public key certificate using sha512. The key pair can be used for strong-naming assemblies in Visual Studio on the project’s signing tab, and it is encrypted with a password of your choice.
# create a new rsa private key with a length of 4 KiB and a matching public key using sha512 as digest.
openssl.exe req -x509 -newkey rsa:4096 -sha512 -nodes -config path\to\openssl.cfg -days 7300 -subj "/CN=MyLib" -keyout key_rsa_4096.pem -out cert_rsa_4096_sha512.pem
# combine the key pair into an encrypted key archive (this might work with -keysig as well making steps 3 and 4 obsolete but i really don't care at this point).
openssl.exe pkcs12 -export -in cert_rsa_4096_sha512.pem -inkey key_rsa_4096.pem -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -out tmp.pfx
# parse the temporary archive file back into pem format
openssl.exe pkcs12 -in tmp.pfx -out keybag.pem
# export the final archive that can be used for signing.
openssl.exe pkcs12 -export -keysig -in keybag.pem -out strongnamekey.pfx
Unfortunately the file’s checksum is still calculated using sha1 and I do not know of a way of changing that for PKCS 12 archives. Microsoft has come up with a different solution which they call enhanced strong naming. It trades encrypted keys for stronger digest options and a more complex setup of the signing process. The following commands will create a strong name key file with a strong private key and digest algorithm for the file’s checksum.
# create a new key pair with a length of 4 KiB.
sn -k KeyPair.snk 4096
# extract the public key and specifiy sha512 as digest algorithm for the checksum.
sn -p KeyPair.snk PublicKey.snk sha512
A first concern should be the safety of the private key since anyone with access to the generated key pair will be able to manipulate your libraries. I’ve done some research on the matter and have found 7-Zip to be a formidable encryption tool for delicate files. An encrypted archive containing the key pair can safely be checked into source control as long as the used password is a strong one. I would recommend using KeePass to both generate and safely store your password.
With security issues out of the way, the new public key can be used to delay sign via the project’s signing tab. Once the project has been compiled, the assembly needs to be resigned using the private key and again Microsoft’s strong naming tool comes to the rescue.
# resign the delay-signed library with the private key
sn -Ra MyLib.dll KeyPair.snk
The entire setup is a lot more complex than anyone could want, and I would love to have the option to specify key length and digest algorithm right there in Visual Studio’s wizard. It wouldn’t hurt if we didn’t have to worry about the safety of our private keys either and if the strong name key files were properly encrypted with a password of our choice as well. Until that day, all developers are going to have to use these workarounds. Another great way of adding security is the use of Authenticode where the publisher’s identity can be validated. However, it’s not a replacement to strong-naming and should not be used like it.