PGP and SSH keys on a YubiKey
Two separate things that both end up on the same piece of hardware: a PGP keypair whose private half lives on the card, and FIDO2-backed SSH keys. They solve different problems and are worth keeping straight.
Generating and moving a PGP key
gpg --expert --full-gen-key
Export everything before it goes anywhere near the card. keytocard is a
move, not a copy: it writes the key to the card and removes the private
half from your keyring. Without a backup at this point the key is gone the
moment the YubiKey is lost.
gpg --export-secret-key --armor $KEYID > privkey.armor
gpg --export-secret-key $KEYID | paperkey > privkey.paperkey
gpg --export $KEYID > pubkey.armor
paperkey strips out everything reconstructible from the public key and leaves
only the secret bits, which is what makes a printed copy a reasonable size.
Store the armored copy somewhere offline.
Then move it:
gpg --edit-key $KEYID
and keytocard. Select the key first by entering key 1, otherwise the
command operates on the primary key rather than the subkey you meant.
Restoring on another machine
The card holds the private key, but a fresh machine still has no idea the public key exists. Fetch it from a keyserver:
gpg --keyserver hkps://keys.openpgp.org --search-keys "[email protected]"
Then pull the card's public key stubs, which tell GnuPG that the private key lives on hardware:
gpg --edit-card
and fetch.
Finally, trust it:
gpg --edit-key $KEYID
and trust. Note that this is --edit-key, not --edit-card. Getting these
two confused is easy and the error messages do not point at the mistake.
git credentials through pass
pass-git-helper lets git pull credentials out of a pass store, which is
itself encrypted to the PGP key on the card:
yay -S pass-git-helper
git config --global credential.helper /usr/bin/pass-git-helper
git config credential.useHttpPath true
Create ~/.config/pass-git-helper/git-pass-mapping.ini:
[github.com]
target=github.com/<username>
credential.useHttpPath true matters if you keep per-repository credentials,
since without it every host collapses to a single entry.
FIDO2-backed SSH keys
Separate mechanism, same token. OpenSSH 8.2 added the ed25519-sk and
ecdsa-sk key types, where the private key is bound to the hardware token and
useless without it. libfido2 is required.
Both ends need to understand the key type, so an old server will reject these outright.
ssh-keygen -t ed25519-sk
You will be asked for the PIN and a touch to confirm generation, and normally a touch on every connection afterwards.
Point your config at the key explicitly:
Host SERVER1
IdentitiesOnly yes
IdentityFile ~/.ssh/id_ed25519_sk
IdentitiesOnly yes is worth setting. Without it, ssh offers every key it can
find, which on a token means a touch prompt for keys you did not intend to use.
Skipping the touch requirement
ssh-keygen -O no-touch-required -t ed25519-sk
Three caveats, in increasing order of annoyance:
-
Not every token supports it. On a YubiKey,
ed25519-skneeds firmware 5.2.3 or newer. -
sshdrejectsno-touch-requiredkeys by default. Allow it per key inauthorized_keys:no-touch-required [email protected] AAAAInN... [email protected]or system-wide in
/etc/ssh/sshd_configwithPubkeyAuthOptions none. -
GitHub and GitLab do not support
no-touch-requiredat all. If these keys are for pushing to a forge, you are touching the token on every push regardless of what you generated.
An ecdsa-sk keypair works the same way, with the usual reasons to prefer
ed25519 over ECDSA still applying.
Reference: the Arch wiki's SSH keys page and Yubico's importing keys guide.