HOWTO: Strong Name Sign a .Net Assembly With a Yubikey/Smart Card and Code Signing Key with AssemblyKeyNameAttribute

Saturday, September 16, 2017

Or, as I’ll refer to it from here on out, Part 2

UPDATE: I ran into a problem that caused all of this to stop working, so I’ve updated the post with what I had to do to resolve that. See Troubleshooting below.

If you haven’t read Part 1, you’ll want to do that now. There’s a few things there that I’ll be referring to here. I’d also recommend going through the steps up to the point of changing your project in Visual Studio. You can skip that part and continue on here.

Introducing AssemblyKeyNameAttribute

Something I only hinted about in the last post on this subject was the AssemblyKeyNameAttribute (go ahead, click that link, see how sad that documentation made me).

It’s the obvious way to handle signing and being a part of your code, makes it easy to solve many of the “Bad News” parts at the bottom of the last post. You could simply create a new build profile and #if/#endif out the entry in AssemblyInfo.cs0.

Unfortunately, I couldn’t make it work. And then, out of nowhere, it did. Shortly after writing and publishing the last post, I decided to add that attribute back in. I ran compile, received my three PIN prompts and … it built. This was odd, since all past attempts yielded a “Keyset not found” error. I figured that my checking the signing box with delay-sign enabled probably yielded the success, so I undid everything. After a lot of google searching, which yielded a whole mess of Stack Overflow and MSDN Forums questions from a myriad of users who hadn’t figured it out, I ended up with the last post.

The long and the short of it is, most of it is unnecessary. The two necessary parts are what follows.

The trick to getting AssemblyKeyNameAttribute working

The most important part is the sn.exe -c "Microsoft Base Smart Card Crypto Provider". This must be run or you’ll get a Keyset not found error on build.

However, there’s an ugly catch-22. Running sn.exe -c, if it ends up changing the CSP, can only be done as an elevated user. However, that same elevated user cannot access the key in the personal store of your non-elevated account. So simply adding this to the pre-build or post-build and running Visual Studio under elevation results in the exact same Keyset not found error on build. Good error messages can mean the difference between solving the problem and … well, that last post should give you an idea.

Unfortunately, it doesn’t look like the sn -c invocation persists after reboot, so we’re minimally stuck with having to run this command manually once after reboot, or find a way to elevate during build just to run that command. I took the later option.

To get this working, you’ll need the script located at this gist . The only requirement is that you have the .Net Framework SDK 4.6, 4.6.1 or 4.6.2 installed. It uses it to get the path to the sn.exe file so that it’ll work regardless of how your system is configured.

First, make sure your execution policy for CurrentUser is set properly:

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

You can replace RemoteSigned to Unrestricted if you’d like to be a little less secure (the script in the gist is signed, but if you modify it and don’t re-sign it, it’ll fail on build).

Now go to your project in Visual Studio and head over to the Build Events tab. If you followed the last post and made changes, you should remove everything from the Post Build except for the SignTool.exe calls. This won’t authenticode sign your resulting .dll or .exe – it’s only strong name signing.

Add a pre-build event as follows:

PowerShell -NoProfile -Command "\path\to\Set-SmartCardCspOnBuild.ps1"

Now open up the Properties\AssemblyInfo.cs file and add the following line:

[assembly: AssemblyKeyName("Your Key Container Name")]

If you aren’t sure what your key container name is, consult the linked post, above, about how to find it.

At this point, simply run a build. You’ll notice once build starts, you’ll get an elevation prompt. That’s happening in the PowerShell script. I haven’t figured out a way to get sn.exe (or any other tool) to display he actual CSP that’s in use. Ideally, it’d be nice if it didn’t have to switch it every build to avoid the elevation prompt, but this works until I can find a better way.

Troubleshooting

Just as soon as I wrote this on Saturday, the build process stopped working on Sunday. The only thing I had done was switch sn.exe back to the default crypto provider to generate a key so that I could strong name a library that I installed via NuGet which wasn’t already strongly named. I switched it back when I was done, but the build would not work.

Apparently, the sn.exe you use matters!

