11

I need to be able to GPG-sign git commits in two different Linux environments.

  1. In a full GUI X-Window setup using the GUI-based password dialog box

  2. In an SSH, without X-forwarding, using command-line only password entry

In both cases I need to be able to use different UID's for signing, all of which are in my keyring. The local and remote access use the same user account on a single machine, and may both be active at the same time. The computer is running openSUSE Leap 15 (4.12.14), using GnuPG 2.2.11, and libgcrypt 1.8.4. My typical remote connection uses ConnectBot v1.9.5 on Android v5.1.1, though I often also use Terminus v4.3.12 on iOS 12.1.1, and I could use a public Win 10 PC on occasion.

I do not want a 'permanent' solution which forces me to use the CLI method locally. Doing so would affect all other uses of gpg, including email.

Reading this question, I tried several variations which seemed to suggest themselves. Even though the solutions there, including the accepted one, were aimed at a permanent solution. I hoped they'd lead to a solution in my use case.

I have tried several variations of setting the pinentry-command in my gpg-agent.conf file. In all cases, the gpg-connect-agent reloadagent /bye command is executed after a change to the config file. The remote session was started fresh after any change and tested, as-is, and after each of the following commands, in sequence:

  1. export GPG_TTY=$(tty)
  2. unset DISPLAY
  3. gpg-connect-agent updatestartuptty /bye

The results have been the same before and after all three commands, and are as follows:

  • Using pinentry gives the GUI method locally - whether I am executing the command locally or remotely (The remote screen simply hangs until time-out happens for the dialog box.)
  • Using pinentry-tty forces CLI in all cases. (Remote usage prompts in the remote terminal and local usage prompts in the local terminal.)
  • Using pinentry-gtk-2 duplicates the results of pinentry
  • Using pinentry-qt also duplicates the results of pinentry
  • Using pinentry-curses provides the curses box, but otherwise duplicates the results of pinentry-tty

In all cases, if the setting results in a command-line prompt locally, all non CLI uses, such as Thunderbird, fail with an error.

I will not include the password on the command line or in a text file (no --passphrase-* option to GnuPG).

I will not use a password-less key.

It does no good to hard code any one key as I often use several within a short span of time.

I will use a command line option, if possible, to force the correct pinentry choice. I'm not sure it's possible to pass gpg options through git.

I would prefer the ability to have the pinentry automatically "pick" the proper option. I would also prefer that the command line pinentry work for all commands. At the moment, however, I only need it to work in connection with git usage. (Signing is often one key while push/pull operations require a different key.) Currently, my main remote SSH client can execute custom commands at login, so a script that affects the current session/tty only would also be acceptable, though not ideal. A per-connection modification of the gpg-agent.conf file (manually or scripted) would not be acceptable.

There was one hopeful answer for making this work on a mac. Attempting it with the above options, however, produced no different results. (I also tried ="USE_TTY=1", just in case it worked.)

I can work with suggestions in an effort to find a real solution (team-effort so to speak) if there are possibilities which might work but cannot be tested outside of my environment.


Current work-around

As a "fix" for my issue I've developed a work-around, which is sub-optimal, but does work. I've also made a few wrapper scripts to utilize the work-around in a semi-automated manner.

The work-around requires that I have two versions of gpg-agent.conf, in my case gpg-agent.local and gpg-agent.remote, which are different in only one line. The local version has pinentry-program /usr/bin/pinentry and the remote version has pinentry-program /usr/bin/pinentry-tty. gpg-agent.conf is a symlink to one of those, usually the local version. To switch to remote I execute:

