Déverrouillage LUKS et authentification FIDO2 sous NixOS avec YubiKey et Nitrokey

Du 2FA pour bien commencer la journée

Cet article est la refonte d’un précédent de 2023, avec quelques précisions.

Renforcer la sécurité de son poste avec FIDO2 sous NixOS

Depuis plusieurs années, j’utilise une clé de sécurité FIDO2 pour renforcer la protection de mes postes de travail. Initialement, il s’agissait principalement d’une YubiKey utilisée pour l’authentification forte et le déverrouillage du disque système.

Les YubiKey sont d’excellents dispositifs : compactes, robustes et largement supportées. Leur principal défaut est qu’elles constituent un point unique de défaillance (Single Point of Failure). En cas de perte sans solution de secours, l’accès au système peut devenir compliqué.

Elles restent également relativement coûteuses et reposent sur un matériel propriétaire.

Parmi les alternatives, on trouve notamment les clés Solo et les produits de la société Nitrokey. Cette dernière propose plusieurs modèles compatibles FIDO2, dont l’intérêt est d’être conçus en Europe, publiés en Open Hardware et régulièrement mis à jour.

N’utilisant pas les fonctionnalités OpenPGP ou SSH de ma YubiKey, je souhaitais disposer d’une solution de secours économique. J’avais déjà une Nitrokey Pro 2 ainsi qu’une Nitrokey FIDO2 qui dormaient dans un tiroir. Depuis, Nitrokey a sorti la Nitrokey 3, qui regroupe les fonctionnalités FIDO2 dans un périphérique plus moderne.

Lors du renouvellement récent de mon poste de travail, j’en ai profité pour revoir complètement ma gestion du chiffrement disque et de l’authentification.

Chiffrement LUKS avec FIDO2

L’ancienne approche

Historiquement, j’utilisais une méthode proche de celle décrite ici :

https://github.com/sgillespie/nixos-yubikey-luks

Cette solution fonctionne correctement, mais reste relativement rigide lorsqu’il s’agit d’ajouter ou de remplacer des clés de sécurité.

L’arrivée de systemd-cryptenroll

Depuis plusieurs versions, NixOS bénéficie de l’intégration de systemd-cryptenroll, qui simplifie considérablement la gestion des moyens de déverrouillage d’un volume LUKS.

Cette méthode permet notamment :

  • D’enregistrer plusieurs clés FIDO2 ;
  • de conserver plusieurs mots de passe ;
  • de générer une clé de récupération (recovery key) ;
  • d’ajouter ou supprimer des méthodes d’authentification à tout moment ;
  • de remplacer facilement une clé perdue ou défectueuse.

L’enrôlement d’une clé FIDO2 est particulièrement simple :

1
2
sudo systemd-cryptenroll /dev/nvme0n1p2 \ --fido2-device=auto \
--fido2-with-client-pin=true 

L’option --fido2-with-client-pin=true impose la saisie du code PIN de la clé lors du processus d’authentification. Cette option n’est pas obligatoire.

On peut répéter l’opération autant de fois que nécessaire pour enregistrer plusieurs YubiKey ou NitroKey.

Vérifier les méthodes enregistrées

Pour afficher les mécanismes de déverrouillage présents sur un volume :

1
systemd-cryptenroll /dev/nvme0n1p2

Exemple :

1
SLOT TYPE 0    password 1    fido2 2    fido2 3    fido2 4    fido2

Ici, le volume peut être déverrouillé soit avec le mot de passe LUKS traditionnel, soit avec l’une des quatre clés FIDO2 enregistrées.

Pour obtenir davantage d’informations :

1
cryptsetup luksDump /dev/nvme0n1p2 

La section Tokens: permet notamment de visualiser l’association entre les différents slots LUKS et les identifiants FIDO2.

Configuration NixOS

Dans hardware-configuration.nix, le volume chiffré est généralement déjà déclaré :

1
2
3
4
5
6
7
boot.initrd.luks.devices = { 
  "partitions" = { 
    device = "/dev/nvme0n1p2";
    preLVM = true;
    crypttabExtraOpts = [ "fido2-device=auto" ];
  };
};

