gpg-agent loses characters when prompting for a GPG passphrase over SSH in Emacs
Closed, ResolvedPublic

Description

gpg-agent does not properly prompt for a passphrase within Emacs over an SSH connection. Some characters in the passphrase are missed by gpg-agent and may actually be inserted into the current Emacs buffer.

Software versions:

  • Linux: Kubuntu 18.04.2
  • Emacs: GNU Emacs 25.2.2
  • SSH: OpenSSH_7.6p1 Ubuntu-4ubuntu0.3, OpenSSL 1.0.2n 7 Dec 2017
  • gnupg: gpg (GnuPG) 2.2.4, libgcrypt 1.8.1

General symptoms:

The ASCII gpg-agent prompt appears for your passphrase. As you type the passphrase, some keystrokes are not echoed (as asterisks ***) as you type. These characters are not being received by gpg-agent.

To reproduce:

  1. Create a file and encrypt it:
$ cd $HOME
$ echo foo > bar
$ gpg -e bar
  1. Demonstrate that you can decrypt this file in Emacs with EasyPG using the ASCII/curses prompt. This should succeed.
$ DISPLAY=
$ emacs bar.gpg
  (gpg-agent prompts for the GnuPG passphase in a pop-up window)
  (exit emacs)
$ rm bar
  1. Assuming everything above worked correctly, now try the same thing over an SSH connection:
$ ssh localhost
$ emacs bar.gpg

gpg-agent again prompts for the passphrase using an ASCII/curses prompt. However, as you type the passphrase, asterisks do not always appear for each keystroke, only for some of the keystrokes. Authentication fails when you press Enter. (Or the Enter keystroke might not be received by gpg-agent either.)

  1. Try the previous test again, but this time, while typing the passphrase, press each key repeatedly until one asterisk appears. For example, if your passphrase is ABCDE, keep pressing "A" until one asterisk appears, then press "B" until the next asterisk appears, etc., until there are 5 asterisks. Authentication will succeed! However, all the extra letters get inserted into the Emacs buffer for bar.gpg. This test suggests that the characters are being misdirected on their way to gpg-agent.

My ~/.gnupg/gpg-agent file:

default-cache-ttl 1
max-cache-ttl 1

The GPG-related content in my .bashrc file:

GPG_TTY=$(tty)
export GPG_TTY

Details

Version
2.2.4

Oh, in case it wasn't clear, the idea that another application (GNU emacs) is receiving keystrokes meant for the gpg-agent prompt is probably a security risk....

I also tried adding this to my gpg-agent.conf file:

pinentry-program /usr/bin/pinentry-tty

The problem in emacs got even worse. A gpg-agent prompt was displayed, but none of my keystrokes seemed to have any effect. After several ctrl-G's, I managed to exit Emacs, but my shell window was now practically hung. I had to close the window (not just the shell).

werner edited projects, added pinentry; removed gpgagent.May 28 2019, 4:30 PM
werner added a subscriber: werner.

Which pinentry are you using in in what mode? Please do a sign operation and watch out for a line similar to:

gpg: pinentry launched (<info from pinentry>)

Thank you, werner. Could you please tell me an exact GPG command to do this signing, and tell me where the output line should appear? I tried this command on the command line:

$ gpg --encrypt --sign myfile

but the only messages were:

gpg: using "..." as default secret key for signing

I also tried M-x epa-sign-file in Emacs but didn't see any messages either.

Sorry, I forgot to mention it. You need to add -v to the command line.

Ah, I added the --verbose option and got this output (sanitized by me):

$ gpg --encrypt --sign --verbose myfile
gpg: using classic trust model
gpg: using "..." as default secret key for signing
gpg: using subkey xxxxxxx instead of primary key zzzzzzzzzzzz
gpg: writing to 'myfile.gpg'
gpg: ELG/AES encrypted for: "<keyname>"
gpg: pinentry launched (22751 qt:curses 1.1.0 /dev/pts/7 xterm-256color -)
gpg: DSA/RIPEMD160 signature from: "<keyname>"
maiden_taiwan added a comment.EditedMay 28 2019, 5:21 PM

I should add that using gpg on the command line works fine over SSH. The problem occurs only inside Emacs over SSH.

Please let me know if I can run any other tests to help debug this issue. I'm happy to help.

gniibe claimed this task.Jun 3 2019, 1:55 AM
gniibe triaged this task as Normal priority.
gniibe added a subscriber: gniibe.

Thanks for your report. The symptom you have could be only solved by using pinentry loopback mode, or using some special pinentry for CLI, I suppose. pinentry-tty is not sufficient for this usage.

You can the see same problem even when you use Emacs on your local machine; The problem is that, when pinentry asks passphrase, there is a process running simultaneously/asynchronously accessing tty (in your case, it's Emacs) .

In normal mode (not loopback mode), it is gpg-agent (on your local machine), which invokes pinentry. Only when you run your gpg synchronously (= waiting its finish of execution), it works well (no matter if it's remote or local use of gpg), by gpg-agent's accepting commands, because it is only the pinentry process which tries to get text from tty.

By using loopback mode, it is your gpg process which asks your passphrase. When needed, gpg-agent asks back to a client about passphrase, not invoking pinentry.

Or, if you are a user of tmux or GNU screen, you can write your own special pinentry program, so that the pinentry uses a dedicated screen (by switching foreground screen back and forth). This can avoid using same tty by pinentry and another process.

Thank you for that analysis. I don't understand some of the parts (because I don't know anything about pinentry), but I do have some questions.

  1. If Emacs and GnuPG integration is broken now, where should the fix be made? Whose bug is it?
  1. Is there a way to configure Emacs/GnuPG to prompt for the GPG passphrase in the Emacs minibuffer, and then send the passphrase to GPG? This is the way Emacs EasyPG commands worked before I upgraded to Emacs 25.2 and gpg-agent.

Thank you.

gniibe added a comment.EditedJun 3 2019, 2:59 AM

For (1): it is broken out-of-the-box, that would be true. When you can configure it properly, there is a way to workaround it. Well, I admit, it's not yet perfect.

For (2), partial answer is: For your use of local Emacs, you can use: https://elpa.gnu.org/packages/pinentry.html

maiden_taiwan added a comment.EditedJun 3 2019, 5:28 PM

When you can configure it properly, there is a way to workaround it.

Thank you. Where can I find step-by-step instructions for this workaround? I have never even heard of gpg-agent and pinentry until last week, so I am not an expert.

For your use of local Emacs, you can use: https://elpa.gnu.org/packages/pinentry.html

Thanks -- I tried this but cannot make it work. M-x pinentry-start runs without error and creates the socket /tmp/emacs1000/pinentry, but when I load a .gpg file into Emacs with find-file, I still get the graphical passphrase pop-up (pinentry-qt). My ~/.gnupg/gpg-agent.conf file contains:

default-cache-ttl 1
max-cache-ttl 1
allow-emacs-pinentry

and I ran gpgconf --kill gpg-agent. I also tried with several different pinentry-program lines, which changed the pinentry program but still never prompted from the Emacs minibuffer.

Any suggestions?

Sorry, I responded in a mode of "tracking a bug to fix soonish". I should have changed my mode into showing HOWTO.
Thanks for sharing useful link.

maiden_taiwan closed this task as Resolved.Jun 4 2019, 2:34 AM

No worries -- you led me in the direction of a solution when you mentioned loopback mode. I appreciate your time and your help!