Yubikey Passthrough on WSL2 With Full FIDO2 Support
I recently starting using Yubikeys both to store passkeys which allow me to do passwordless logins to websites like GitHub, and to SSH into remote servers with FIDO2.
I have a number of machines at home, but I spend the majority of my time using
a Windows 11 desktop computer running NixOS on WSL2 (in the past I’ve described
Windows 11 + my tiling window manager
komorebi as the “desktop environment”
on top of my NixOS WSL2 shell).
Rarely if ever do I make SSH connections from Windows 11 directly; if I am making an SSH connection it is almost always from NixOS. This posed a problem for my adoption of FIDO2 SSH with my Yubikeys, because the process is not quite as simple as just passing through the USB Yubikey to the WSL2 VM.
Below I’ll outline the steps to get USB Yubikey passthrough to a NixOS WSL2 VM working with full FIDO2 support.
Although these steps specifically target NixOS, the underlying information can be used to produce the same result on the Linux distribution of your choice.
Prerequisites on Windows 11⌗
Get started by installing Yubikey Manager on Windows and making sure your Yubikey(s) are being recognized:
winget install -e --id Yubico.YubikeyManager
# Unfortunately, WinGet _still_ isn't able to place installed binaries in the $PATH reliably 🤦
❯ & "C:\Program Files\Yubico\YubiKey Manager\ykman.exe" info
Device type: YubiKey 5C
Serial number: XXXXXXXXXX
Firmware version: 5.4.3
Form factor: Keychain (USB-C)
Enabled USB interfaces: OTP, FIDO, CCID
Applications
OTP             Enabled
FIDO U2F        Enabled
FIDO2           Enabled
OATH            Enabled
PIV             Enabled
OpenPGP         Enabled
YubiHSM Auth    Enabled
Next, install usbipd-win, which is
what we’ll use to do the USB passthrough to the WSL2 VM.
# For whatever reason, this one does seem to be added to the $PATH correctly 🤷
winget install usbipd
In an Administrator PowerShell Terminal, run usbipd list and take note of the
BUSID for your Yubikey, which we will need later:
❯ usbipd list
Connected:
BUSID  VID:PID    DEVICE                                                        STATE
9-4    1050:0407  USB Input Device, Microsoft Usbccid Smartcard Reader (WUDF)   Not shared
Building and Loading a Custom WSL2 Linux Kernel⌗
Now comes the fun part.
In order to enable full USB passthrough with FIDO2 support, we need to compile a custom WSL2 Linux kernel, because the kernels released by Microsoft are for whatever reason missing a few key options that we need.
If you’ve already compiled your own WSL2 Linux Kernels before and are
comfortable with this process, you just need to go and enable HIDDEV and
HIDRAW and then recompile.
Otherwise, you can navigate to my
custom-wsl2-linux-kernel
project and download the latest release. This project pulls the latest version
of the offical
WSL2-Linux-Kernel released by
Microsoft, enables the required configuration options, builds the kernel on
GitHub Actions, and finally makes the resulting vmlinux file available to
download.
If you are interested in building WSL2 Linux kernels with different options
enabled, you
can fork the project and commit your own edits to the config-wsl file. GitHub
Actions will then start building the kernel for you and store the resulting
vmlinux file as a build artifact for you to download.
Anyway, once you have your vmlinux file, place it in a convenient directory
(I keep mine in $HOME 🤷) and then edit (or create) your ~/.wslconfig file
on Windows 11 to instruct WSL2 to use this specific kernel when booting VMs:
[wsl2]
kernel=C:\\Users\\LGUG2Z\\vmlinux
Now make sure you’ve saved any work you’re doing in any WSL2 VMs and run wsl --shutdown in a PowerShell prompt. Then wait for at least 10 seconds after
that command terminates before starting up your NixOS WSL2 VM again.
Configuring NixOS to Automatically Attach Your Yubikey⌗
Thankfully, now that we are back in NixOS land the rest of this tutorial is fairly declarative.
I must give a huge thank you to everyone who has been contributing to the
various threads about this on the
NixOS-WSL repo, in particular the
user terlar who has put together a pending
PR which the Nix code
below is largely based on.
Start by creating a usbip.nix file and storing it wherever feels best in your
flake repo:
{
  config,
  lib,
  pkgs,
  ...
}:
with lib; let
  usbipd-win-auto-attach = pkgs.fetchurl {
    url = "https://raw.githubusercontent.com/dorssel/usbipd-win/v3.1.0/Usbipd/wsl-scripts/auto-attach.sh";
    hash = "sha256-KJ0tEuY+hDJbBQtJj8nSNk17FHqdpDWTpy9/DLqUFaM=";
  };
  cfg = config.wsl.usbip;