Cette option indique à systemd-cryptsetup de rechercher automatiquement un périphérique FIDO2 compatible lors du démarrage.

Le détail qui change tout

Pendant longtemps, malgré un enrôlement correct des clés FIDO2, le système continuait à demander systématiquement le mot de passe LUKS.

La solution a été d’activer explicitement l’initrd basé sur systemd :

1
boot.initrd.systemd.enable = true;

Après reconstruction :

1
sudo nixos-rebuild switch

le comportement attendu est apparu immédiatement.

Au démarrage :

  • la clé FIDO2 est détectée automatiquement ;
  • sa LED s’allume ;
  • un simple contact sur le capteur valide l’authentification ;
  • en l’absence de clé, le système bascule naturellement sur la demande du mot de passe LUKS.

Pourquoi est-ce nécessaire ?

Les mécanismes FIDO2 de systemd-cryptenroll reposent sur les composants systemd présents dans l’environnement d’initialisation.

Lorsque l’initrd classique est utilisé, certaines fonctionnalités avancées de systemd-cryptsetup peuvent ne pas être disponibles ou se comporter différemment selon les versions.

L’activation de :

1
boot.initrd.systemd.enable = true;

garantit que le processus de déchiffrement est entièrement pris en charge par systemd dès les premières phases du démarrage.

Nouveauté 2026

La version 2026.5 introduit le passage obligatoire à un initrd basé sur systemd à la place de l’ancienne fabrication basée sur des scripts. Il faudra également veiller à changer :

1
    device = "/dev/nvme0n1p2";

par son équivalent bésée sur mapper :

1
    device = "dev/mapper/partitions"

Authentification FIDO2 pour la session et sudo

Le déverrouillage du disque est une première étape, mais il est également possible d’utiliser les mêmes clés pour l’authentification PAM (connexion graphique, TTY, sudo, etc.).

La génération du fichier d’enregistrement se fait avec :

1
pamu2fcfg -u alexandre > ~/.config/Yubico/u2f_keys

Un détail peu documenté mérite d’être signalé : même avec une Nitrokey, il est préférable d’utiliser le répertoire ~/.config/Yubico/.

La documentation Nitrokey indique souvent :

1
pamu2fcfg -u alexandre > ~/.config/Nitrokey/u2f_keys 

Cependant, sous NixOS et avec certaines configurations PAM, cela peut empêcher le fonctionnement correct de l’authentification.

Pour éviter tout problème, il est conseillé de centraliser les enregistrements dans :

1
~/.config/Yubico/u2f_keys

que l’on utilise une YubiKey, une Nitrokey ou plusieurs clés simultanément.

Tester sa configuration

Avant de redémarrer ou de modifier les règles PAM, il est fortement recommandé de tester la configuration.

Installation de l’outil :

1
nix-shell -p pamtester

Tests :

1
pamtester login alexandre authenticate pamtester sudo alexandre authenticate 

Ces vérifications permettent d’éviter de se retrouver bloqué après une mauvaise configuration.

Verrouiller son poste automatiquement lors du retrait de la clé

Fin du fin, lancer le lock screen lors du retrait de la clé. Ici c’est un peu plus complexe. Cela dépend de plusieurs facteurs. Est-ce que l’environnement graphique est configuré depuis home-manager ou depuis configuration.nix ? Quel est le locker ? Moi j’utilise Wayland, hyprland et hyprlock. hyprland est installé depuis configuration.nix, mais il est configuré depuis home-manager.

Pour y arriver j’ai la règle udev suivante dans configuration.nix :

1
2
3
4
5
6
7
8
#  exécute hyprlock lors du retrait de la yubikey
  services.udev.extraRules = ''
 # Remplacez XXXX:YYYY par l'ID vendor:product de votre YubiKey
 # Pour trouver l'ID : lsusb | grep -i yubi
    ACTION=="remove", SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{PRODUCT}=="1050/407/*", RUN+="${pkgs.systemd}/bin/systemctl --no-block --user --machine=alexandre@.host start yubikey-lock.service"
  '';
  # Configure la fermeture du laptop sur lock+suspend nécessaire pour hyprlock+hypridle
  services.logind.settings.Login.HandleLidSwitch = "suspend";

