Protecting PowerShell secrets with certificate on Yubikey

Page content

Intro

Sometimes you need to store secrets and PowerShell has a great way of encrypting text for you with the *-CMSMessage cmdlets! Now what if I want other people to be able to encrypt information without being able to decryot it again or if I want to encrypt something myself and only be able to decrypt it by going through a certain procedure like checking out the decryption-key from a secure storage? In either way, storing the key for decryption on a Yubikey is a good choice!

I’ve written a few posts on using Yubikeys with PowerShell before, you can find them here:

Enough, let’s get to it!

Creating a certificate

To encrypt and decrypt secrets we’re going to use the cmdlets Protect-CMSMessage and Unprotect-CMSMessage. To protect (encrypt) the message we need the public key from a certificate and to unprotect (decrypt) the same message we need access to the associated private key.

In my example I’m just going to create a self-signed certificate, you could either issue a similar cert from your own PKI or buy one from any public provider.

To get started, I’m going to create an inf-file with some settings and use certreq to selfsign a certificate for myself. Of course we do that with PowerShell. You should ofcourse replace the Subject and FriendlyName with something that suits you.

@'
[Version]
Signature = "$Windows NT$"

[Strings]
szOID_ENHANCED_KEY_USAGE = "2.5.29.37"
szOID_DOCUMENT_ENCRYPTION = "1.3.6.1.4.1.311.80.1"

[NewRequest]
Subject = "[email protected]"
FriendlyName = "Simon CMS"
MachineKeySet = false
KeyLength = 2048
KeySpec = AT_KEYEXCHANGE
HashAlgorithm = Sha256
Exportable = true
RequestType = Cert
KeyUsage = "CERT_KEY_ENCIPHERMENT_KEY_USAGE | CERT_DATA_ENCIPHERMENT_KEY_USAGE"
ValidityPeriod = "Years"
ValidityPeriodUnits = "1"

[Extensions]
%szOID_ENHANCED_KEY_USAGE% = "{text}%szOID_DOCUMENT_ENCRYPTION%"
'@ | Out-File -FilePath MyCert.inf
certreq -new MyCert.inf MyCert.cer

This should import the cert with private key to your certificate store, create MyCert.cer containing the public key in the current directory and give you an output similar to:

Installed Certificate:
  Serial Number: 54ccbdaf31a10abe4cd700757a71130f
  Subject: [email protected]
  NotBefore: 10/2/2018 4:21 PM
  NotAfter: 10/2/2019 4:31 PM
  Thumbprint: 9f6a619dbd6a742fa6cf7af94ace3c42fb7366f8
  Friendly Name: Simon CMS
  Microsoft Strong Cryptographic Provider
  9b7cdfe5f228af229303e2b1ae6d0d5a_266ee505-0860-41b2-be89-617291bb724e

To make sure I keep track of it I copy the thumbprint and store it in a variable. I’ll also grab a reference to my newly created certificate in the store using Get-Item and export the certificate with it’s private key to a pfx-file. Running the Export-PfxCertificate will ask for a password, choose one you can remember.

$Thumbprint = '9f6a619dbd6a742fa6cf7af94ace3c42fb7366f8'
$OrgCert = Get-Item -Path "cert:\currentuser\my\$Thumbprint"
Export-PfxCertificate -Cert $OrgCert -FilePath MyCert.pfx -Password (Read-Host -AsSecureString -Prompt 'Password')

Put the cert on my Yubikey

Next up I’m going to use the yubico-piv-tool to store the cert on my Yubikey. You can download the latest release from Yubico. I’m using version 1.4.4.

To not have to type the full path to the tool I create an alias for it. On my machine the file is located in Program Files (x86), you might have to adjust the path to fit your machine. This will import the MyCert.pfx to slot 9d on your Yubikey (make sure you don’t overwrite something important!) and ask you for the password you set in the previous step.

Set-Alias yubico-piv-tool "C:\Program Files (x86)\Yubico\YubiKey PIV Manager\yubico-piv-tool.exe"
yubico-piv-tool -s 9d -i MyCert.pfx -K PKCS12 -a set-chuid -a import-key -a import-cert

Now when the certificate is securely stored on my Yubikey I can remove the Ubikey from my computer and remove the original cert from both my store and the PFX-file. After this the private key of my certificate is only stored on my Yubikey and without it I won’t be able to decrypt anything.

$OrgCert | Remove-Item
'MyCert.pfx' | Remove-Item

That’s it! Let’s try it out!

To encrypt a message I only need the public key. That was stored by certreq in MyCert.cer and I can encrypt something by giving the full path to the cer-file to the -To prameter of Protect-CMSMessage. When I try to Unprotect the same message I’m asked to connect my SmartCard (Yubikey).

AskingforSmartCard

Go encrypt something!