tillitis / tkey-ssh-agent

SSH Agent for TKey, the flexible open hardware/software USB security key 🔑
https://www.tillitis.se
BSD 2-Clause "Simplified" License
131 stars 15 forks source link

Linux: Document pinentry and Desktop Notifications #118

Open mchack-work opened 7 months ago

mchack-work commented 7 months ago

We need to document the assumptions behind running tkey-ssh-agent, and especially pinentry, on Linux systems better. The assumptions are:

A working graphical pinentry typically means something like pinentry-gnome3 or pinentry-qt on most systems.

People who use GNOME or KDE or another full desktop are probably all set. People who use more lightweight systems with a minimal wm or a minimal Wayland compositor need to set things up themselves.

pinentry-gnome3 and Desktop Notifications typically also have dependencies on a working dbus session. If pinentry-gnome3 can't connect to the dbus session you get the dreaded:

No Gcr System Prompter available, falling back to curses

It wants to speak to gcr-prompter, which listens on the dbus.

When running, for instance, Sway or River this means you have to do something like

exec dbus-update-activation-environment --systemd WAYLAND_DISPLAY

in your config to get both pinentry-gnome3 and screen sharing working.

Note well: The agent tries to look in the user's gpg-agent.conf for what they want to use as a pinentry and tries to use that. This can be really confusing if they have something there that's old and isn't even installed on their system. Perhaps remove that functionality altogether? If they really want control they can set it with the --pinentry flag.

To check if you have a working graphical pinentry, first install something like pinentry-gnome3, then:

mcbacon4:~ % pinentry-gnome3 
OK Pleased to meet you
GETPIN

You should get a graphical window asking for something. I typed in "foo" and hit return.

D foo
OK

Theoretically something other than pinentry-gnome3 or pinentry-qt should work fine, but I haven't tested for instance pinentry-gtk2. Sadly, the minimalist pinentry-bemenu doesn't work. However, pinentry-emacs does! See the different implementations here:

git://git.gnupg.org/pinentry.git

Note well: If you just want to run the agent in the foreground and want it to ask for the USS in the same terminal, just call it with pinentry-curses:

% tkey-ssh-agent -p --uss --pinentry pinentry-curses
niels-moller commented 7 months ago

Ah, there are two usecases: When the agent is started as a service, it seems a bit tricky to ask the user for a secret. Requiring some kind of graphical environment might be reasonable, even though I don't get how the daemon will connect to it. Another option might be to require an explicit Unlock call (ssh-add -X) to the agent before first use.

In my case, I'm starting the agent from a terminal, and I was trying tkey-ssh-agent -p --uss (to check that I do get a different pubkey with a uss, as expected). In this case, the agent never daemonizes. And it would make sense to me to have tkey-ssh-agent --uss always ask for the user secret before it daemonizes and detaches from the terminal.

I'm also a little confused because when I start it with tkey-ssh-agent -a (to make it a "daemon" that serves requests), it stays running in the foreground, and it is terminated on ^C, so I don't see how it detaches from the terminal, and I also see nothing like that after a quick look in the source.

mchack-work commented 7 months ago

The agent talks to the pinentry program with the Assuan protocol:

https://www.gnupg.org/documentation/manuals/assuan/

I believe it simply starts a child program (pinentry) and talks the Assuan protocol on its stdout/stdin.

On many Linux distributions this pinentry program is itself a script and tries to start yet another program.

I can't reproduce your exact problem on my Arch system if I remove my gpg-agent config. /usr/bin/pinentry (a script) is started, then it starts pinentry-curses in the terminal running tkey-ssh-agent in the foreground. I'll try on a Debian system later.

tkey-ssh-agent never daemonizes itself. It uses systemd or similar things to do that, like most modern services.

Since the USS is entered every time you insert a new TKey it doesn't make much sense to ask for USS before daemonizing. You need to enter USS again and again every time you insert a TKey and try to load a device app, so you will have to have some way of entering it even after daemonizing.

niels-moller commented 7 months ago

Ok, then it's not just me that finds the errors about not a tty puzzling. For reference, these were the errors I saw:

$ tkey-ssh-agent -p --uss
Auto-detected serial port /dev/ttyACM0
Connecting to TKey on serial port /dev/ttyACM0
TKey is in firmware mode.
Notify message "Could not show USS prompt: pinentry: unexpected response: \"S ERROR curses.isatty 83918950 \"" failed: beeep: The name org.freedesktop.Notifications was not provided by any .service files; exec: "kdialog": executable file not found in $PATH
Failed to load app: Failed to get USS: pinentry GetPin: pinentry: unexpected response: "S ERROR curses.isatty 83918950 "
Connect failed

