Anthony J. Martinez

SSH at Scale with OpenSSH Certificates - SmartCard Backed Keys

Many weeks ago, when I prematurely declared a final note on the SSH at Scale topic, I was left wondering if I could go further and loop my GPG Smartcard into the mix. This post details what I found hiding just below the surface of OpenSSH and OpenSC to let one maintain all necessary secrets on a SmartCard and still use OpenSSH Certificates for login.

First find your pkcs11 module (on Linux)

$ PKCS11_MODULE=$(pkcs11-tool | sed -n 's|.*(default:\(.*\))|\1|p;')
$ echo "${PKCS11_MODULE}"
/usr/lib/x86_64-linux-gnu/opensc-pkcs11.so

Using the PKCS15 interface, export the public parts of associated keys for signing and authentication. In the case of my Librem Key, the relevant IDs are 01 and 03:

Signature Pubkey:

$ pkcs15-tool --read-ssh-key 01 -o sig.pub
Using reader with a card: Purism, SPC Librem Key (000000000000000000009BB4) 00 00

$ cat sig.pub
ssh-rsa AAAAB3N...DVUhQ== Signature key

Authentication Pubkey:

$ pkcs15-tool --read-ssh-key 03 -o auth.pub
Using reader with a card: Purism, SPC Librem Key (000000000000000000009BB4) 00 00

$ cat auth.pub 
ssh-rsa AAAAB3N...fuzQ== Authentication key

Now that both public keys are on disk, one can utilize the corresponding SmartCard-backed private keys in a normal OpenSSH Certificate flow. The first, and least intuitive, difference is in the call to ssh-keygen. When the private key is on disk one passes the path after -s and this key is used as the Certificate Authority. In the new case, the path to the public key is given after -s and the path to the pkcs11 module is given after -D. An example follows, signing the exported auth.pub to create a certificate:

$ ssh-keygen -D ${PKCS11_MODULE} -s sig.pub -n ${USER} -I EXAMPLE_CERT -z $(date +%s) -V +15m auth.pub
Enter PIN for 'OpenPGP card (User PIN (sig))': 
Signed user key auth-cert.pub: id "EXAMPLE_CERT" serial 1662515192 for amartinez valid from 2022-09-06T20:45:00 to 2022-09-06T21:01:32

$ ssh-keygen -Lf auth-cert.pub 
auth-cert.pub:
        Type: ssh-rsa-cert-v01@openssh.com user certificate
        Public key: RSA-CERT SHA256:V2KMVlJjPOn86z6a2srEcnMQj78OujEXJ597PJ6+wyY
        Signing CA: RSA SHA256:HoXa4G9gmsln+8gOUPEeKNmcCA0cppiUlmUuEjt8joA (using rsa-sha2-512)
        Key ID: "EXAMPLE_CERT"
        Serial: 1662515192
        Valid: from 2022-09-06T20:45:00 to 2022-09-06T21:01:32
        Principals: 
                amartinez
        Critical Options: (none)
        Extensions: 
                permit-X11-forwarding
                permit-agent-forwarding
                permit-port-forwarding
                permit-pty.
                permit-user-rc

Using the generated cert with a private key on the SmartCard requires that the target host is configured to trust the key used as a CA above. An earlier post details how such a configuration could be accomplished. Assuming your target host is properly configured all that is left is telling ssh where to find the pkcs11 module, -I, and which certificate to utilize -o CertificateFile=.... Another example follows doing just that:

$ ssh -I ${PKCS11_MODULE} -o CertificateFile=auth-cert.pub beaglebone
Enter PIN for 'OpenPGP card (User PIN)': 

Last login: Wed Sep  7 00:25:47 2022 from ...
amartinez@beaglebone:~$ sudo grep -i cert /var/log/auth.log | tail -3 | grep -v grep
Sep  7 01:52:21 beaglebone sshd[1344]: Accepted publickey for amartinez from ... port 40728 ssh2: RSA-CERT SHA256:V2KMVlJjPOn86z6a2srEcnMQj78OujEXJ597PJ6+wyY ID EXAMPLE_CERT (serial 1662515192) CA RSA SHA256:HoXa4G9gmsln+8gOUPEeKNmcCA0cppiUlmUuEjt8joA

The scenario used in these examples was exceedingly simple, and in production one should use an entirely different smartcard (or other pkcs11 device like a TPM) to act as the CA. If, like me, you tend to use your SmartCard with gpg rather than pcscd it is worth noting that use of the certificate works just fine without the -I option to ssh provided the associated key is available through gpg-agent serving as your ssh-agent. Signing, as far as I can tell, does require first stopping the local user's gpg-agent and then starting pcscd globally to use pkcs11. As a final note, the above works exactly the same way in Windows with the exception of the format of the paths given.