I had tried to generate the key using sn.exe -k and received an error (the specific error escapes me, now, but I recall that it sounded like the “keyset does not exist” error, which is absurd since I was trying to generate a keyset!). I had noticed that ilasm was being picked up from one version of the .NET Framework and sn.exe was being picked up from another, older version, so I modified my path to use the sn.exe of 4.6.2. No dice. On a hunch, I realized that the Smart Card Crypto Provider might not be able to generate a key This makes sense since the -k command is used to generate a key and put the key set in a file. Smart cards can generate a key but can only export the public key part of the key set. I ran sn.exe -c to reset the CSP back to the default (which I assume is the Base Crypto Provider v1). I ran sn.exe -k and it generated the new key.snk for that library without issue.

Then, I re-ran the build. I received no PIN prompt and an error that the keyset was not found (with the name of my container). Oops, I forgot to switch the CSP back! So I went back to my PowerShell prompt and pointed sn.exe back to the Smart Card Crypto Provider. Right when I did it, I realized this wouldn’t work – I was already using the script, above, and this should have happened during pre-build!

And, no surprise, it still didn’t work! After a few choice four-letter words, I launched a fresh PowerShell Administrator prompt (which reset my path) and reran the sn.exe -c "Microsoft Base Smart Card Crypto Provider" command. I didn’t expect this to work, but … it did.

There were two differences in the second run. My PowerShell Profile injects the Visual Studio 2017 developer command prompt environment which, on my “developer laptop”2, resulted in the sn.exe from the .NET Framework 4.6.1 SDK (specifically the x64 version) being picked up.

Here’s the thing that makes no sense. I used the x64 version of sn.exe from .NET Framework 4.6.2 when I reset the CSP and it succeeded in breaking the build, but setting it back with that same version didn’t fix it. My PowerShell script, above, uses the x86 version from 4.6.2 and that didn’t fix it3. When I ran the x64 version from 4.6.1 to set the CSP to the Smart Card Provider, it fixed it.

I’m not convinced it has anything to do with the 32-bit vs the 64-bit version of the sc.exe tool, but I think the .NET Framework version mattered. So, at least until I have time to reproduce the issue, it appears that resetting the CSP using sc.exe -c with either the 4.6.2 or 4.6.1 version will break things, but only the 4.6.1 version (possibly only the x64 version of that) can be used to fix it.

This is made even more fun by the fact that I can’t find a way to have any of the tools indicate what provider they’re using to perform these operations, which would make it really easy to see what is going on (and allow for a more intelligent build script).

In Closing

Good grief this is a hassle. Every signing tool that I’ve used from Microsoft does things slightly differently. All of the .Net Framework tools inherit sn.exe’s CSP (csc.exe and ilasm.exe, though I’ve been unable to get the latter to work with my code signing key4).

From here, I’m going to find a way to discover what CSP sn.exe is configured for so that I can improve the build script and avoid setting that value if it’s already set correctly. I also recall from past experience with Smart Cards that the OS is supposed to support caching of PIN entry with configurable time-outs. There’s some forum posts ut there that indicate this may be broken in Windows 10 after a patch that was released in June, and since I’m running insider previews, I don’t really have a way of backing that patch out, but I’m not so sure I even have things configured to allow caching, so I’d like to get that solved as well. When I do, I’ll post an update.

0 Of course, this was always possible with the modifications we did to the .csproj file, but not many of us enjoy messing around with MSBuilds wonky XML-based language. Even Microsoft had a (short) moment of clarity there. Unfortunately, it was too big of a mess to untangle.

1 I only use generated keys for strong naming libraries that aren’t mine. This is partly because it feels wrong to sign a library with my code signing key that isn’t my library, even though the purpose of strong naming isn’t to validate ownership/authorship.

2 Read that as “it’s pretty banged up and things aren’t always what they seem” on this device, i.e. ilasm.exe being picked up in a location in my path that isn’t the same as sn.exe, so there could be other things going on here.

3 The reason I specifically picked the 32-bit version is because Visual Studio is a 32-bit application and I assumed that was the right version to use. I’m still pretty sure that’s the right version, but evidence would indicate otherwise.

4 Don’t even get me started on vsixsigntool.exe, which is the Visual Studio Extension equivalent to signtool.exe. Yeah, it works differently. So far, only signtool.exe works somewhat intelligently. The others require a lot of trial and error if you want to do things in the most secure manner (not storing the miserable keyset in a file in your filesystem)

No comments :