Yubikey udev rules and group permissions

I recently got my YubiKey (w GPG keys) working on NixOS, but hit some snags along the way around udev rules.

services.pcscd.enable = true;
# unclear if these are useful
services.udev.packages = [ pkgs.yubikey-personalization ];
hardware.gpgSmartcards.enable = true;

As root, gpg --card-status worked, but as the nixos user (“wheel” group) it didn’t, which ruled out various issues.

gpg --card-status
gpg: selecting card failed: No such device
gpg: OpenPGP card not available: No such device
# gpg-agent logs with guru log level
scdaemon[13819]: check permission of USB device at Bus 002 Device 010

Looking into /etc/udev/rules.d, I saw 60-scdaemon.rules has a rule matching my card.

# Yubikey 4 OTP+U2F+CCID
SUBSYSTEM=="usb", ATTR{idVendor}=="1050", ATTR{idProduct}=="0407", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"

and a device file is created with user root, group root.

ls -al /dev/bus/usb/002
crw-rw-r-- 1 root root 189, 137 Oct  9 21:05 010

But wait, scdaemon (nor pcscd) don’t run as root these days. scdaemon runs per-user - one for root and another as a regular user. Which explains why scdaemon can’t read the usb device.

ps aux | rg scdaemon
root        3766  0.0  0.0 229588  3312 ?        SLl  17:07   0:00 scdaemon --multi-server
nixos      13819  0.0  0.0 445680  3512 ?        SLl  19:54   0:00 scdaemon --multi-server

Reading this writeup about this area, there are a lot of options.

  • Run scdaemon as root
  • Run pcscd as root
  • Add your own udev rule to make usb devices use the “wheel” group (or something else)
  • Use some semi-standard custom group?

In my case, it’s a single user system. I unfortunately do need to keep supporting GPG. But would prefer this were simpler.

So my question is, do you run scdaemon or pcscd or both? Are they per-user or a shared root one? Do you use a custom group for Yubikey-like devices?

Approach 3:

services.udev.extraRules = ''
    SUBSYSTEM=="usb", ATTR{idVendor}=="1050", ATTR{idProduct}=="0407", ENV{ID_SECURITY_TOKEN}="1", GROUP="wheel"
  '';

I can’t say that I’m experiencing this, and haven’t engaged any workarounds you mention:

I don’t:

  • use pcscd anymore
  • do anything to run scdaemon differently than gnupg wants to run it

I do use:

hardware.gpgSmartcards.enable = true;
hardware.ledger.enable = true; # probably unrelated
services.udev.packages = [ pkgs.yubikey-personalization ];
services.pcscd.enable = false;

I do also include yubikey-personalization, yubikey-manager, and yubico-piv-tool in my home pages, but that also shouldn’t affect what you’re discussing.

Unfortunately, I can’t really explain why this works. A quick skim through all of my /dev/bus/usb devices indicates they’re all owned by root/root. Checking ^ps aux | grep scdaemon also would seem to indicate it’s running as my user.

Questions:

  • Did you reboot after making these changes?
  • Have you made any configuration changes related to CCID for gpg-agent/scdaemon?
  • Have you ensured you don’t have pcsc* stuff running?

EDIT:

╭ zeph  /dev/bus/usb  9ms ✘1
╰─▶ getfacl 003/005
# file: 003/005
# owner: root
# group: root
user::rw-
user:cole:rw-
group::rw-
mask::rw-
other::r--

EDIT2:

╭ zeph  /nix/store/b5cjmb11lgnkxhlf06j0bq5i6xbrgwsf-system-path/lib/udev/rules.d  7ms
╰─▶ cd /etc/udev/rules.d/

╭ zeph  /nix/store/vka7m7qmsn8x6rbl06y6l7v9lyz0j9ds-udev-rules  0sec
╰─▶ rg -L "ID_SMARTCARD_READER"
60-scdaemon.rules
8:SUBSYSTEM=="usb", ATTR{idVendor}=="046a", ATTR{idProduct}=="0005", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
9:SUBSYSTEM=="usb", ATTR{idVendor}=="046a", ATTR{idProduct}=="0010", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
10:SUBSYSTEM=="usb", ATTR{idVendor}=="046a", ATTR{idProduct}=="003e", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
12:SUBSYSTEM=="usb", ATTR{idVendor}=="04e6", ATTR{idProduct}=="5111", ENV{ID_SMARTCARD_READER}="1", ENV{ID_SMARTCARD_READER_DRIVER}="gnupg"
# many similar lines omitted