rm ~/.gnupg/gpg-agent.conf
ln -s ~/.gnupg/gpg-agent.remote ~/.gnupg/gpg-agent.conf`
gpgconf --kill gpg-agent
gpg-connect-agent reloadagent /bye
gpg-connect-agent updatestartuptty /bye

Perhaps all three commands are overkill, but I've not been able to determine exactly which subset will guarantee the complete switch, and I know that all three always works.

Returning to the local version is the same process, with the local version replacing the remote version in the symlink.

The two things I do most while SSH'ed is git commit and push, so I have a wrapper script for both which will do the switch to remote conf, do the git operation, and switch the conf back to the local version so that it's ready for use from the local machine. Everything else requires that I remember to switch to remote before trying to use GPG, and switch back to local when done.

If I happen to forget the switch to remote, after a pause, I realize that nothing's happening because GPG is showing the GUI dialog on the host, and can CTRL+C out, do the switch and rerun the command. If I forget to return to the local version, using a command on the local machine gives me a passphrase prompt in the CLI and I can type the passphrase and then reset the config to local for the remainder of my session.

The reason I consider this work-around to be sub-optimal is that all of it is manually done. Scripts to make the switch, or wrappers around other commands which apply, and reset, the switch help, but are not handled by the system or configuration, automatically. My intervention is always required to make it happen, and to unwind what was done when I'm through, or when I switch terminal sessions.

The suggestion by user Michele to use export DBUS_SESSION_BUS_ADDRESS=/dev/null may make a big difference in what I have to do, but will still require that I make that command a manually executed command. I can add it to my current SSH client as a "first" command, but if/when I use a different client I will still have to remember to use that command, and remember what it is.

Perhaps with my work-around, and the idea from Michele, someone else might have the solution that "just works."

8
  • What you're asking for is already the default behavior. Can you confirm that gpg-connect-agent "getinfo std_session_env" /bye, immediately after updatestartuptty, does not have DISPLAY anymore? Jul 9, 2019 at 21:35
  • 2
    @grawity Somehow the "default" behavior isn't compliant in my system. The command given, after using unset DISPLAY and DISPLAY="" followed by updatestartuptty still lists DISPLAY=:0 as well as the correct GPG_TTY=/dev/pts/17. Using the local terminal to 'echo "testing" /dev/pts/17` causes the message to appear on the remote terminal. Perhaps the DISPLAY remains fixed because the local machine is still logged in, and my use case requires that to be an option.
    – Chindraba
    Jul 9, 2019 at 23:36
  • No it doesn't – gpg-agent does not care about "logins" of any shape. But you are saying that none of the environment variables get updated within gpg-agent (e.g. GPG_TTY in std_session_env is supposed to always correspond to the current tty), so DISPLAY is not special in your case. Jul 10, 2019 at 4:34
  • I should have specified that "immediately" also includes "within the same terminal window and within the same shell instance". Jul 10, 2019 at 4:35
  • 1
    @grawity The GPG_TTY does show the correct pts number for each shell, even before updatestartuptty, the DISPLAY remains locked, irrespective of which shell/session is calling gpg-connect-agent. Using the echo command from another shell confirmed that the reported pts was correct.
    – Chindraba
    Jul 10, 2019 at 14:51

3 Answers 3

4

Seems that (at least on my system) gpg actually uses DBUS to determine where to display the prompt. A workaround that makes it fallback on the ncurse pinentry prompt when on CLI is to set

export DBUS_SESSION_BUS_ADDRESS=/dev/null

or another invalid location. I set this in my .bashrc , and does what the OP asked for - ncurses prompt whenever on CLI, GUI whenever gpg gets called from a GUI process.

2
  • This is an interesting option, and might be useful, in a different manner. Though what I was looking for was the split between local and remote. In most cases the local usage is also in CLI as well, having the GUI dialog box pop up for the passphrase is acceptable, and handy. Having it pop up on the local machine when the command was issued from the SSH session is the problem. However, I'll experiment with the idea of using the export command as part of the SSH client's initial command option. That might solve, or work around, the issue.
    – Chindraba
    May 2, 2020 at 23:08
  • Well, never mind. Using that command still results in the same behavior. Attempting to use GPG from the SSH session (client) still places the GUI pinentry on my desktop (host). :(
    – Chindraba
    May 2, 2020 at 23:57
3

The root-cause of this issue is that gpg-agent (and pinentry) are shared between the GUI and ssh/serial/termminal users.

The most seamless solution is to create a pinentry-auto script, for example in ~/bin/

#!/bin/sh
# By defaulting to this alternative, we could place this script
# at /usr/bin/pinentry to avoid extra configuration elsewhere,
# however, it might be overwritten by upgrades.
pe=/etc/alternatives/pinentry
bin=/usr/bin
case "$PINENTRY_USER_DATA" in
*USE_TTY*)  pe=$bin/pinentry-tty  ;;
*USE_CURSES*)   pe=$bin/pinentry-curses ;;
*USE_GTK2*) pe=$bin/pinentry-gtk-2 ;;
*USE_GNOME3*)   pe=$bin/pinentry-gnome3 ;;
*USE_X11*)  pe=$bin/pinentry-x11 ;;
*USE_QT*)   pe=$bin/pinentry-qt ;;
esac
exec $pe "$@"

And reference in this script in gpg-agent.conf

pinentry-program bin/pinentry-auto

The environment variable PINENTRY_USER_DATA will be passed from the gpg client, to the agent and then to pinentry-program on each invocation of gpg, so you can use this in your user environment to control which pinentry should be used.

In your .bash_profile you can set the variable only for ssh logins:

if [ "$SSH_CLIENT" ]; then
   # I have logged in via SSH
   export PINENTRY_USER_DATA=USE_CURSES
fi
1
  • Oh how I wish I still had the setup I was using. This actually looks like the answer I needed all those years ago. I may even have to recreate the situation just to test this. Thx.
    – Chindraba
    Jan 8, 2023 at 16:53
0

As a workaround, one could use replacing/aliasing gpg2 with:

#!/bin/bash

if [ -n "$DISPLAY" ] ; then
  echo "gui |$DISPLAY|"
  /usr/bin/gpg2 $@
else
  echo "no gui"
  /usr/bin/gpg2 --pinentry-mode loopback $@
fi

but the annoying part is that the GUI pinentry is still displayed on the remote machine (with "remote" I mean the one I'm ssh-ing to), so it's invisible.

Enforcing pinentry-tty I still had the problem that I had no prompt in some cases (probably depending on how/where the agent was started) and it just failed. So for now I resort to adding to .gnupg/gpg.conf:

pinentry-mode loopback

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .