SSH Security Keys (Yubikey) with GPG-Agent

I recently migrated from Arch Linux to NixOS, but I have a problem since my migration I can’t use my Yubikey with ssh. I get an error when I do ssh-add <path of ssh key of type sk> :

Could not add identity “toto”: agent refused operation

Whereas I can use it when I do ssh-keygen to create a key of type sk.

Here’s my configuration.nix :

...
  services.udev.packages = [ pkgs.yubikey-personalization ];
  services.pcscd.enable = true;
  hardware.gpgSmartcards.enable = true;

  programs.gnupg.agent = {
    enable = true;
    enableSSHSupport = true;
  };
...

Here’s my home.nix

...
  programs.ssh = {
    enable = true;
    extraConfig = ''
      IdentitiesOnly yes
      IdentityAgent none
    '';
  };

  services.gpg-agent = {
    enable = true;
    enableSshSupport = true;
    enableExtraSocket = true;
    enableScDaemon = true;
    defaultCacheTtl = 60 * 60 * 6;
    defaultCacheTtlSsh = 60 * 60 * 6;
  };
1 Like

I spent a couple weeks a long while ago trying to get gpg-agent, pinentry, and ssh working together; I didn’t have any luck :⁠‍⁠/

1 Like

You can check if the gpg agent is correctly set for ssh:

❯ echo $SSH_AUTH_SOCK
/run/user/1000/gnupg/S.gpg-agent.ssh

This is what I have in my configuration.nix:

programs.gnupg.agent = {
  enable = true;
  enableSSHSupport = true;
};

environment.shellInit = ''
  export GPG_TTY="$(tty)"
  gpg-connect-agent /bye
  export SSH_AUTH_SOCK="/run/user/$UID/gnupg/S.gpg-agent.ssh"
'';

I remember my team mates had an issue using Gnome, I can check with them if it’s still the case, just tell me.

Edit: if not already done you should also give ultimate trust to your key:

# import your GPG public key
gpg --import mykey.gpg

# if needed, find your key id
gpg -k --keyid-format long

# ultimate trust the key
gpg --edit-key 0xFFFFFFFFFFFFFFFF
> trust
> 5
> y
> save

# hopefully your key is there
ssh-add -L

I’ve tested your configuration, but it still doesn’t work, I get the same error

❯ echo $SSH_AUTH_SOCK
/run/user/1000/gnupg/S.gpg-agent.ssh
❯ ssh-add -L
ssh-rsa AA ... == cardno:20_694_419
❯ ssh-add toto
Enter passphrase for toto: 
Could not add identity "toto": agent refused operation

For what I understand ssh-add serves to add a ssh private key, but currently the private key is on the yubikey and should not be on your host. If ssh-add -L lists your key you should be able to ssh into hosts using it, did I miss something?

When I do ssh-add -L, it lists my Yubikey key and not my ssh key (ed25519-sk) which uses this Yubikey key. So no, I can’t connect to the host

Maybe we have different setups, I use the yubikey’s GPG auth subkey for SSH.

I don’t remember well, I did it a while ago following Dr Duh guide.

I’ve been banging my head against an issue with yubikey pgp ssh auth as well but only for TTY based pinentry (pinentry-tty and pinentry-curses) and only failing on zsh.

Could it be that you are on a zsh login shell?

I have this setup which works for bash login shell but fails for zsh. I tested on a fresh user.

from my configuration.nix (I added some comments to illustrate):