╭ zeph  /nix/store/b5cjmb11lgnkxhlf06j0bq5i6xbrgwsf-system-path/lib/udev/rules.d  11ms ✘2
╰─▶ rg -L "ID_SMARTCARD_READER"
60-fido-id.rules
10:SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ENV{ID_USB_INTERFACES}=="*:0b????:*", ENV{ID_SMARTCARD_READER}="1"
12:ENV{ID_SMARTCARD_READER}=="1", TAG+="security-device"

99-systemd.rules
54:ENV{ID_SMARTCARD_READER}=="?*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="smartcard.target", ENV{SYSTEMD_USER_WANTS}+="smartcard.target"

70-uaccess.rules
47:ENV{ID_SMARTCARD_READER}=="?*", TAG+="uaccess"

So, it’s happening via udev/uaccess (which I believe is tied to logind and the current seat).

1 Like

Oh, interesting. I am seeing the same error if the yubikey was inserted during boot; replugging it makes the error disappear.

I’ll check if this is due to root ownership of something later, too, I never managed to figure out why it was breaking.

Those are very helpful details for comparison, thank you.

Enabling the gpg-agent and scdaemon debug/guru logs and your comment make me think those are the only two relevant components. I tried turning off pcscd, and you’re right, it doesn’t seem neccessary.

Concerning CCID alterations, I’ve seen some folks have ~/.gnupg/scademon.conf with disable-ccid, but I just have the following.

pcsc-shared

I think we’re both letting gpg-agent and scdaemon run as they do by default (as the user). But the USB device files differ.

getfacl /dev/bus/usb/002/002 
getfacl: Removing leading '/' from absolute path names
# file: dev/bus/usb/002/002
# owner: root
# group: wheel
user::rw-
group::rw-
other::r--

Mine worked because of my custom udev rule that sets the group to wheel. Yours has the user:cole:rw-.

I lack 60-fido-id.rules, 99-systemd.rules, and 70-uaccess.rules. I gather it’s that last udev rule that matches devices with ID_SMARTCARD_READER and allows the current user to access the device without being elevated. Any idea what package that comes from or it’s custom? I bet that’s how I was supposed to do this!

lrwxrwxrwx 1 root root  91 Jan  1  1970 70-uaccess.rules -> /nix/store/1zmmnm0r0bdga398rl7fc7s4hkyqxjk4-systemd-254.3/lib/udev/rules.d/70-uaccess.rules

Just seems like systemd?

On NixOS 23.05 minimal, adding the systemd package doesn’t bring in that file into /etc/udev/rules.d for me. But also none of the files in /etc/udev/rules.d are symlinks to the Nix Store so that doesn’t match. But that seems like a separate issue, I don’t want to pull you into my rabbit hole.

So it seems like there are several ways udev rules can handle non-privileged access to Yubikeys, thanks for your help!

Just one bit of clarification, I didn’t show the other cd command I ran.

The 70-uaccess.rules was from /run/current-system/sw/lib/udev/rules.d. So another spot to check.

Ah, I see the same set of udev rules as you in /run/current-system/sw/lib/udev/rules.d and they’re indeed symlinks to the Nix Store.

70-uaccess.rules
47:ENV{ID_SMARTCARD_READER}=="?*", TAG+="uaccess"
ls -al 70-uaccess.rules 
lrwxrwxrwx 1 root root 91 Jan  1  1970 70-uaccess.rules -> /nix/store/9gzw98jc64qkwd17a6qqm63w25zysi57-systemd-253.6/lib/udev/rules.d/70-uaccess.rules

I need to learn more about how NixOS manages files there, where the /etc/udev/rules.d come from, and the priority order / if they’re active.

Hum, a bit of a necro, but I just figured out what was causing this on my end. Apparently since I use sops-nix with a gpg key, an scdaemon instance is started on boot which hogs the yubikey, even though I do not use it to decrypt my sops secrets.

I’ve not yet found a clean way to disable this instance, but that seems to be the root cause in my case.

Edit:

Decided to go for the KISS solution since I couldn’t find anything resembling a way to configure gpg for sops:

  # sops-nix will launch an scdaemon instance on boot, which will stay
  # alive and prevent the yubikey from working with any users that log
  # in later.
  systemd.services.shutdownSopsGpg = {
    path = [pkgs.gnupg];
    script = ''
      gpgconf --homedir /var/lib/sops --kill gpg-agent
    '';
    wantedBy = ["multi-user.target"];
  };
1 Like