Microsoft recommend that all assemblies be furnished with a strong name. The Code Analyser within Visual Studio will generate error CA2210, "Assemblies should have valid strong names", if the assembly does not have a strong name. So, like a good citizen, you strongly name all your assemblies and all is well.
One of the constraints placed upon assemblies which have a strong name is that all assemblies which they reference must also have a strong name. So what happens if you need to use a third-party assembly which doesn't have a strong name? You could contact the supplier and ask them to provide a version with a strong name. But what if they can't? Or won't? You'll need to add a strong name yourself.
When compiling an assembly, you'd normally add a strong name by referencing a file containing a key pair via the AssemblyKeyFile attribute, or via the Signing tab of Project Properties. But this is a third-party assembly – you don't have the source code. So how do you add the strong-name retrospectively?
Adding a Strong Name via a Key Pair
In all the examples which follow, my third-party assembly is in ASQLService.dll.
To start, we're going to need to backup the original (unsigned) third-party assembly:
> COPY ASQLService.dll ASQLService.dll.backup 1 file(s) copied.
We'll also need a key pair with which to sign the assembly, so let's create one via the Strong Name tool:
> SN -k MyKeyPair.snk Microsoft (R) .NET Framework Strong Name Utility Version 3.5.30729.1 Copyright (c) Microsoft Corporation. All rights reserved. Key pair written to MyKeyPair.snk
Let's check out the tools which we know can add a strong name to see what would be most appropriate:
- The C# Compiler (CSC.exe) accepts a /keyfile:<file> argument, but that's going to require some C# source code and all we have is an assembly.
- The Assembly Linker (AL.exe) accepts a /keyfile:<filename> argument, but that's going to require us to pass it a module (i.e. a raw MSIL file without a manifest).
- The Strong Name Tool (SN.exe) has a -R option with parameters <assembly> <infile> which "re-signs a signed or partially signed assembly with the key pair in <infile>".
That last option sounds promising – we can pass SN an assembly (which we have) and a file containing the key pair (which we have). Let's try it out:
> SN -R ASQLService.dll MyKeyPair.snk Microsoft (R) .NET Framework Strong Name Utility Version 3.5.30729.1 Copyright (c) Microsoft Corporation. All rights reserved. ASQLService.dll does not represent a strongly named assembly
Perhaps we should have read the description of the -R option more carefully. SN requires that the assembly already have a strong name, or at least have been partially signed already. This is because when we sign an assembly we embed into it a hash of the file, encrypted with a private key, and the public key with which to decrypt the hash. If space has not been reserved within the file to accommodate this data then we're out of luck.
Now if we knew where these additional bytes were stored within an assembly, perhaps we could write a little utility to allocate the space and make SN happy. But we don't (well, I don't). And anyway, surely there must be a technique which uses the pre-build tools supplied by Microsoft? There is.
Our problem is that we have an assembly and what we really need is the file which would have existed before the assembly was created: the C# source code or at least the MSIL. Wouldn't it be nice if Microsoft supplied a tool which could disassemble an assembly into MSIL? Enter the MSIL Disassembler (ILDASM.exe).
> ILDASM ASQLService.dll /out:ASQLService.il
The result of running the above statement is that an ASQLService.il and an ASQLService.res file are written to disk. The .il file contains the disassembled intermediate language and the .res file contains any resources which were embedded within the assembly. One caveat: as the documentation for ILDASM says, "you cannot use this technique with PE files that contain embedded native code (for example, PE files produced by Visual C++)".
So now we can use the MSIL Assembler (ILASM.exe) to assemble our ASQLService.il into a new assembly, adding a strong name in the process:
> ILASM ASQLService.il /dll /resource=ASQLService.res /key=MyKeyPair.snk Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.3053 Copyright (c) Microsoft Corporation. All rights reserved. Assembling 'ASQLService.il' to DLL --> 'ASQLService.dll' (...irrelevant output removed from here...) Writing PE file Signing file with strong name Operation completed successfully
Now we have a new version of ASQLService.dll, but this one contains a strong name. Wasn't that easy?
Adding a Strong Name via a Certificate in a Certificate Store
The above works fine if you want to use a key-pair file to provide the assembly with a strong name, but what if you want to use a certificate? Does that behave the same way? Nearly. We're still going to have to back-up the original assembly and use ILDASM to disassemble the assembly into IL:
> COPY ASQLService.dll ASQLService.dll.backup 1 file(s) copied. > ILDASM ASQLService.dll /out:ASQLService.il
Let's create a test certificate so we can see how things vary slightly:
> MAKECERT MyCertificate.cer Succeeded
So how do we pass this certificate to ILASM? If we read the documentation for ILDASM, or just issue a ILASM /? command, we see two relevant options:
- /KEY=<keyfile> Compile with strong signature (<keyfile> contains private key)
- /KEY=@<keysource> Compile with strong signature (<keysource> is the private key source name)
So which one of those accepts a file containing a certificate? Well, we certainly have a file so let's try specifying a keyfile:
> ILASM ASQLService.il /dll /resource=ASQLService.res /key=MyCertificate.cer Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.3053 Copyright (c) Microsoft Corporation. All rights reserved. Assembling 'ASQLService.il' to DLL --> 'ASQLService.dll' Source file is ANSI ASQLService.il(65) : error -- Failed to extract public key: 0x80090007 (...irrelevant output removed from here...) ***** FAILURE *****
Okay – so not that option then! So we must be supposed to specify a keysource. But what's a keysource? The documentation refers to it as "the private key source name". I'd never heard that term, so tried searching on-line for "ilasm keysource". I used Google and found just 53 hits, almost all of which were just re-hashes of the official on-line documentation. I tried lots of different searches, plus trial and error, but couldn't work out what "private key source name" actually meant. Eventually I disassembled ILASM and noticed a call to StrongNameGetPublicKey within it. StrongNameGetPublicKey is a Win32 API which accepts a szKeyContainer parameter, which is defined as "the name of the key container that contains the public/private key pair". Now that makes more sense – I certainly know what a key container is.
So, we need to create a certificate in a certificate store and provide a name for the key container. So let's create one in a certificate store named MyCertificateStore, assigning a key container name of MyKeyContainer:
> MAKECERT -ss MyCertificateStore -sk MyKeyContainer Succeeded
Now we can pass the key container name (sorry, the "private key source name") to ILASM:
> ILASM ASQLService.il /dll /resource=ASQLService.res /key=@MyKeyContainer Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.3053 Copyright (c) Microsoft Corporation. All rights reserved. Assembling 'ASQLService.il' to DLL --> 'ASQLService.dll' (...irrelevant output removed from here...) Writing PE file Signing file with strong name Operation completed successfully
Perfect – we've now added a certificate-based strong name to our third-party assembly.
Adding a Strong Name via a Certificate in a Personal Information Exchange (.pfx) File
What if your certificate isn't stored in a certificate store, but is stored within a Personal Information Exchange (.pfx) file? Neither of the above approaches work for a .pfx file as passing a keyfile to ILASM expects you to pass a key pair, whilst passing a keysource expects the certificate to be in a certificate store. So let's run through this scenario, which is actually the one I faced when I originally encountered the need to sign a third-party assembly.
To test this out we'll need to create separate .pvk (private key) and .cer (public key) files:
> MAKECERT -r -pe -sv MyCertificate.pvk MyCertificate.cer
When you're prompted for the password, enter anything you like – just make sure you enter the same value each time, as every question is asking you for the password for the subject key. I'm going to assume you've used the password qwerty.
Then we can combine these into a .pfx using PFV2PFX:
> PVK2PFX -pvk MyCertificate.pvk -pi qwerty -spc MyCertificate.cer -pfx MyCertificate.pfx
If you've already got a .pfx file (created via Visual Studio, perhaps), you can obviously skip the above steps.
To make use of the .pfx file, we need to extract it's public key. I know we already have the public key in MyCertificate.cer but that's apparently not good enough for ILASM – it wants it in an .snk file. So we must oblige it:
> SN -p MyCertificate.pfx MyCertificate-publickey.snk Microsoft (R) .NET Framework Strong Name Utility Version 3.5.30729.1 Copyright (c) Microsoft Corporation. All rights reserved. Enter the password for the PKCS#12 key file: Public key written to MyCertificate-publickey.snk
Now that we have the public key in an .snk file, we can pass it to ILASM. Note that because we're only passing the public key, the third-party assembly will only be partially signed (delay-signed).
> ILASM ASQLService.il /dll /resource=ASQLService.res /key=MyCertificate-publickey.snk Microsoft (R) .NET Framework IL Assembler. Version 2.0.50727.3053 Copyright (c) Microsoft Corporation. All rights reserved. Assembling 'ASQLService.il' to DLL --> 'ASQLService.dll' (...irrelevant output removed from here...) Writing PE file Operation completed successfully
Notice how the output does not include the message 'Signing file with strong name', which we saw earlier. That's because it's only partially signed at this point. To complete the process, we need to use SN again.
> SN -R ASQLService.dll MyCertificate.pfx Microsoft (R) .NET Framework Strong Name Utility Version 3.5.30729.1 Copyright (c) Microsoft Corporation. All rights reserved. Enter the password for the PKCS#12 key file: Assembly 'ASQLService.dll' successfully re-signed
Not the easiest process in the world, but at least the third-party assembly is now strong-named.
A Final Word of Caution
You'll need to check your license agreement to see whether the vendor of the third-party component allows you to reverse-engineer their assembly, albeit only briefly, in order to add a strong name. You could also get into issues with any support arrangements as you've actually altered the assembly they supplied which may conceivably not operate exactly as the original did. Having said that, I've used this technique where the supplier was another development team within the same organisation (who were primarily Java focussed and didn't really 'get' the concept of strong names) and never had any problems.