in {
  options.wsl.usbip = with types; {
    enable = mkEnableOption "USB/IP integration";
    autoAttach = mkOption {
      type = listOf str;
      default = [];
      example = ["4-1"];
      description = "Auto attach devices with provided Bus IDs.";
    };
  };
  config = mkIf (config.wsl.enable && cfg.enable) {
    environment.systemPackages = [
      pkgs.linuxPackages.usbip
      pkgs.yubikey-manager
      pkgs.libfido2
    ];
    services.pcscd.enable = true;
    services.udev = {
      enable = true;
      packages = [pkgs.yubikey-personalization];
      extraRules = ''
        SUBSYSTEM=="usb", MODE="0666"
        KERNEL=="hidraw*", SUBSYSTEM=="hidraw", TAG+="uaccess", MODE="0666"
      '';
    };
    systemd = {
      services."usbip-auto-attach@" = {
        description = "Auto attach device having busid %i with usbip";
        after = ["network.target"];
        scriptArgs = "%i";
        path = [pkgs.linuxPackages.usbip];
        script = ''
          busid="$1"
          ip="$(grep nameserver /etc/resolv.conf | cut -d' ' -f2)"
          echo "Starting auto attach for busid $busid on $ip."
          source ${usbipd-win-auto-attach} "$ip" "$busid"
        '';
      };
      targets.multi-user.wants = map (busid: "usbip-auto-attach@${busid}.service") cfg.autoAttach;
    };
  };
}
Then, in the part of your flake that deals with your WSL configuration, import
the file and set the appropriate BUSID for your Yubikey which we determined
earlier:
{
  imports = [
    # Your import path will probably be different
    ./usbip.nix
  ];
  wsl = {
    usbip = {
      enable = true;
      # Replace this with the BUSID for your Yubikey
      autoAttach = ["9-4"];
    };
  };
}
Go ahead and rebuild your system with your updated NixOS configuration, and now whenever your Yubikey is detected in the port that you have identified, it will be passed through automatically to your NixOS VM. 🎉
We can test this by running ykman fido credentials list inside of our WSL2 VM:
❯ ykman fido credentials list
Enter your PIN: ************
Credential ID  RP ID              Username               Display name
abcdefgh...    github.com         LGUG2Z                 جاد
Great! You can now use your existing FIDO2 SSH keys from inside your WSL2 VM, or generate a new key by following the Linux setup instructions for “Securing SSH with FIDO2” on the Yubico website.
A few things to keep in mind:
- You’re probably going to want to keep two Yubikeys connected to your Windows machine simultaneously in order to use one in Windows and one in WSL2
- If you have both Yubikeys connected simultaneously, and you have set up Yubico Login for Windows, you’ll have to remove one of them when you are logging into the computer after a restart otherwise the login will fail
- Outside of this specific edge case, I haven’t encountered any other issues with keeping two Yubikeys connected simultaneously for daily use
- In order to use FIDO2 functionality, you need to explicitly set up a FIDO2 PIN on your Yubikey as one is not set by default
I’m Not Sure I Can Replicate This on Ubuntu…⌗
The main things to note if you are trying to adapt the NixOS configuration snippets above to work on other Linux distrubtions are:
- The packages being added to the system environment
- The auto-attach.shscript being pulled from theusbipd-winproject
- The udevrules being set forusbandhidraw*
- The systemdservice which calls theauto-attach.shscript and attaches the device on the givenBUSIDto the VM
Look, I’m not gonna lie, I probably couldn’t replicate this with any confidence on Ubuntu either.
If you’d like to try running NixOS on WSL2, please take a look at my
nixos-wsl-starter template
which should have you up and running with a useable terminal-powered
development environment in less than 10 minutes.
There is even a video you can follow along with step by step.
If you have any questions you can reach out to me on Twitter and Mastodon.
If you’re interested in what I read to come up with solutions like this one, you can subscribe to my Software Development RSS feed.
If you’d like to watch me writing code while explaining what I’m doing, you can also subscribe to my YouTube channel.