Pourquoi ne pas utiliser le mécanisme de loginctl ? Dans ce type de configuration, l’information ne passe pas dans le canal D-Bus.

La configuration qui va bien de hyprlock et hypridle :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
{
  pkgs,
  config,
  lib,
  ...
}:
{
  services.hypridle = {
    enable = true;
    settings = {
      general = {
        lock_cmd = "pidof hyprlock || hyprlock";
        before_sleep_cmd = "pidof hyprlock || hyprlock";
        after_sleep_cmd = "hyprctl dispatch dpms on";
      };

      listener = [
        {
          timeout = 900;
          on-timeout = "pidof hyprlock || hyprlock";
        }

        {
          timeout = 930;
          on-timeout = "hyprctl dispatch dpms off";
          on-resume = "hyprctl dispatch dpms on";
        }
      ];
    };
  };
  systemd.user.services.yubikey-lock = {
    Unit.Description = "Lock screen on YubiKey removal";
    Service = {
      Type = "oneshot";
      ExecStart = "${pkgs.hyprlock}/bin/hyprlock";
      Environment = [
        "WAYLAND_DISPLAY=wayland-1"
        "XDG_RUNTIME_DIR=/run/user/1000"
      ];
    };
  };
  systemd.user.services.hyprlock-on-lock = {
    Unit = {
      Description = "Launch hyprlock on loginctl lock-session";
      After = [ "graphical-session.target" ];
    };
    Service = {
      Type = "oneshot";
      ExecStart = "${pkgs.hyprlock}/bin/hyprlock";
      Environment = [
        "WAYLAND_DISPLAY=wayland-1"
        "XDG_RUNTIME_DIR=/run/user/1000"
      ];
    };
  };

  #
  # Lier le service au signal lock de systemd-logind
  systemd.user.targets.lock = {
    Unit = {
      Description = "Lock target";
      Requires = [ "hyprlock-on-lock.service" ];
    };
    Install.WantedBy = [ "lock-session@.target" ];
  };
  # ================ hyprlock ===============
  programs.hyprlock = {
    enable = true;
  };
  xdg.configFile."hypr/hyprlock.conf".text = ''
    general {
        disable_loading_bar = true
    }
    background {
        monitor =
        path = screenshot
        blur_passes = 3
        blur_size = 8
    }

    label {
        monitor =
        text = cmd[update:1000] echo "$(date +"%H:%M")"
        font_size = 96
        position = 0, 100
        halign = center
        valign = center
    }
    label {
    monitor =
    text = cmd[update:30000] sh -c 'echo " $(cat /sys/class/power_supply/BAT*/capacity | head -n1)%"'
    color = rgba(a6e3a1ff)

    font_size = 22
    font_family = JetBrainsMono Nerd Font

    position = -30, 30
    halign = right
    valign = bottom
    }
    input-field {
        monitor =
        size = 250, 50
        outline_thickness = 2
        dots_size = 0.2
        position = 0, -100
        halign = center
        valign = center
    }
  '';
}

Conclusion

L’association de LUKS, FIDO2 et systemd-cryptenroll apporte un excellent compromis entre sécurité et confort d’utilisation.

La configuration minimale repose essentiellement sur :

1
2
3
4
5
6
7
8
9
boot.initrd.systemd.enable = true;

boot.initrd.luks.devices = {
  "partitions" = {
     device = "/dev/mapper/partitions";
     preLVM= true;
     crypttabExtraOpts = [ "fido2-device=auto" ];
  };
};

Cette approche permet :

  • Un déverrouillage rapide du disque grâce à une clé FIDO2 ;
  • la conservation du mot de passe LUKS comme solution de secours ;
  • l’enregistrement de plusieurs clés matérielles ;
  • l’utilisation des mêmes dispositifs pour l’authentification système et sudo.

En pratique, c’est probablement l’une des améliorations les plus simples à mettre en place pour renforcer significativement la sécurité d’un poste NixOS tout en gagnant en confort au quotidien.

En prime on peut ajouter le démarrage du lock screen lorsque l’on ferme l’écran d’un portable et quand on retire la clé.

Généré avec Hugo
Thème Stack conçu par Jimmy