getting code signing certificate on own Yubikey and then using it to sign windows binaries in unattended way, on KVM VM

starting from June 2023 code signing certificates for MS Windows cannot be delivered as a file anymore. files are easy to steal. now certs must reside on security modules which don’t allow private key extraction, at least not for mere mortals.

we’re using such a cert to sign exe and msi files on a build server which is a VM running Server version of MS Windows. below – details how to get a new cert and make it work in such CI/CD environment.

i’ve done some googling and found few resouces:

looks like cloud-based signing is ‘the easy way’, but we wanted to avoid it for multiple reasons, including security and costs. few of the offers:

purchases

  • i’ve bought Organization Validation Code signing certificate from ssl.com. website is sketchy, i even got errors in the process but their support was responsive and i got vetted [ i have proven that i am acting on behalf of the company for which i’ve ordered the cert ] within few days:
    • i had to provide registration papers,
    • i also gave DUNS number to speed things up,
    • they made automated call to number visible in D&B records, i had to re-type the number provided over the phone to their website,
  • separately – i’ve bought Yubikey 5 Nano FIPS – to be sure that i’m generating the private key and it’s not known to anyone else.

finalizing the cert issuance

  • based on https://www.ssl.com/how-to/key-generation-and-attestation-with-yubikey/ i’ve generated private key on the newly bought Yubikey 5 Nano FIPS:
    • in the process i’ve set my own PIN, PUK and Management key for Yubikey via Yubikey Manager,
    • in Yubikey Manager i’ve generated code signing certificate in slot 9a, with ECC P384 private key, then – following instruction above – i’ve created attestation file [ proving that private key was created on Yubikey device ] and intermediate certificate,
  • I’ve provided both of above to ssl.com and waited for them to issue the certificate,
  • once i got the certificate – i’ve imported it together with intermediate and root cert of ssl.com to yubikey [ based on https://www.ssl.com/how-to/install-sslcom-root-and-intermediate-certificates-on-yubikey/ ],
  • i’ve installed yubikey’s smartcard minidriver and rebooted my pc,
  • after that the new certificate showed up in certmgr.msc > personal > certificates – good!

testing if the certificate works

setting up VM with Windows Server

qemu-img create -f qcow2 vda.qcow2 32G
virt-install --connect qemu:///system --arch=x86_64 -n w2 -r 6144 --vcpus=4 --cdrom ../win2022/SERVER_EVAL_x64FRE_en-us.iso --disk path=vda.qcow2 --disk path=../win2022/virtio-win.iso,device=cdrom --graphics vnc,listen=127.0.0.1,port=5907 --noautoconsole --os-type windows --os-variant=win2k16 --network=bridge:br0,model=virtio --accelerate --noapic

# windows iso taken from https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022
# virtio-win drivers taken from the latest build at https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/

i’ve continued the setup via VNC available on loopback of my linux box, on port 5907

once system was installed – i’ve:

  • installed virtio drivers from the ISO file – by running virtio-win-gt-x64.msifrom it
  • run windows updates and rebooted the Windows Server VM few times
  • enabled RDP access

passing USB device from physical server to Windows VM

i’m working with assumption that only one yubikey will be plugged into physical server – so it’s enough find out identifier of the USB token that i want to pass to Windows VM:

lsusb -v|less
# i'm searching for Yubico
Bus 001 Device 002: ID 1050:0404 Yubico.com Yubikey 4/5 CCID
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               2.00
  bDeviceClass            0
  bDeviceSubClass         0
  bDeviceProtocol         0
  bMaxPacketSize0        64
  idVendor           0x1050 Yubico.com
  idProduct          0x0404 Yubikey 4/5 CCID
  bcdDevice            5.43
  iManufacturer           1 Yubico
  iProduct                2 YubiKey CCID
  iSerial                 0

that’s the key part: idVendor=0x1050, idProduct=0x0404. i’ve put them to the /etc/libvirt/qemu/w2.xml – definition of my VM, in <devices> section

<hostdev mode='subsystem' type='usb' managed='yes'>
  <source>
    <vendor id='0x1050'/>
    <product id='0x0404'/>
  </source>
  <address type='usb' bus='0' port='2'/>
</hostdev>

then virsh define /etc/libvirt/qemu/w2.xml and virsh start w2.

from now on – use VNC and not RDP to connect to the Windows VM. Why? Because RDP, intentionally, makes it impossible to access smartcard/security devices that are attached to Windows machine to which you’re connecting.

in the device manager of Windows i can see the smartcard device, with a warning – that’s a good sign:

if you have more than one identical yubikey plugged into the physical server and want to pass just one of them – it’s possible to pass whatever is attached to a specific port.

making Yubikey actually work on the Windows VM

  • now the key part – based on this thread – in the Device Manager – double blick on the Microsoft Usbccid Smartcard Reader (UMDF2) go to Driver > Update Driver, Browser my computer for drivers Let me pick from a list of available drivers on my computer and select Micrososft Usbccid Smartcard Reader (WUDF)

Yubikey token should immediately show up in the Yubikey Manager:

signing a binary in the Windows VM

install signtool from https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ – it’s enough to select Windows SDK Signing Tools for Desktop Apps

run certmgr.msc , find your certificate, double click on it, go to Details and scroll down to the Thumbprint – it’s the hash we’ll use to identify cert that should be used in the signing process

add c:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64 to path to make signtool easily accessible from command line, providing – as the long hex – thumbprint of your cert:

signtool.exe sign /sha1 2ad5caaad3caf23baa33a2ad7a6eaaf18255a7c7 /fd sha256 /td sha256 /tr http://timestamp.digicert.com “Clear Settings Wizard.exe”

if all went fine – signature on the exe should be replaced with yours

again – above will not work if you RDP – yubikey manager will tell Insert your YubiKey, signtool will ask to select a smartcard device, and tell “No certificates were found that met all the given criteria”.

signing without need to manually enter the PIN each time

I’ve found few options for unattended / fully automated signing, without prompt for pin, with Yubikey:

  • scsigntool – https://www.mgtek.com/smartcard – free, but closed source and fairly obscure. seems to work fine, but i’d prefer to have more transparency
  • https://ebourg.github.io/jsign/ – which requires Java and seems to work fine; you’ll need to install Yubico-piv-tools and add C:\Program Files\Yubico\Yubico PIV Tool\bin to PATH.
  • osslsigncode might support it, i did not try. details: 1, 2.
java -jar jsign-5.0.jar -tsaurl http://timestamp.sectigo.com --storetype YUBIKEY --storepass MYPIN --replace yubico-piv-tool-2.3.1-win64.msi
ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console...
Adding Authenticode signature to yubico-piv-tool-2.3.1-win64.msi

i’m sure it’s possible to specify which cert to use, but i don’t know how to do it. anyway – this seems to work.

reminder: access to local smartcard is not possible via RDP, i was getting this error when trying to use jsign via RDP instead of VNC:

jsign: Failed to load the keystore
java.security.KeyStoreException: keystore type 'YUBIKEY' is not supported with security provider SunPKCS11-yubikey
        at net.jsign.KeyStoreType.getKeystore(KeyStoreType.java:474)
        at net.jsign.KeyStoreBuilder.build(KeyStoreBuilder.java:283)
        at net.jsign.SignerHelper.build(SignerHelper.java:256)
        at net.jsign.SignerHelper.sign(SignerHelper.java:388)
        at net.jsign.JsignCLI.execute(JsignCLI.java:132)
        at net.jsign.JsignCLI.main(JsignCLI.java:40)
Caused by: java.security.KeyStoreException: PKCS11 not found
        at java.base/java.security.KeyStore.getInstance(KeyStore.java:967)
        at net.jsign.KeyStoreType.getKeystore(KeyStoreType.java:469)
        ... 5 more
Caused by: java.security.NoSuchAlgorithmException: no such algorithm: PKCS11 for provider SunPKCS11-yubikey
        at java.base/sun.security.jca.GetInstance.getService(GetInstance.java:101)
        at java.base/sun.security.jca.GetInstance.getInstance(GetInstance.java:218)
        at java.base/java.security.Security.getImpl(Security.java:684)
        at java.base/java.security.KeyStore.getInstance(KeyStore.java:964)
        ... 6 more
Try `java -jar jsign.jar --help' for more information.

looks like there’s a way around it, but requires binary patching: 1, 2, 3.

Extended Validation code signing certificates

few weeks after buying OV Code Signing cert from ssl.com and testing if all works fine i’ve bought proper EV certs to be used in the next 3 years – one from ssl.com another from sectigo via ssl2buy.com. vetting process was very similar for both OV and EV certs – it was based on call to number found in company registration papers. installation and use of EV certs was identical to OV. in case of sectigo besides getting the requested cert, i’ve received two intermediary certs and root cert. i’ve installed them via:

cd C:\Program Files\Yubico\YubiKey Manager
ykman.exe piv certificates import 82 C:\sectigo\out\root.crt -m managementkey
ykman.exe piv certificates import 83 C:\sectigo\out\int1.crt -m managementkey
ykman.exe piv certificates import 84 C:\sectigo\out\int2.crt -m managementkey

Leave a Reply

Your email address will not be published. Required fields are marked *

(Spamcheck Enabled)