API authentication with the private key on a smartcard (yubikey)

I’m trying to figure out how much work it would be for incus client to use a private key on a smartcard (specifically, a yubikey through its PIV applet) as opposed to the current on-disk client.key.

Is there anyone else interested in such a feature? I know of the existence of OIDC, but I’d rather keep things local/simple, and I am heavily relying on a yubikey already for SSH (yubikey-agent/sk-ssh-ed25519@openssh.com), age (though age-plugin-yubikey) and its OpenPGP applet, so might as well continue down that path.

My impression is that the server side is already sufficiently generic for this to work, because the trust list takes fingerprints. That is, you can do this:

$ ykman piv keys generate --algorithm ECCP256 83 pubkey.pem
$ ykman piv certificates generate --subject "CN=incus" 83 pubkey.pem
$ ykman piv certificates export 83 client.crt
$ incus config trust add client.crt

What is currently missing, is a codepath in the incus client to use a yubikey instead of client.key on-disk. I have to investigate whether the go tls library makes plumbing this in simple or hard. But at least on the PIV side, there is GitHub - go-piv/piv-go: Keys and certificates for YubiKeys, written in Go · GitHub to build on.

It looks like piv-go exposes a standard crypto.PrivateKey and this could be passed into the X509KeyPair that is constructed inside of Incus.

I’m not sure that we’d want to pull in a Yubikey-specific backend, we’d likely then be asked about supporting TPM backend keys, HSM backed keys, … and don’t really want to have with each of them individually.

PKCS11 may be the better option by using something like GitHub - miekg/pkcs11: pkcs11 wrapper for Go · GitHub.

The problem there is that it would require the client to use cgo, or rather, we’d need to make this entire logic dependent on the client being built with cgo and likely a specific pkcs11 build tag. Then distributions can decide whether to link against pkcs11 and provide that functionality.

OK, that sounds very understandable. The CGO stuff is unfortunate.

Going by the hint in miek’s repo, the combination of GitHub - eclipse-keypont/crypto11: Implement crypto.Signer and crypto.Decrypter for HSM-protected keys via PKCS#11 · GitHub and YKCS11 should get me where I’d want to be.

Native FIDO (passkey) support would be awesome indeed but I understand the trade-off.

As an alternative, as PKCS11/PIV/OpenPGP are being thrown around, could incus be redirected to ephemeral certificates signed by user certificate? The usecase I think of is generating a short-lived cert from PKCS11/PIV/OpenPGP using a first tool and pointing the incus binary to that for authentication.

That’s assuming there is logic to accept client-cert-signed certs in Incus.

But is there much difference between that, and issuing an OIDC token?

That’s assuming there is logic to accept client-cert-signed certs in Incus.

AFAIK, incus only looks at the client’s public key fingerprint. It doesn’t check that the cert is signed by any particular CA, or with any particular subject identity.

Yes, there is, unless you meant webauthn (which doesn’t actually require a web browser)… Incidentally, Webauthn (or old U2F) would do exactly what we want: it’s actually a perfect solution to the problem.

OIDC is complex and introduces quite a few dependencies (especially on a third-party OpenID Provider) and a few restrictions (your OID provider needs to be unique and accessible both from your client and the relying party [incus] at all times). You lose part of that network, your routing goes wrong, your hosting provider has a bad day, your OIDC service is a bit moody, your clock is not quite right, etc. and no access for you anymore.

That’s where having a break-glass account would be nice, but leaving certs lying around is not really that great security-wise, even if you do things properly. The easy and secure solution is for your private key to never leave a device no operating system can directly access, hence FIDO/PIV/TPM solutions.

Also OIDC is not exactly trivial to self-host. Even if you do do it, it’s dependent on DNS/NTP/Routing to be always right and it needs valid TLS certs you sure don’t want expiring. When you use SaaS, sure you trust them to keep their service running, until there is an outage (on their side or yours) and you are SoL.


Just adding that you are absolutely right about client certs: they won’t work for signing other certs so this was a bad idea.