Monday 7 December 2009

Adding a Strong Name to a Third-Party Assembly

Introduction

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.

13 comments:

  1. Hello Ian,
    I'm using MemcachedProviders.dll for my MVC project and my problem is "Referenced assembly 'MemcachedProviders' does not have a strong name DevExpressTest". So as you can see, this is strong name problem. I'm following the "Adding a Strong Name via a Key Pair" part. Then I erase the old referance dll and add the newly created dll. But after building the project I have this error: "Parser Error Message: Exception has been thrown by the target of an invocation.".

    Do you have any idea about the solution?

    Thanks in advance!

    "Source Error:


    Line 71:
    Line 72:
    Line 73:
    Line 74:
    Line 75: "

    ReplyDelete
    Replies
    1. Source Error:


      sessionState timeout="1" cookieless="false" regenerateExpiredSessionId="true" mode="Custom" customProvider="MemcachedSessionProvider"
      Line 72: providers
      Line 73: add name="MemcachedSessionProvider" type="MemcachedProviders.Session.SessionStateProvider,MemcachedProviders" dbType="None" writeExceptionsToEventLog="false"
      Line 74: providers
      Line 75: sessionState

      Delete
    2. Nurhak -

      Unfortunately, there could be a lot of reasons for the "Exception has been thrown by the target of an invocation" message - that's always a tricky one to track down.

      My advice would be to initially confirm that it's the result of the strong name having been added to MemcachedProviders. So that means you'll need to use the original (non-strong named) version, and temporarily remove the strong name from the calling assembly (which looks like it's DevExpressTest). I'd make sure you can get that working first.

      If that works, and you still can't use the version you've added the strong-name to, you might be out-of-luck with the strong-naming trick. But all is not lost... the source code for MemcachedProviders is available at http://memcachedproviders.codeplex.com, so you can always download that and build it yourself - adding a strong name in the process.

      One last thought, before trying the above you could try fulling qualifying the type name in your config file. I note that you've specified "MemcachedProviders.Session.SessionStateProvider,MemcachedProvider" which should be fine, but you might try appending ", Culture=aaa, PublicKeyToken=bbb, Version=ccc" to it to explicitly specify the culture, public key token and version your expecting. It probably won't help, but it's worth a try.

      Good luck.

      Cheers,
      Ian.

      Delete
  2. Thank you. After searching for an answer to sign a 3rd-party-DLL with an existing pfx-File, I found this here! This saved my day an was very helpful.

    In your last step, it Looks like the following line is missing?! (For generating *.res- and *.il-file)

    ILDASM ASQLService.dll /out:ASQLService.il


    Best Regards,

    Steffen M.

    ReplyDelete
  3. Awesome. Thanks

    ReplyDelete
  4. The only solution that worked for me!
    Thank you Ian!

    ReplyDelete
  5. Thanks ! This is good knowhow

    ReplyDelete
  6. Thanks you so much
    I got solution here… I solved it.. thanks again.
    You saved my days. :)

    ReplyDelete
  7. This is perfect. Thank you

    ReplyDelete
  8. great post :)

    really enjoyed and pocket it.

    Thanks,

    ReplyDelete
  9. Saved my bacon! Thanks very much for sharing this.

    ReplyDelete