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.
  • 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.

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 :

SecretValeur par défautLongueur miniRôle
User PIN1234566 caractèresUsage quotidien : signer, déchiffrer, s’authentifier
Admin PIN123456788 caractèresAdministration : générer des clés, changer les politiques, débloquer le User PIN
Reset Code(non défini)8 caractèresOptionnel : 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-code
    
    • Dé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 Code
      
    • La YubiKey accepte jusqu’à 127 caractères : autant utiliser une vraie phrase de passe plutôt qu’un code numérique, d’autant que gpg-agent la met en cache.

Si le User PIN est bloqué, on le débloque avec l’Admin PIN (passwd2) ou le Reset Code. Si l’Admin PIN est bloqué, il n’y a aucun recours : seul ykman openpgp reset remet 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é.

PolitiqueComportement
offAucun contact requis (défaut)
onContact requis à chaque opération
cachedContact requis, puis mis en cache 15 secondes par la clé
fixedComme on, mais irréversible sans reset de l’applet
cached-fixedComme cached, mais irréversible sans reset de l’applet
  • 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) et att (attestation). Demande l’Admin PIN.
    • cached est le bon compromis au quotidien : un git rebase qui signe dix commits ne demande qu’un seul contact, mais rien ne se signe pendant qu’on est parti chercher un café.
    • Sur les versions de ykman antérieures à la 5, la commande s’écrit ykman openpgp set-touch sig cached.

Ne jamais utiliser fixed ou cached-fixed sans 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 est ykman 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) et not forced. Le laisser sur forced : le cache de gpg-agent rend la contrainte invisible à l’usage.

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 url est le plus utile : il indique où récupérer la clé publique, ce qui permet plus tard un fetch en une commande sur n’importe quelle machine.
  • 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 adduid est certifié par la clé maître : la YubiKey doit être branchée, elle demandera le User PIN et un contact.
  • 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 2 sélectionne, uid 2 à nouveau désélectionne.
  • 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) ou 3 (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.

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-status après changement de clé crée les stubs de la nouvelle carte.
  • # 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. -u force la clé qui signe, ce qui est indispensable quand le trousseau en contient plusieurs.
  • 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 > devant ssb signale 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.
  • 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_keys ou dans GitHub. Ajouter un ! après l’identifiant (0xCCCC3333!) pour désigner une sous-clé précise.
  • 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.
  • gpg --keyserver hkps://keys.openpgp.org --send-keys 0xAAAA1111
    
    • Publie sur un serveur de clés. keys.openpgp.org demande 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é.

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 url de la carte, ce qui rend le déploiement sur une nouvelle machine trivial.

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 ~/.gnupg avec les fichiers par défaut. Sans ça, les commandes suivantes râlent.
  • gpg --import cle-publique.asc
    
    • Importe l’identité publique. Sans clé publique dans le trousseau, gpg ne 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 url a é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, gpg affiche un avertissement à chaque signature et Git refuse de considérer les commits comme vérifiés.

    • Version scriptable, sans menu interactif :

      echo "AAAA1111BBBB2222CCCC3333DDDD4444EEEE5555:6:" | gpg --import-ownertrust
      
      • Prend l’empreinte complète (40 caractères). Le 6 correspond à la confiance ultime dans le format du fichier — c’est bien le 5 du menu interactif, la numérotation diffère.
  • 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-keys montre l’identité complète.

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 28800
    
    • pinentry-qt sous KDE Plasma ; pinentry-gnome3 sous GNOME, pinentry-curses en TTY pur.
    • enable-ssh-support expose la socket S.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-ttl plafonne 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-agent suffit pour la plupart des options, mais pas pour enable-ssh-support qui exige un vrai redémarrage.
  • 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.d ne 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.
  • 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-agent
    
    chmod +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.d sur une session Plasma démarrée par systemd, mais inoffensif et utile si la session bascule sur le mode de démarrage historique.
  • ~/.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/null
    
    • GPG_TTY indique à pinentry sur quel terminal afficher la demande de PIN. Indispensable en console.
    • Le garde SSH_CONNECTION évite d’écraser l’agent transféré par ssh -A : sans lui, se connecter à un serveur distant casserait le rebond par agent forwarding.
    • updatestartuptty recale 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-agent expose automatiquement les clés d’authentification des cartes, sans passer par ~/.gnupg/sshcontrol.
  • ssh-add -L | ssh utilisateur@serveur 'cat >> ~/.ssh/authorized_keys'
    
    • Autorise la clé sur un serveur distant. ssh-copy-id utilisateur@serveur fait 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é).
  • 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
    
    ~/.ssh/config
      
    Host github.com
        IdentitiesOnly yes
        IdentityFile ~/.ssh/yubikey-a.pub
    
    • À faire dès que le trousseau SSH contient déjà plusieurs clés : sans ça, le serveur peut couper la connexion (Too many authentication failures) avant d’arriver à celle de la YubiKey.
    • IdentityFile accepte ici une clé publique : ssh s’en sert uniquement pour désigner, parmi celles de l’agent, la seule identité à proposer.

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.
  • 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/gpg
    
    • Le ! final épingle exactement cette sous-clé : sans lui, gpg choisit lui-même une sous-clé de signature, ce qui devient ambigu avec plusieurs cartes. Les guillemets simples sont obligatoires, sinon Bash interprète le ! comme une expansion d’historique.
    • gpg.format openpgp est la valeur par défaut, mais l’expliciter évite les surprises si la configuration a un jour basculé sur ssh.
    • L’adresse de user.email doit 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.

