I have a public codesigning certificate issued by DigiCert. I want to be able to sign my PowerShell scripts anywhere, for example when I’m at a customer, but it’s a hassle having to move the code to where I have my code signing certificate just to sign it. I’ve been experimenting with build jobs in VSTS so if I check in a script a build job will automatically sign it for me, but that requires access to internet and VSTS.
Yubikey to the rescue
A Yubikey is a small USB device that looks a bit like a really thin USB-stick. I’ve been using a Yubikey as a second factor for quite some time and it’s been working great, but I haven’t thought about the possibility to use it for code signing. When set up with certificates, it works like a smartcard, plug it in and it will hold your private keys for you. A while back I had my key reset, gave it new pin-codes and stored my signing certificate on it. Now I can plug it in at any computer and have my cert readily available without having to worry about storing my private key somewhere. For instructions on how to set up a Yubikey, check my post on how to Set up Yubikey for PowerShell Code Signing.
Signing code with my Yubikey
Once my cert is imported into my Yubikey, I can insert the key to any computer and a certificate will be available in the current user’s certificate store. Let’s enumerate the store and see how it looks:
Get-ChildItem -Path "cert:\currentuser\my" -CodeSigning
Using the parameter -CodeSigning only works when the path points to a certificate store and it will make Get-ChildItem only return certificates valid for code signing.
Next up, let’s sign something!
I have a module called DSACL that contains functions for setting ACLs in Active Directory. I want to sign this module before posting it to the gallery. There are two ways to sign a module, signing each individual file and using catalog signing. I’m going to use both, you can read more about it on the publishing best practice guide for PowerShell gallery.
First I need a reference to my certificate. If you only have one code signing certificate on your computer you can store the output from Get-ChildItem above to a variable. If you have more than one I like using Out-GridView to choose the one I want, like this:
$Cert = Get-ChildItem -Path 'cert:\currentuser\my' -CodeSigning | Out-GridView -PassThru
Now I’m going to sign my script files, this is the signature that PowerShell will validate when loading the module. Signing is done with the command Set-AuthenticodeSignature. To make sure that my signature is valid even after my certificate is expired I use a timestamp server, this will add a timestamp to my signature and makes it possible to validate that my certificate was valid at the time of signing. I only want to sign my module and manifest files so I use the following command and make sure I’m in the directory where my files are:
Get-ChildItem -Path '*.ps*' | Set-AuthenticodeSignature -Certificate $Cert -TimestampServer 'http://timestamp.digicert.com'
The next part is to create a catalog file which will contain the hash sum of each file, that way confirming that nothing has been changed. This is used by PowerShellGet when I install my module using Install-Module. I can choose between using catalog version 1 or version 2. Version 1 will use the SHA1 and version 2 will use the SHA256 algorithm. For compatibility with Windows 7 (which we probably will have to live with for a while longer), I’m going to use version 1. The command I use is:
New-FileCatalog -CatalogVersion 1 -CatalogFilePath .\DSACL.cat -Path $PWD.Path
Here I’m still in the directory where my files to be signed are. This allows me to use the default variable $PWD which contains the path of the current folder. This will create the file DSACL.cat which will contain the hashes for each file in the directory. If any file is changed, added or removed, the DSACL.cat file will no longer be valid. Now to make sure that my catalog file is not changed, I’m going to sign that to using the following command:
Set-AuthenticodeSignature -Certificate $Cert -TimestampServer 'http://timestamp.digicert.com' -FilePath .\DSACL.cat
Now PowerShell can validate the signature on each PowerShell file (the module file and the module manifest) and the PowerShell get can validate the signature on my catalog signing file as well as the validity of my catalog signing file and as long as it all checks out, I can be confident that no one changed my code.
And that’s it! Happy signing!