# maybe not relevant to mention, used for greetd
  users.users.greeter = {
    isNormalUser = false;
    group = "greeter";
    shell = pkgs.bash; # could be an issue but more for security not tty-messup? switch to nologin?
  };

  # defensive but shouldn't hurt
  environment.etc."ssh/ssh_config".text = ''
    Match host * exec "gpg-connect-agent UPDATESTARTUPTTY /bye"
  '';

 users.users.myuser = {
    isNormalUser = true;
    shell = pkgs.zsh;
  };

  programs.zsh = {
    enable = true;
    ohMyZsh = {
      enable = true;
      plugins = [
        "git"
        "z"
        "gpg-agent" # Just for refrence. Tested with and without doesn't change the outcome
      ];
      theme = "gnzh";
    };
  };

 programs.gnupg = {
    agent = {
      enable = true;
      enableSSHSupport = true;     # Enable SSH agent support in gpg-agent
      enableExtraSocket = true;    # Optional, disable extra sockets if not needed
      enableBrowserSocket = true; # Optional, disable browser socket
      pinentryPackage = pkgs.pinentry-curses; #works on bash, not on zsh
      settings = {
        default-cache-ttl = 300;
        max-cache-ttl = 7200;
        #log-level = "/tmp/gpg-agent.log";
        #debug-level = "advanced";
      };
    };
  };

 environment.variables = {
    # Set SSH_AUTH_SOCK to default gpg-agent SSH socket path
    # This is usually ~/.gnupg/S.gpg-agent.ssh or /run/user/UID/gnupg/S.gpg-agent.ssh
    # You can hardcode the path or infer via environment at runtime.
    SSH_AUTH_SOCK = "${builtins.getEnv "XDG_RUNTIME_DIR"}/gnupg/S.gpg-agent.ssh";
  };

  environment.shellInit = ''
    export GPG_TTY="$(tty)"
    gpg-connect-agent updatestartuptty /bye
    export SSH_AUTH_SOCK="/run/user/$UID/gnupg/S.gpg-agent.ssh"
  '';

Again, it works if I change from pinentry-curses to e.g. pinentry-qt OR if I am using pkgs.bash for my user.

the error, which is hard to debug further, is gpg-agent’s call to pinentry-curses. It is as if it isn’t finding the tty but I couldn’t get that level of confirmation. I even went so far as to compile a custom c wrapper that hardcodes the -T argument and feeds it into pinentry-curses. To no avail.

1 Like

Yes, I am indeed using zsh as my login shell.

That might explain it — thanks a lot for the detailed write-up!
If I can’t get it to work cleanly, I’ll just switch to a different pinentry (maybe pinentry-qt or pinentry-tty) as a workaround.

Thanks you :pray:

1 Like

Assuming on wayland you may like pinentry-rofi

cheers :grinning:

I put pinentry-rofi but I have the same problem

It is installed as a package and reacts correctly when you call it manually on your terminal?
Type “GETPIN” into stdin when it launches.

If that works but the gpg-agent still refuses then my wisdom ends here :slight_smile: try gtk …

And just for curiosity sake, what happens if you switch your login shell to bash? If that “fixes” it (not suggesting you keep using bash of course) maybe we can edit the title of this help thread to include “with zsh” or such.

Interesting I got this working fine on my setup with KDE and zsh as the default shell a little while ago and didn’t have a huge amount of trouble with it.

If your are using gpg agent and pcscd you need to disable the normal ssh agent as the gpg agent takes over that functionality. They don’t get on so you kind of have to pick one or the other.

programs.ssh.startAgent = false;

gpg agent doesn’t, to my knowledge, work with the ed25519-sk style keys, you need the ordinary ssh agent for that to work. I used to use these and I swapped to gpg so I could use the gpg agent for signing git commits with my yubikey and changed my ssh auth over to gpg so it would all play nice.

1 Like

Yes that setting I have too. I forgot to include it in the config above.

Running hyprland.

Hmm If I drop to a TTY with my desktop session still going I still get the qt pin prompt in the desktop.

Looks like if I change the pinentry package i’ll need to restart for it to have an effect as if I change to ‘curses’ / ‘tty’ / or ‘all’ variants I still just get the qt prompt after a rebuild switch. I’ll report back at some point if any of the tty options work for me after a reboot.

In the terminal, when I launch it manually and enter GETPIN, everything works correctly.

Unfortunately, even with GTK it doesn’t work


I’ve never set this to true and by default it’s set to false

However, I had managed to use it on Arch, if I’m not mistaken


I’ll test with bash as shell

Even with bash it doesn’t work, which suggests that it’s the gpg-agent and -sk keys that are the problem