Deux identités selon le dépôt

Avec une clé pro et une clé perso, autant laisser Git choisir en fonction du chemin du dépôt.

  • ~/.gitconfig
      
    [user]
        name = Jean DUPONT
    [commit]
        gpgsign = true
    [tag]
        gpgSign = true
    [gpg]
        program = /usr/bin/gpg
    [includeIf "gitdir:~/Documents/GitHub/Perso/"]
        path = ~/.gitconfig-perso
    [includeIf "gitdir:~/Documents/GitHub/Pro/"]
        path = ~/.gitconfig-pro
    
    ~/.gitconfig-perso
      
    [user]
        email = jean.dupont@folf.fr
        signingkey = 0xAAAA1111BBBB2222!
    
    • includeIf charge le bon couple e-mail / clé selon l’emplacement du dépôt. Le slash final du gitdir: est significatif : il fait porter la règle sur tout le sous-arbre.

FIDO2 / WebAuthn

L’applet FIDO2 est indépendant d’OpenPGP : PIN distinct, reset distinct.

  • ykman fido info
    
    • État de l’applet : PIN défini ou non, essais restants, nombre de clés résidentes stockées.
  • ykman fido access change-pin
    
    • Définit ou change le PIN FIDO2 (4 caractères minimum, 63 maximum). Requis pour les passkeys (clés résidentes) et pour toute authentification à deux facteurs de la clé elle-même.
  • ykman fido credentials list
    ykman fido credentials delete <credential-id>
    
    • Liste et supprime les passkeys stockées sur la clé. L’espace est limité (25 à 100 selon le firmware) : le ménage se fait à la main.
  • ykman fido reset
    
    • Remet l’applet FIDO2 à zéro et invalide toutes les passkeys enregistrées. À ne faire qu’après avoir enregistré la seconde clé sur les services concernés.

Le PIN FIDO2 tolère 8 essais au total, avec un débranchement / rebranchement imposé toutes les 3 tentatives échouées. Au huitième échec, l’applet se verrouille et seul un ykman fido reset le débloque — au prix de toutes les passkeys.

Le vrai réflexe : enregistrer les deux YubiKeys sur chaque service, au moment de l’inscription. Rajouter la seconde clé après avoir perdu la première n’est plus possible.