My "pinentry" program is a symlink (via /etc/alternatives) to pinentry-curses, debian package version 1.2.1-1. As far as I understand, it ought to work to spawn that program and talk the "assuan" protocol to it. The line "S ERROR..." looks like it's almost an assuan protocol response, but from the docs an error response should be "ERR ...", not "S ERROR ...". And the actual error is odd. Maybe the pinentry program I have is just broken, or implementing some older (undocumented?) variant of assuan.

I wasn't thinking of the case with a long lived tkey-ssh-agent, while user removes and inserts tkeys. I take it there are many different use cases, but for me, one reasonable behavior would be that if tkey-ssh-agent is configured to pass a user secret when loading the device app, then a newly inserted tkey (in provision mode) could be considered locked, and requiring unlocking (ssh-add -X). When tkey-ssh-agent gets the unlock request, it can use the provided password as the uss and load the device app. But I haven't looked up in detail how that request works in the ssh-agent protocol.

niels-moller commented 7 months ago

To get some more details of what's going on, I'm trying this crude hack, pinentry-bug.sh:

#! /bin/bash

exec 2> pinentry.err

coproc PINENTRY { pinentry-curses; }

echo 0: ${PINENTRY[0]} 1: ${PINENTRY[1]} >&2

# Assume pinentry starts ("OK Pleased to meet you")
# followed by single-line req and response.
while read -r -u ${PINENTRY[0]} resp ; do
    echo resp: "${resp}" >&2
    echo "${resp}"
    read -r req || break
    echo req: "${req}" >&2
    echo ${req} >& ${PINENTRY[1]}
done

# Read any queued up responses
while read -r -t + -u ${PINENTRY[0]} resp ; do
    echo resp: "${resp}" >&2
    echo "${resp}"
done

Running tkey-ssh-agent -p --uss --pinentry ./pinentry-bug.sh then logs this conversation in pinentry.err:

0: 63 1: 60
resp: OK Pleased to meet you
req: SETDESC tkey-ssh-agent needs a User Supplied Secret%0A(USS) for your TKey with number:%0A01337:2:1:0000011b
resp: OK
req: SETPROMPT User Supplied Secret
resp: OK
req: SETTITLE tkey-ssh-agent
resp: OK
req: GETPIN
resp: S ERROR curses.isatty 83918950
req: BYE
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
resp: ERR 83918950 Inappropriate ioctl for device <Pinentry>
./pinentry-bug.sh: line 20: read: resp: invalid file descriptor specification

Correction: The " -t +" arg to one of the read calls above was a typo, intended "-t 0".

niels-moller commented 7 months ago

And it seems that the problem is that pinentry-curses by default tries to use stdin as the terminal to display its curses ui, which is a bit silly since that's likely going to be a pipe to the program that needs the pin. Maybe the pinentry script you mentioned works around that?

Using this script as the pinentry program actually works:

#! /bin/bash
exec pinentry-curses -T /dev/tty

(in my use case, where the tkey-ssh-agent and its child processes do have a controlling tty).

Annoying, and not clear to me what tkey-ssh-agent can do about that, besides documenting more of the pinentry details, and have the error message point to those docs.

dehanj commented 7 months ago

Great investigation @niels-moller, thanks!

I reproduced the issue you have. I get the same error when running an Ubuntu VM, also verified the script you wrote works on that very same VM. What is interesting is that pinentry seems to behave a bit different on different platforms. When running macos and using "pinentry", which points to the same homepage as pinentry-curses on Ubuntu (https://www.gnupg.org/related_software/pinentry/) it works fine an provides a pinentry dialog in the same terminal as tkey-ssh-agent -p --uss was called from.

mchack-work commented 7 months ago

I have updated the original issue text to what we want to do, that is update the documentation about our assumptions, and maybe remove the thing with gpg-agent.conf.

mchack-work commented 7 months ago

I noted that the minimalist pinentry-bemenu doesn't work because it doesn't handle the SETTITLE command. Maybe we can drop that? Using --pinentry pinentry-bemenu works fine if I comment out the setting of the title.

Users of straight WM's and compositors without desktop environments then doesn't need neither pinentry-gnome3, dbus, or gcr-prompter.

Also, let me quote the code:

    // Title is not displayed by all pinentry programs (or
    // displayed obscurely in window title).
    pinentry.WithTitle(progname),

It's likely that the corresponding X11 program pinentry-dmenu also works, but I don't know if that grabs the X server, so might be insecure.

quite commented 7 months ago

I noted that the minimalist pinentry-bemenu doesn't work because it doesn't handle the SETTITLE command. Maybe we can drop that? Using --pinentry pinentry-bemenu works fine if I comment out the setting of the title.

I think that a pinentry server implementation should be fixed to handle common commands (possibly doing nothing, if it doesn't make sense). Or the pinentry client should accept an error result from a command which is known to not be broadly implemented (if it isn't something very important).

Now it happens that pinentry-bemenu 0.13.0 does support SETTITLE, perhaps you are using an older version?

mchack-work commented 7 months ago

Nice! Thank you. Yes, on 0.12.0 here.