A Practical YubiKey Setup for FIDO2, OpenPGP, macOS and Linux
A YubiKey 5 contains separate applications for FIDO2, OpenPGP and PIV. They use different keys, PINs and reset procedures.
This setup uses:
| Device | Purpose |
|---|---|
| Daily A | FIDO2/passkeys, OpenPGP subkeys, PIV login |
| Daily B | Spare daily key with the same OpenPGP and PIV identities |
| Cold A | Offline copy of the OpenPGP primary key |
| Cold B | Optional second offline copy of the same primary key |
| USB Backup A/B | Encrypted full OpenPGP and PIV recovery material |
Design
- Create one OpenPGP primary key for the identity.
- Keep the primary key offline and copy it to one or two cold YubiKeys.
- Put the same OpenPGP signing, encryption and authentication subkeys on Daily A and Daily B.
- Register Daily A and Daily B separately for every FIDO2 account. FIDO2 credentials cannot be cloned.
- Put the same PIV 9A credential on both daily keys.
- Put the same PIV 9D credential on both daily keys.
- Use different key pairs for 9A and 9D.
- Create all backups before transferring OpenPGP keys to hardware.
This makes the daily keys interchangeable. The trade-off is shared compromise scope: losing either daily key requires rotating the shared OpenPGP subkeys and PIV credentials. FIDO2 credentials can be removed per device.
Requirements
Use a YubiKey 5 Series model. Yubico Security Key models support FIDO2 but not OpenPGP or PIV.
Install the required tools. The commands below assume ykman 5; a few slot names differ in ykman 4.
macOS
brew install ykman gnupg pinentry-mac openssl@3
export PATH="$(brew --prefix openssl@3)/bin:$PATH"
Point gpg-agent at the graphical pinentry. Without it GnuPG can only prompt in a terminal, which breaks signing from editors and Git clients:
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
echo "pinentry-program $(brew --prefix)/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf
gpgconf --kill gpg-agent
Ubuntu or Debian
sudo apt update
sudo apt install -y \
gnupg \
yubikey-manager \
openssl \
pcscd \
opensc-pkcs11 \
sssd \
libpam-sss \
pamtester \
p11-kit
Use a temporary offline Linux system for primary-key and PIV-key generation. A live USB with networking disabled avoids leaving private keys on a normal workstation.
1. Label and inventory the YubiKeys
Physically label the devices:
Daily A
Daily B
Cold A
Cold B
Connect one key at a time and record its model, firmware and serial number:
ykman info
Use separate secrets for:
- FIDO2 PIN
- OpenPGP user PIN
- OpenPGP admin PIN
- OpenPGP reset code
- PIV PIN
- PIV PUK
- PIV management key
Use a six-to-eight-digit numeric PIV PIN for broad macOS and Linux compatibility. Different PINs on Daily A and Daily B reduce shared risk.
The following commands erase an application and are not part of normal setup:
ykman fido reset
ykman openpgp reset
ykman piv reset
2. Configure FIDO2 and passkeys
Run these commands separately on Daily A and Daily B:
ykman fido info
ykman fido access change-pin
ykman fido credentials list
credentials list shows discoverable credentials stored on the key. It does not list every non-discoverable second-factor registration.
For each important account:
- Register Daily A.
- Test it from a signed-out session.
- Register Daily B as a second security key.
- Test Daily B.
- Store account recovery codes offline.
Do not register a cold OpenPGP key for routine FIDO2 use. It would no longer be cold storage.
3. Generate the OpenPGP identity offline
Create a temporary GnuPG home:
umask 077
export GNUPGHOME="$(mktemp -d)"
chmod 700 "$GNUPGHOME"
export GPG_TTY="$(tty)"
IDENTITY="Alex Example <alex@example.net>"
Create a certification-only primary key:
gpg --quick-generate-key \
"$IDENTITY" \
ed25519 \
cert \
5y
Read its fingerprint:
KEY_FP="$(
gpg --with-colons --list-keys "$IDENTITY" |
awk -F: '$1 == "fpr" { print $10; exit }'
)"
printf '%s\n' "$KEY_FP"
Create separate daily subkeys:
gpg --quick-add-key "$KEY_FP" ed25519 sign 2y
gpg --quick-add-key "$KEY_FP" cv25519 encrypt 2y
gpg --quick-add-key "$KEY_FP" ed25519 auth 2y
Inspect every fingerprint:
gpg --list-secret-keys \
--with-subkey-fingerprint \
"$KEY_FP"
The result should contain:
Primary key [C] certification
Subkey [S] signing
Subkey [E] encryption
Subkey [A] authentication
YubiKey 5 firmware 5.2.3 and later supports Ed25519 and Curve25519 in OpenPGP. Use RSA if the target YubiKey or required software does not support these curves.
To use RSA-4096 instead, for example to keep an existing RSA identity, for older software, or because the key will be published on Keybase (see section 7), swap the algorithm names. Everything else in this guide stays the same:
gpg --quick-generate-key "$IDENTITY" rsa4096 cert 5y
gpg --quick-add-key "$KEY_FP" rsa4096 sign 2y
gpg --quick-add-key "$KEY_FP" rsa4096 encrypt 2y
gpg --quick-add-key "$KEY_FP" rsa4096 auth 2y
4. Back up the complete OpenPGP identity
Create exports before using keytocard:
mkdir -p export
gpg --armor --export "$KEY_FP" \
> export/public.asc
gpg --armor --export-secret-keys "$KEY_FP" \
> export/secret-full.asc
gpg --armor --export-secret-subkeys "$KEY_FP" \
> export/secret-subkeys.asc
gpg --armor \
--output export/revocation.asc \
--gen-revoke "$KEY_FP"
gpg --fingerprint "$KEY_FP" \
> export/fingerprint.txt
gpg --export-ownertrust \
> export/ownertrust.txt
Copy the export directory to two separately encrypted USB drives stored in different secure locations.
Verify one backup in a new keyring:
VERIFY_HOME="$(mktemp -d)"
chmod 700 "$VERIFY_HOME"
GNUPGHOME="$VERIFY_HOME" \
gpg --import export/secret-full.asc
GNUPGHOME="$VERIFY_HOME" \
gpg --list-secret-keys --with-subkey-fingerprint
Compare the fingerprints with fingerprint.txt.
The encrypted secret-full.asc backups are the recovery source. Private keys transferred to a YubiKey cannot be exported from it later.
5. Put the OpenPGP subkeys on Daily A and Daily B
Provision each daily key from a fresh import of secret-full.asc. Do not try to copy keys back out of Daily A. In every new shell, set KEY_FP to the full primary fingerprint stored in fingerprint.txt:
KEY_FP="<FULL_PRIMARY_FINGERPRINT>"
Initialize the OpenPGP application
Connect Daily A:
ykman openpgp access change-pin
ykman openpgp access change-admin-pin
ykman openpgp access change-reset-code
Restore a fresh temporary keyring
export GNUPGHOME="$(mktemp -d)"
chmod 700 "$GNUPGHOME"
gpg --import /path/to/secret-full.asc
gpg --card-status
gpg --edit-key "$KEY_FP"
Inside GnuPG, list the keys:
gpg> list
Select the signing subkey and move it to the Signature slot:
gpg> key 1
gpg> keytocard
Deselect it, select the encryption subkey and move it to the Encryption slot:
gpg> key 1
gpg> key 2
gpg> keytocard
Deselect it, select the authentication subkey and move it to the Authentication slot:
gpg> key 2
gpg> key 3
gpg> keytocard
Save:
gpg> save
The numbers are examples. Use list and verify the [S], [E] and [A] flags before each transfer.
Set touch policies:
ykman openpgp keys set-touch sig on
ykman openpgp keys set-touch dec cached
ykman openpgp keys set-touch aut on
ykman 5 calls the encryption slot dec; ykman 4 called it enc. cached remembers a touch for 15 seconds. Avoid fixed and cached-fixed until the setup is fully tested; they cannot be disabled without deleting the affected private key.
Repeat the complete process for Daily B from a new temporary keyring and a fresh import of secret-full.asc.
6. Put the primary OpenPGP key on cold YubiKeys
This is optional but useful for offline key maintenance. The USB backups remain mandatory.
For Cold A:
- Initialize the OpenPGP PIN, admin PIN and reset code.
- Import
secret-full.ascinto a fresh temporary keyring. - Connect only Cold A.
- Open the key editor:
gpg --edit-key "$KEY_FP"
Ensure no subkey is selected, then transfer the primary key. GnuPG asks Really move the primary key?; confirm it:
gpg> key 0
gpg> keytocard
Choose the Signature slot and save:
gpg> save
Require touch:
ykman openpgp keys set-touch sig on
Repeat from another fresh import for Cold B.
Use the cold keys only in an offline environment to:
- add or renew subkeys
- revoke compromised subkeys
- add or revoke user IDs
- sign OpenPGP key-management changes
7. Use OpenPGP on daily computers
Import only the public key and mark it as ultimately trusted. Without the trust entry, gpg refuses to encrypt to the key (There is no assurance this key belongs to the named user) or asks for confirmation every time:
gpg --import public.asc
printf '%s:6:\n' "$KEY_FP" | gpg --import-ownertrust
gpg --card-status
Test signing:
printf 'hardware signing test\n' | gpg --clearsign
Test encryption and decryption:
printf 'hardware encryption test\n' |
gpg --armor --encrypt --recipient "$KEY_FP" \
> test-message.asc
gpg --decrypt test-message.asc
Git commit and tag signing
git config --global user.signingkey "$KEY_FP"
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git config --global gpg.program gpg
Test in a temporary repository:
mkdir yubikey-signing-test
cd yubikey-signing-test
git init
git commit --allow-empty -m "Test hardware-backed signing"
git log --show-signature -1
Publish the key on Keybase
Keybase can list the key on a profile and let others verify it without ever holding the private key. keybase pgp select reads the key from GnuPG’s secret-key list, where the card stubs appear like any other key, exports only the public half and has gpg sign the proof, so the YubiKey produces that signature and the offline primary key is not involved. This was tested with an RSA-4096 key, the variant from section 3. Keybase’s OpenPGP library also parses Ed25519 and Curve25519 keys, but that path is untested here. It accepts version 4 keys only, which is what GnuPG generates by default.
On a daily computer with Daily A inserted, the public key imported and Keybase logged in:
gpg --card-status
keybase pgp select
Pick the key when prompted and touch the YubiKey for the signature. Do not pass --import: the secret half is on the card, so the import cannot succeed, and it is not needed. If the account already lists another PGP key, add --multi, or retire the old key first. The Keybase key ID comes from keybase pgp list:
keybase pgp list
keybase pgp drop <KEYBASE_KEY_ID>
pgp drop only removes the key from the Keybase account. A PGP revocation certificate for a retired key has to be published separately.
After renewing or rotating subkeys (section 16), push the updated public key from GnuPG:
keybase pgp update "$KEY_FP"
This updates subkeys, user IDs and signatures on the profile; the primary key cannot be changed this way. Keybase’s own keybase pgp sign and keybase pgp decrypt need the private key in Keybase’s keyring, so keep using gpg for signing and decryption. The Keybase entry serves as the public directory and identity proof.
Fix GnuPG after switching daily keys
GnuPG keeps a stub for every card-backed key, and the stub records the serial number of the YubiKey the key was last seen on. With two daily keys holding the same subkeys, inserting the other one makes gpg ask for the card with the remembered serial number. Re-learn the inserted card:
gpg-connect-agent "scd serialno" "learn --force" /bye
If gpg-agent still holds stale card state, restart it:
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
gpg --card-status
If required:
gpgconf --kill all
gpg --card-status
Add this to the shell configuration:
export GPG_TTY="$(tty)"
8. Create the shared PIV login identity
PIV uses different slots for different purposes:
| Slot | Purpose in this setup |
|---|---|
| 9A | User authentication and computer login |
| 9D | Key management, encryption and macOS keychain/FileVault integration |
Create two separate key pairs:
PIV 9A key pair X
PIV 9D key pair Y
Import X into 9A on Daily A and Daily B. Import Y into 9D on Daily A and Daily B.
Do not import the same private key into both 9A and 9D.
Cloning PIV credentials provides an interchangeable spare, but:
- the keys were generated outside the YubiKeys
- per-device key attestation is lost
- logs cannot distinguish Daily A from Daily B cryptographically
- loss of either token requires rotation of the shared PIV identity
9. Create a local PIV certificate authority
Linux smart-card login should validate the card certificate against a trusted CA. Create a small offline CA instead of using unrelated self-signed certificates.
Run this in the offline environment:
umask 077
mkdir piv-provisioning
cd piv-provisioning
Create an encrypted CA key:
openssl genpkey \
-algorithm EC \
-pkeyopt ec_paramgen_curve:prime256v1 \
-aes-256-cbc \
-out piv-login-ca.key.pem
Create the CA certificate:
openssl req \
-new \
-x509 \
-sha256 \
-days 3650 \
-key piv-login-ca.key.pem \
-subj "/CN=Example PIV Login CA/O=Example Lab" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash" \
-out piv-login-ca.crt.pem
10. Generate separate 9A and 9D keys
Use P-256:
prime256v1 = secp256r1 = NIST P-256 = ECCP256
Do not use secp256k1; it is not the normal PIV interoperability curve.
openssl genpkey \
-algorithm EC \
-pkeyopt ec_paramgen_curve:prime256v1 \
-aes-256-cbc \
-out piv-9a.key.pem
openssl genpkey \
-algorithm EC \
-pkeyopt ec_paramgen_curve:prime256v1 \
-aes-256-cbc \
-out piv-9d.key.pem
Use a version in the certificate names. When a token is lost, issue v2 certificates and stop matching v1 on Linux.
openssl req \
-new \
-sha256 \
-key piv-9a.key.pem \
-subj "/CN=Alex Example PIV Authentication v1/O=Example Lab" \
-out piv-9a.csr.pem
openssl req \
-new \
-sha256 \
-key piv-9d.key.pem \
-subj "/CN=Alex Example PIV Key Management v1/O=Example Lab" \
-out piv-9d.csr.pem
Create 9a.ext:
basicConstraints = critical,CA:FALSE
keyUsage = critical,digitalSignature
extendedKeyUsage = clientAuth
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
Create 9d.ext:
basicConstraints = critical,CA:FALSE
keyUsage = critical,keyAgreement
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
Sign both certificates:
openssl x509 \
-req \
-in piv-9a.csr.pem \
-CA piv-login-ca.crt.pem \
-CAkey piv-login-ca.key.pem \
-set_serial 0x1001 \
-days 1825 \
-sha256 \
-extfile 9a.ext \
-out piv-9a.crt.pem
openssl x509 \
-req \
-in piv-9d.csr.pem \
-CA piv-login-ca.crt.pem \
-CAkey piv-login-ca.key.pem \
-set_serial 0x1002 \
-days 1825 \
-sha256 \
-extfile 9d.ext \
-out piv-9d.crt.pem
Verify them:
openssl verify \
-CAfile piv-login-ca.crt.pem \
piv-9a.crt.pem \
piv-9d.crt.pem
Expected output:
piv-9a.crt.pem: OK
piv-9d.crt.pem: OK
11. Import the shared PIV identity into both daily keys
Connect only Daily A.
Change the default administrative secrets:
ykman piv access change-pin
ykman piv access change-puk
ykman piv access change-management-key --generate --protect
Import the 9A key without writing an unencrypted copy to disk:
openssl pkey -in piv-9a.key.pem |
ykman piv keys import \
--pin-policy once \
--touch-policy never \
9a -
Import and verify its certificate:
ykman piv certificates import \
--verify \
9a piv-9a.crt.pem
Import the separate 9D key:
openssl pkey -in piv-9d.key.pem |
ykman piv keys import \
--pin-policy once \
--touch-policy never \
9d -
Import and verify its certificate:
ykman piv certificates import \
--verify \
9d piv-9d.crt.pem
Generate a device-specific CHUID and inspect the result:
ykman piv objects generate chuid
ykman piv info
ykman piv keys info 9a
ykman piv keys info 9d
Disconnect Daily A. Repeat the complete process on Daily B using the same four cryptographic files:
piv-9a.key.pem
piv-9a.crt.pem
piv-9d.key.pem
piv-9d.crt.pem
Use a different PIN, PUK, management key and CHUID for Daily B.
Store the encrypted PIV private keys and CA key in two cold locations if future exact cloning is required. Destroy the PIV private-key files after provisioning if exact replacement is not required. A replacement will then need a new PIV identity and new computer pairings.
12. Configure PIV login on macOS
macOS pairs an account with the public-key hash of the card and does not evaluate the certificate by default (checkCertificateTrust is 0), so the next two steps are optional hardening. They make macOS reject a card whose certificate does not chain to the local CA, and they add a failure mode: login stops working if the CA is not trusted or the leaf certificate has expired.
Optional: copy only piv-login-ca.crt.pem to the Mac and trust the local CA:
sudo security add-trusted-cert \
-d \
-r trustRoot \
-k /Library/Keychains/System.keychain \
piv-login-ca.crt.pem
Optional: require a trusted certificate chain for smart-card login:
sudo defaults write \
/Library/Preferences/com.apple.security.smartcard \
checkCertificateTrust \
-int 1
Insert Daily A and list identities:
sc_auth pairing_ui -s enable
sc_auth identities
Pair the 9A public-key hash with the local account:
sudo sc_auth pair \
-u alex \
-h <SHARED_9A_PUBLIC_KEY_HASH>
Check the pairing:
sc_auth list -u alex
sc_auth pairs the account with a public-key hash. Because Daily A and Daily B contain the same 9A key, one pairing should cover both tokens. Test both. If macOS presents a separate identity for Daily B, pair that displayed hash as well.
macOS also supports multiple unique smart-card pairings unless oneCardPerUser has been enabled.
Test both daily keys independently:
- Lock the Mac.
- Authenticate with Daily A and its PIV PIN.
- Remove Daily A.
- Authenticate with Daily B and its own PIN.
- Test logout and login.
- Test a full reboot.
- Confirm the login keychain unlocks correctly.
- Test FileVault preboot before relying on either key.
To lock the screen when the login token is removed, enable:
System Settings -> Privacy & Security -> Advanced
-> Turn on screen saver when login token is removed
FileVault
Apple silicon Macs with macOS 11 or later support PIV-compatible smart cards at FileVault authentication. Smart-card FileVault unlock is managed per user with sc_auth filevault. Check the status for the paired account and enable it if it is off:
sc_auth filevault -o status -u alex
sc_auth filevault -o enable -u alex -h <SHARED_9A_PUBLIC_KEY_HASH>
Test both physical keys from a complete shutdown.
Intel Macs with a T2 chip do not support smart cards in FileVault preboot. Use the password for FileVault and require the smart card at the following login window:
sudo defaults write \
/Library/Preferences/com.apple.loginwindow \
DisableFDEAutoLogin \
-bool YES
Do not enforce smart-card-only authentication until both keys, the local password, FileVault recovery key and recoveryOS procedure have been tested.
13. Configure PIV login on Ubuntu Linux
These steps target Ubuntu 24.04 LTS with local users. Its SSSD 2.9 still provides the implicit files domain used below. SSSD 2.11 removed the files provider, so Ubuntu 26.04 and other distributions shipping SSSD 2.11 or later need the proxy-domain variant at the end of this section. Other distributions also use different packages and PAM paths.
Enable the smart-card service:
sudo systemctl enable --now pcscd
Check available PKCS#11 modules and the connected YubiKey:
p11-kit list-modules
ykman piv info
Export the 9A certificate and verify it:
ykman piv certificates export 9a card-9a.pem
openssl verify \
-CAfile piv-login-ca.crt.pem \
card-9a.pem
Install the CA for SSSD:
sudo install -d -m 0755 /etc/sssd/pki
sudo install -m 0644 \
piv-login-ca.crt.pem \
/etc/sssd/pki/sssd_auth_ca_db.pem
Create or merge the following into /etc/sssd/sssd.conf. Do not overwrite an existing SSSD domain configuration.
[sssd]
enable_files_domain = True
services = pam
[certmap/implicit_files/alex]
matchrule = <ISSUER>.*CN=Example PIV Login CA.*&&<SUBJECT>.*CN=Alex Example PIV Authentication v1.*
[pam]
pam_cert_auth = True
pam_cert_db_path = /etc/sssd/pki/sssd_auth_ca_db.pem
Replace alex with the local Linux username.
Secure the file and restart SSSD:
sudo chown root:root /etc/sssd/sssd.conf
sudo chmod 600 /etc/sssd/sssd.conf
sudo systemctl restart sssd
Test certificate discovery:
sudo /usr/libexec/sssd/p11_child \
--pre \
-d 10 \
--debug-fd=2 \
--ca_db=/etc/sssd/pki/sssd_auth_ca_db.pem
Enable smart-card authentication with password fallback first:
sudo pam-auth-update \
--disable sss-smart-card-required \
--enable sss-smart-card-optional
Test without logging out:
pamtester -v login "$USER" authenticate
Run the test with Daily A, Daily B and then without a key to confirm password fallback.
After successful testing, smart-card-only PAM authentication can be enabled:
sudo pam-auth-update \
--disable sss-smart-card-optional \
--enable sss-smart-card-required
A global required configuration also affects sudo and su. Keep an authenticated root shell open while testing.
On GNOME, inspect and optionally enable lock on token removal:
gsettings range \
org.gnome.settings-daemon.peripherals.smartcard \
removal-action
gsettings set \
org.gnome.settings-daemon.peripherals.smartcard \
removal-action \
'lock-screen'
Because both daily YubiKeys contain the same 9A certificate and key, one SSSD mapping accepts either token.
SSSD 2.11 and later
The implicit files domain no longer exists there. SSSD’s own migration guide replaces it with a proxy domain that reads the local user database through the files NSS library, with local_auth_policy = only so that SSSD handles smart-card authentication for that domain itself. Install the sssd-proxy package and attach the mapping rule to the new domain name. The proxy provider learned certificate mapping in SSSD 2.9.3, so this variant also works on Ubuntu 24.04:
[sssd]
services = pam
domains = shadowutils
[domain/shadowutils]
id_provider = proxy
proxy_lib_name = files
local_auth_policy = only
[certmap/shadowutils/alex]
matchrule = <ISSUER>.*CN=Example PIV Login CA.*&&<SUBJECT>.*CN=Alex Example PIV Authentication v1.*
[pam]
pam_cert_auth = True
pam_cert_db_path = /etc/sssd/pki/sssd_auth_ca_db.pem
Password fallback still comes from pam_unix in the PAM stack. The p11_child, pam-auth-update and pamtester steps are unchanged; test with pamtester before enabling the required profile.
14. Use a YubiKey for SSH
FIDO2-backed OpenSSH keys are the simplest option. Generate a separate SSH credential on each daily key.
The OpenSSH bundled with macOS is built without a FIDO provider, so ssh-keygen -t ed25519-sk stops with No FIDO SecurityKeyProvider specified. Install Homebrew’s OpenSSH, which includes one, and make sure it comes first in PATH:
brew install openssh
Daily A:
ssh-keygen \
-t ed25519-sk \
-O verify-required \
-C "Alex Example - Daily A" \
-f ~/.ssh/id_ed25519_sk_daily_a
Daily B:
ssh-keygen \
-t ed25519-sk \
-O verify-required \
-C "Alex Example - Daily B" \
-f ~/.ssh/id_ed25519_sk_daily_b
Install both .pub files in each server’s authorized_keys.
verify-required requires FIDO user verification, normally the FIDO2 PIN. Add -O resident only when the credential should be recoverable from the token itself; a resident credential makes a stolen token more valuable.
Alternatives:
- expose the mirrored OpenPGP authentication subkey through
gpg-agent - use PIV through a PKCS#11 provider such as
ykcs11
15. Windows note
A standalone Windows account does not have the same simple local PIV pairing flow as macOS or SSSD on Linux.
For a personal Windows computer, use:
- Windows Hello for local sign-in
- FIDO2/passkeys for supported accounts
- separately registered Daily A and Daily B security keys
Native PIV smart-card sign-in is normally an Active Directory and PKI deployment. FIDO2 security-key sign-in can be enabled on supported Microsoft Entra joined devices.
16. Recovery and rotation
Daily A or Daily B is lost
- Remove that device’s FIDO2 registration from every account.
- Continue with the remaining daily key.
- Use a cold OpenPGP key to revoke the shared daily subkeys.
- Create and provision new OpenPGP subkeys on both daily keys.
- Create PIV
v29A and 9D keys and certificates. - Provision the remaining and replacement daily keys with the new PIV identity.
- Unpair the old macOS public-key hash and pair the new one.
- Change the Linux SSSD mapping from
v1tov2. - Register the replacement FIDO2 key separately.
A lost cloned PIV token cannot be revoked independently from its identical spare. Rotation is the clean response.
A daily key is damaged but remains in your possession
Provision a replacement from the encrypted OpenPGP and PIV backups. Rotation is optional when the old token is physically controlled and destroyed.
A cold YubiKey is lost
Restore the same OpenPGP primary key from secret-full.asc onto a replacement cold key in an offline environment.
One encrypted USB backup is lost
Create a new encrypted copy from the remaining verified backup.
The OpenPGP primary key may be compromised
Import and publish the revocation certificate, then create a new OpenPGP identity. Rotating only subkeys is insufficient.
17. Maintenance checklist
Every six to twelve months:
- test Daily A and Daily B on critical FIDO2 accounts
- test OpenPGP signing, decryption and authentication with both keys
- test macOS and Linux login with both keys
- test FileVault behavior after a full shutdown
- check OpenPGP subkey and PIV certificate expiry dates
- verify an encrypted
secret-full.ascbackup in a clean keyring - confirm account recovery codes remain available
- remove obsolete FIDO2 registrations
Avoid these mistakes
- Creating two unrelated OpenPGP primary keys for redundancy.
- Generating the only OpenPGP primary key directly on a YubiKey when duplication is required.
- Treating a cold YubiKey as a replacement for encrypted file backups.
- Assuming FIDO2 credentials are copied to a spare key.
- Importing one PIV private key into both 9A and 9D.
- Using
secp256k1for normal PIV login. - Enabling irreversible OpenPGP
fixedtouch policies before testing. - Enforcing smart-card-only login before testing both keys and recovery paths.
- Using certificates valid for tens of thousands of days.
- Weakening a main OpenPGP identity with legacy SHA-1 or 3DES preferences for old Keybase compatibility. Use a separate compatibility key instead.
Sources
- DrDuh YubiKey Guide
- YubiKey Manager installation
- YubiKey Manager FIDO commands
- Registering a spare FIDO security key
- YubiKey Manager OpenPGP commands
- GnuPG OpenPGP key management
- Keybase
pgp selectcommand - YubiKey Manager PIV commands
- Yubico PIV slot reference
- Yubico smart-card authentication on macOS
- Apple smart-card configuration
- Apple advanced smart-card options
- Apple FileVault and smart cards
- Apple login-token removal setting
- Ubuntu smart-card authentication
- SSSD 2.11.0 release notes
- SSSD files provider removal
- OpenSSH ssh-keygen manual
- Yubico SSH with PIV and PKCS#11
- Microsoft FIDO2 security-key sign-in