YubiKey — OpenPGP, agent SSH & signature Git
J’utilise deux YubiKeys : une au quotidien, une en secours dans un tiroir fermé. Elles me servent pour du FIDO2/WebAuthn (connexion aux sites), du OpenPGP (signature Git, chiffrement) et comme clé SSH (le tout via gpg-agent).
Voici la marche à suivre complète, de la sortie du blister jusqu’aux commits signés.
Les clés privées OpenPGP générées sur la YubiKey ne peuvent jamais en ressortir. C’est tout l’intérêt, mais cela veut dire qu’une clé perdue ou cassée est une identité perdue. D’où le fait qu’il faut avoir deux clés avant de générer quoi que ce soit !
Réception et prise en main
Yubico expédie ses clés dans un blister scellé. Avant toute chose, vérifier qu’il n’a pas été ouvert, puis contrôler l’authenticité de la clé sur yubico.com/genuine (le site interroge l’attestation matérielle de la clé via WebAuthn).
Toute la configuration se fait avec YubiKey Manager CLI (ykman).
# Fedora / RHEL dnf install yubikey-manager # Debian / Ubuntu apt install yubikey-manager- Installe
ykman.
- Installe
ykman list- Liste les clés branchées avec leur numéro de série. À noter quelque part : c’est ce numéro qui permet de savoir laquelle des deux clés est insérée. Il est également inscrit physiquement sur la YubiKey.
ykman info- Affiche le modèle, la version de firmware et les applets activés sur chaque interface (USB / NFC).
- Il faut au minimum le firmware 5.2.3 pour utiliser Ed25519 / Curve25519 en OpenPGP. En dessous, on est limité au RSA.
ykman config usb --list ykman config usb --disable OTP --force- Désactive l’applet OTP. Sans ça, un appui involontaire sur la clé tape une longue chaîne
cccccc…dans le champ actif — dans un terminal ou un formulaire de mot de passe, c’est pénible. - Applets disponibles :
OTP,U2F,FIDO2,OATH,PIV,OPENPGP,HSMAUTH. Ne garder que ce qu’on utilise. - La clé redémarre après la commande : la débrancher / rebrancher.
- Désactive l’applet OTP. Sans ça, un appui involontaire sur la clé tape une longue chaîne
Réinitialiser l’applet OpenPGP
Sur une clé neuve comme sur une clé récupérée, on repart d’un applet OpenPGP propre.
ykman openpgp info- État actuel : version de l’applet, compteurs d’essais restants, politiques de touch, présence de clés.
ykman openpgp reset- Remet l’applet OpenPGP à zéro. Détruit irrémédiablement les clés qui s’y trouvent et remet les PIN par défaut. N’affecte pas les autres applets (FIDO2, PIV, OATH).
Après un reset, les valeurs d’usine sont :
| Secret | Valeur par défaut | Longueur mini | Rôle |
|---|---|---|---|
| User PIN | 123456 | 6 caractères | Usage quotidien : signer, déchiffrer, s’authentifier |
| Admin PIN | 12345678 | 8 caractères | Administration : générer des clés, changer les politiques, débloquer le User PIN |
| Reset Code | (non défini) | 8 caractères | Optionnel : débloque le User PIN sans connaître l’Admin PIN |
PIN, KDF et compteurs d’essais
L’ordre des opérations compte : kdf-setup et set-retries réinitialisent les PIN sur certains firmwares. On les fait avant de définir ses propres secrets.
gpg --card-edit gpg/card> admin gpg/card> kdf-setup gpg/card> quit- Active le Key Derived Format : le PIN est haché côté machine avant d’être envoyé à la clé, il ne transite donc jamais en clair sur le bus USB.
- À faire immédiatement après le reset, sur un applet vierge.
ykman openpgp access set-retries 5 5 5- Passe les compteurs d’essais à 5 pour le User PIN, le Reset Code et l’Admin PIN (3 par défaut). Demande l’Admin PIN.
ykman openpgp access change-pin ykman openpgp access change-admin-pin ykman openpgp access change-reset-codeDéfinit les trois secrets. Ou, de façon équivalente, en interactif :
gpg --card-edit gpg/card> admin gpg/card> passwd # 1 = User PIN, 2 = débloquer le User PIN, 3 = Admin PIN, 4 = Reset CodeLa YubiKey accepte jusqu’à 127 caractères : autant utiliser une vraie phrase de passe plutôt qu’un code numérique, d’autant que
gpg-agentla met en cache.
Si le User PIN est bloqué, on le débloque avec l’Admin PIN (
passwd→2) ou le Reset Code. Si l’Admin PIN est bloqué, il n’y a aucun recours : seulykman openpgp resetremet la clé en service, et il détruit les clés OpenPGP au passage.
Politique de présence (touch)
Par défaut, dès que le PIN est en cache, la clé signe et déchiffre sans aucune action physique : un malware sur la machine peut s’en servir silencieusement. La politique de touch corrige ça en exigeant un contact sur le disque doré.
| Politique | Comportement |
|---|---|
off | Aucun contact requis (défaut) |
on | Contact requis à chaque opération |
cached | Contact requis, puis mis en cache 15 secondes par la clé |
fixed | Comme on, mais irréversible sans reset de l’applet |
cached-fixed | Comme cached, mais irréversible sans reset de l’applet |
Même avec la politique sur
off, la clé réclame le User PIN à la première opération suivant chaque branchement : le cache degpg-agentest vidé dès que la carte est retirée. Ce queoffsupprime, c’est le contact physique, pas la saisie du PIN.
ykman openpgp keys set-touch sig cached ykman openpgp keys set-touch enc cached ykman openpgp keys set-touch aut cached ykman openpgp keys set-touch att cached- Applique la politique aux quatre emplacements :
sig(signature),enc(chiffrement),aut(authentification SSH) etatt(attestation). Demande l’Admin PIN. cachedest le bon compromis au quotidien : ungit rebasequi signe dix commits ne demande qu’un seul contact, mais rien ne se signe pendant qu’on est parti chercher un café.
- Applique la politique aux quatre emplacements :
Ne jamais utiliser
fixedoucached-fixedsans en mesurer la portée : la politique ne peut plus jamais être assouplie, y compris par soi-même. Le seul retour en arrière estykman openpgp reset, qui efface les clés.
gpg --card-edit gpg/card> admin gpg/card> forcesig- Bascule le drapeau Signature PIN entre
forced(la clé réclame le PIN à chaque signature, valeur par défaut) etnot forced. Le laisser surforced: le cache degpg-agentrend la contrainte invisible à l’usage.
- Bascule le drapeau Signature PIN entre
Générer l’identité OpenPGP sur la clé
C’est l’approche que je retiens : les trois clés sont générées par le processeur de la YubiKey et n’en sortent jamais.
Avec cette méthode, la clé de signature devient la clé maître (celle qui certifie les identités). Elle vit sur la YubiKey : sans elle, impossible d’ajouter une adresse e-mail, de signer une autre clé ou de prolonger une date d’expiration.
gpg --card-edit gpg/card> admin gpg/card> name # DUPONT # Jean gpg/card> lang # fr gpg/card> url # https://github.com/jeandupont.gpg- Renseigne les métadonnées du porteur. Le champ
urlest le plus utile : il indique où récupérer la clé publique, ce qui permet plus tard unfetchen une commande sur n’importe quelle machine.
- Renseigne les métadonnées du porteur. Le champ
gpg/card> key-attr # Signature key → (2) ECC → (1) Curve 25519 # Encryption key → (2) ECC → (1) Curve 25519 # Authentication → (2) ECC → (1) Curve 25519- Choisit l’algorithme des trois emplacements avant génération. Curve 25519 donne Ed25519 pour la signature et l’authentification, et Curve25519 (cv25519) pour le chiffrement : plus rapide et bien plus court que du RSA 4096, pour une sécurité au moins équivalente.
- Rester sur RSA 4096 si le firmware est antérieur à 5.2.3, ou si l’on doit interopérer avec des outils anciens.
gpg/card> generate # Sauvegarde hors carte de la clé de chiffrement ? → n # La clé est-elle valable jusqu'à ? → 0 (pas d'expiration) # Nom réel : Jean DUPONT # Adresse e-mail : jean.dupont@folf.fr # Commentaire : YubiKey Principale de Jean DUPONT !! gpg/card> quit- Génère les trois paires de clés sur la puce. Compter une à deux minutes en RSA 4096, quelques secondes en Curve 25519. Demande l’Admin PIN puis le User PIN.
- Répondre
nà la sauvegarde hors carte : la YubiKey ne laissera de toute façon pas sortir la clé. - Mettre le numéro de série de la clé dans le commentaire évite bien des hésitations quand on jongle entre deux YubiKeys.
gpg --card-status- Vérifie le résultat : empreintes des trois clés, politiques de touch, compteur de signatures, numéro de série. C’est aussi cette commande qui crée les stubs (références locales vers la carte) dans le trousseau.
Gérer plusieurs adresses e-mail
Une identité OpenPGP peut porter autant d’UID que nécessaire — pro, perso, alias — tous couverts par la même clé.
gpg --edit-key jean.dupont@folf.fr gpg> adduid # Nom réel : Jean DUPONT # Adresse e-mail : jean.dupont@email-pro.com gpg> adduid # Adresse e-mail : jean.dupont@email-associatif.be gpg> save- Ajoute des identités. Chaque
adduidest certifié par la clé maître : la YubiKey doit être branchée, elle demandera le User PIN et un contact.
- Ajoute des identités. Chaque
gpg --edit-key jean.dupont@folf.fr gpg> uid 2 gpg> primary gpg> save- Désigne l’UID n° 2 comme identité principale (celle affichée par défaut par les outils).
uid 2sélectionne,uid 2à nouveau désélectionne.
- Désigne l’UID n° 2 comme identité principale (celle affichée par défaut par les outils).
gpg --edit-key jean.dupont@email-pro.com gpg> uid 3 gpg> revuid gpg> save- Révoque un UID devenu obsolète (changement d’employeur, domaine abandonné). On ne supprime pas un UID déjà publié — on le révoque, pour que les personnes ayant la clé sachent qu’il ne faut plus l’utiliser.
Certificat de révocation
À faire le jour même de la génération, tant que la clé est disponible.
gpg --output revocation-0xABCDEF.asc --gen-revoke 0xABCDEF0123456789- Produit un certificat qui invalidera publiquement la clé. Choisir le motif
1(clé compromise) ou3(clé plus utilisée) — un certificat par motif utile, tant qu’à faire. - Sans ce fichier, une clé perdue reste indéfiniment valide aux yeux du monde.
- Produit un certificat qui invalidera publiquement la clé. Choisir le motif
Ce fichier permet à quiconque le détient de tuer l’identité. Il se stocke hors ligne : clé USB chiffrée dans un coffre, ou imprimé sur papier (
paperkey, ou un QR code).
Deux YubiKeys, deux identités croisées
Puisque chaque clé génère sa propre identité, il faut que les deux se reconnaissent mutuellement : une signature croisée permet à qui fait confiance à l’une de faire confiance à l’autre.
# YubiKey A branchée gpg --armor --export 0xAAAA1111 > cle-a.asc # YubiKey B branchée gpg --card-status gpg --armor --export 0xBBBB2222 > cle-b.asc- Exporte les deux clés publiques. Le
gpg --card-statusaprès changement de clé crée les stubs de la nouvelle carte.
- Exporte les deux clés publiques. Le
# YubiKey A branchée gpg -u 0xAAAA1111 --sign-key 0xBBBB2222 # YubiKey B branchée gpg -u 0xBBBB2222 --sign-key 0xAAAA1111- Chaque clé certifie l’autre.
-uforce la clé qui signe, ce qui est indispensable quand le trousseau en contient plusieurs.
- Chaque clé certifie l’autre.
gpg --armor --export 0xAAAA1111 0xBBBB2222 > cles-publiques.asc- Agrège les deux clés publiques, signatures croisées comprises, dans un seul fichier à distribuer.
Exporter et publier son identité
gpg --list-secret-keys --keyid-format=long --with-subkey-fingerprints- Affiche les empreintes complètes des clés et sous-clés. Le
>devantssbsignale une sous-clé qui vit sur une carte. C’est ici qu’on récupère l’identifiant de la sous-clé de signature pour Git.
- Affiche les empreintes complètes des clés et sous-clés. Le
gpg --armor --export 0xAAAA1111 > cle-publique.asc- Exporte la clé publique au format texte. C’est le seul fichier à diffuser librement.
gpg --export-ssh-key 0xAAAA1111- Sort la sous-clé d’authentification au format OpenSSH, prête à coller dans un
~/.ssh/authorized_keysou dans GitHub. Ajouter un!après l’identifiant (0xCCCC3333!) pour désigner une sous-clé précise.
- Sort la sous-clé d’authentification au format OpenSSH, prête à coller dans un
gpg --export-ownertrust > ownertrust.txt- Exporte la base de confiance. Utile pour rejouer à l’identique sur une autre machine, sans repasser par le menu
trust.
- Exporte la base de confiance. Utile pour rejouer à l’identique sur une autre machine, sans repasser par le menu
gpg --keyserver hkps://keys.openpgp.org --send-keys 0xAAAA1111- Publie sur un serveur de clés.
keys.openpgp.orgdemande une validation par e-mail pour chaque UID, et retire les signatures tierces : les signatures croisées n’y survivront pas. D’où l’intérêt de diffuser aussi le fichier agrégé.
- Publie sur un serveur de clés.
Je publie surtout mes clés via GitHub, qui les expose automatiquement :
curl https://github.com/jeandupont.gpg # clés OpenPGP curl https://github.com/jeandupont.keys # clés SSH publiques- GitHub sert les clés déclarées dans Settings → SSH and GPG keys. C’est cette URL que je mets dans le champ
urlde la carte, ce qui rend le déploiement sur une nouvelle machine trivial.
- GitHub sert les clés déclarées dans Settings → SSH and GPG keys. C’est cette URL que je mets dans le champ
Redéployer son identité sur une nouvelle machine
Le scénario du quotidien : machine fraîche, YubiKey en poche.
gpg -k- Crée l’arborescence
~/.gnupgavec les fichiers par défaut. Sans ça, les commandes suivantes râlent.
- Crée l’arborescence
gpg --import cle-publique.ascImporte l’identité publique. Sans clé publique dans le trousseau,
gpgne saura pas quoi faire de la carte : c’est bien la clé publique qui arrive en premier.Alternative sans transfert de fichier, si le champ
urla été renseigné sur la carte :gpg --card-edit gpg/card> fetch- Télécharge la clé publique depuis l’URL stockée sur la YubiKey.
gpg --edit-key jean.dupont@folf.fr gpg> trust # 5 = confiance ultime # y gpg> quit- Déclare la clé comme étant la sienne. Sans confiance ultime,
gpgaffiche un avertissement à chaque signature et Git refuse de considérer les commits comme vérifiés.
- Déclare la clé comme étant la sienne. Sans confiance ultime,
gpg --card-status- YubiKey branchée, crée les stubs de clés secrètes qui pointent vers la carte. À partir d’ici,
gpg --list-secret-keysmontre l’identité complète.
- YubiKey branchée, crée les stubs de clés secrètes qui pointent vers la carte. À partir d’ici,
Configurer gpg-agent (et l’agent SSH)
gpg-agent sait se faire passer pour un agent SSH. Une seule brique gère alors le PIN, le cache et les deux protocoles.
~/.gnupg/gpg-agent.conf pinentry-program /usr/bin/pinentry-qt enable-ssh-support default-cache-ttl 28800 max-cache-ttl 28800 default-cache-ttl-ssh 28800 max-cache-ttl-ssh 28800pinentry-qtsous KDE Plasma ;pinentry-gnome3sous GNOME,pinentry-cursesen TTY pur.enable-ssh-supportexpose la socketS.gpg-agent.ssh.- Les TTL sont en secondes : 28800 = 8 heures, soit une journée de travail avec une seule saisie de PIN.
max-cache-ttlplafonne le cache même en cas d’usage continu, il doit donc être au moins égal àdefault-cache-ttl.
gpgconf --kill gpg-agent gpgconf --launch gpg-agent- Applique la configuration.
gpgconf --reload gpg-agentsuffit pour la plupart des options, mais pas pourenable-ssh-supportqui exige un vrai redémarrage.
- Applique la configuration.
mkdir -p ~/.config/environment.d~/.config/environment.d/10-gpg-ssh.conf SSH_AUTH_SOCK=${XDG_RUNTIME_DIR}/gnupg/S.gpg-agent.ssh- Déclare la socket auprès du gestionnaire de session
systemd --user. C’est ce qui rend la variable disponible aux applications graphiques lancées depuis le menu (VS Code, IDE JetBrains, clients Git graphiques) et pas seulement aux terminaux. environment.dne fait pas de substitution de commande :$(gpgconf …)n’y fonctionnerait pas, seule l’expansion de variables déjà définies comme${XDG_RUNTIME_DIR}est supportée.
- Déclare la socket auprès du gestionnaire de session
mkdir -p ~/.config/plasma-workspace/env~/.config/plasma-workspace/env/10-gpg-agent.sh #!/bin/sh export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" gpgconf --launch gpg-agentchmod +x ~/.config/plasma-workspace/env/10-gpg-agent.sh- Filet de sécurité pour Plasma, qui exécute ces scripts au démarrage de la session. Redondant avec
environment.dsur une session Plasma démarrée par systemd, mais inoffensif et utile si la session bascule sur le mode de démarrage historique.
- Filet de sécurité pour Plasma, qui exécute ces scripts au démarrage de la session. Redondant avec
~/.bashrc export GPG_TTY=$(tty) if [ -z "$SSH_CONNECTION" ]; then export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)" fi gpg-connect-agent updatestartuptty /bye >/dev/nullGPG_TTYindique àpinentrysur quel terminal afficher la demande de PIN. Indispensable en console.- Le garde
SSH_CONNECTIONévite d’écraser l’agent transféré parssh -A: sans lui, se connecter à un serveur distant casserait le rebond par agent forwarding. updatestartupttyrecale l’agent déjà lancé sur le terminal courant. Sans ça, le second terminal d’une session hérite du TTY du premier et le prompt de PIN part dans le vide.
Après quoi, on redémarre pour que tout soit correctement appliqué.
Utiliser sa YubiKey en SSH
ssh-add -L- Liste les clés proposées par l’agent. La sous-clé d’authentification de la carte insérée doit apparaître, suivie d’un commentaire
cardno:….gpg-agentexpose automatiquement les clés d’authentification des cartes, sans passer par~/.gnupg/sshcontrol.
- Liste les clés proposées par l’agent. La sous-clé d’authentification de la carte insérée doit apparaître, suivie d’un commentaire
ssh-add -L | ssh utilisateur@serveur 'cat >> ~/.ssh/authorized_keys'- Autorise la clé sur un serveur distant.
ssh-copy-id utilisateur@serveurfait la même chose en allant chercher la clé publique dans l’agent — à condition d’y avoir déjà accès autrement (mot de passe, autre clé).
- Autorise la clé sur un serveur distant.
ssh -T git@github.com- Vérifie l’accès Git par SSH. La YubiKey clignote : contact requis.
gpg --export-ssh-key 0xAAAA1111 > ~/.ssh/yubikey-a.pub- Exporte la clé publique SSH de la yubikey.
Signer ses commits Git
gpg --list-secret-keys --keyid-format=long --with-subkey-fingerprints- Récupère l’identifiant de la sous-clé portant la capacité
[S]— c’est elle qui signe, pas la clé maître.
- Récupère l’identifiant de la sous-clé portant la capacité
git config --global user.name "Jean DUPONT" git config --global user.email "jean.dupont@folf.fr" git config --global user.signingkey '0xCCCC3333DDDD4444' git config --global commit.gpgsign true git config --global tag.gpgSign true git config --global gpg.format openpgp git config --global gpg.program /usr/bin/gpggpg.format openpgpest la valeur par défaut, mais l’expliciter évite les surprises si la configuration a un jour basculé surssh.- L’adresse de
user.emaildoit correspondre à un UID de la clé, sinon GitHub et consorts affichent la signature comme non vérifiée.
git commit --allow-empty -m "test signature" git log --show-signature -1- Vérifie la chaîne complète. La sortie doit indiquer
Good signature from "Jean DUPONT <…>"sans avertissement de confiance.
- Vérifie la chaîne complète. La sortie doit indiquer
Signer ses e-mails dans Thunderbird
Thunderbird embarque sa propre implémentation OpenPGP, avec un trousseau interne qui ne sait pas parler à une carte à puce. Pour signer avec la YubiKey, il faut le basculer sur le gpg du système.
Paramètres → Général → Éditeur de configuration… mail.openpgp.allow_external_gnupg = true- Active la prise en charge de GnuPG externe. Sans ce réglage, l’option permettant de saisir un identifiant de clé n’apparaît pas dans les paramètres du compte.
- Redémarrer Thunderbird.
Paramètres du compte → Chiffrement de bout en bout → Gestionnaire de clés OpenPGP → Fichier → Importer une ou des clés publiques depuis un fichier(ou… depuis une URL)- Importer
cle-publique.asc, oucles-publiques.ascpour couvrir les deux YubiKeys d’un coup. L’import par URL prend directement lehttps://github.com/jeandupont.gpgrenseigné dans le champurlde la carte. - Thunderbird n’a besoin que de la clé publique : elle lui donne les UID et les empreintes des sous-clés. La clé privée reste sur la YubiKey, c’est
gpgqui l’utilise.
- Importer
Gestionnaire de clés OpenPGP → clic droit sur la clé → Propriétés de la clé- Passer l’acceptation au niveau maximum — « Oui, j’ai vérifié en personne que cette clé a la bonne empreinte » — et cocher chacune des adresses e-mail de la clé. Une adresse non cochée fera passer les messages qui en partent pour non vérifiés.
- Relever au passage l’identifiant de la clé affiché ici : il sert à l’étape suivante.
Paramètres du compte → Chiffrement de bout en bout → Ajouter une clé → Utiliser votre clé externe via GnuPG- Coller l’identifiant relevé dans le gestionnaire de clés, puis sélectionner cette clé comme clé personnelle du compte.
- Cocher enfin « Signer numériquement les messages non chiffrés » pour signer par défaut. À la première signature, la YubiKey réclame le User PIN et un contact.