jorgelbg / pinentry-touchid

Custom GPG pinentry program for macOS that allows using Touch ID for fetching the password from the macOS keychain.
Apache License 2.0
521 stars 23 forks source link

Pinentry Serve returned error: EOF #28

Open stffndtz opened 2 years ago

stffndtz commented 2 years ago

Describe the bug

When running echo "GETPIN" | pinentry-touchid I get the following response

OK Hi from pinentry-touchid!
OK
2022/06/06 10:54:16 Pinentry Serve returned error: EOF

pinentry-mac runs as expected - I have tried to unsinstall & re-install pinentry-mac & pinentry-touchid with no sucess at this point. I have also been looking for similar issues but haven't found a solution as of yet.

System information

macOS

GPG

gpg (GnuPG) 2.3.6
libgcrypt 1.10.1
Copyright (C) 2021 Free Software Foundation, Inc.
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /Users/<USER>/.gnupg
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256, TWOFISH,
        CAMELLIA128, CAMELLIA192, CAMELLIA256
AEAD: EAX, OCB
Hash: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
Compression: Uncompressed, ZIP, ZLIB, BZIP2 

Yes.

Configuration

gpg:OpenPGP:/usr/local/Cellar/gnupg/2.3.6/bin/gpg
gpgsm:S/MIME:/usr/local/Cellar/gnupg/2.3.6/bin/gpgsm
keyboxd:Public Keys:/usr/local/Cellar/gnupg/2.3.6/libexec/keyboxd
gpg-agent:Private Keys:/usr/local/Cellar/gnupg/2.3.6/bin/gpg-agent
scdaemon:Smartcards:/usr/local/Cellar/gnupg/2.3.6/libexec/scdaemon
dirmngr:Network:/usr/local/Cellar/gnupg/2.3.6/bin/dirmngr
pinentry:Passphrase Entry:/usr/local/opt/pinentry/bin/pinentry
cat /Users/<USERNAME>/.gnupg/gpg-agent.conf
debug-level basic
log-file /Users/<USERNAME>.gnupg/gpg-agent.log
pinentry-program /usr/local/opt/pinentry-touchid/bin/pinentry-touchid

Logs

gpg-agent:

2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 -> OK Pleased to meet you, process 36805
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 <- RESET
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 -> OK
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 <- OPTION ttyname=/dev/ttys004
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 -> OK
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 <- OPTION ttytype=xterm-256color
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 -> OK
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 <- OPTION lc-ctype=UTF-8
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 -> OK
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 <- reloadagent
2022-06-06 10:53:55 gpg-agent[36329] SIGHUP received - re-reading configuration and flushing cache
2022-06-06 10:53:55 gpg-agent[36329] reading options from '/Users/<USERNAME>/.gnupg/gpg-agent.conf'
2022-06-06 10:53:55 gpg-agent[36329] enabled debug flags: ipc
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 -> OK
2022-06-06 10:53:55 gpg-agent[36329] DBG: chan_8 <- [eof]

pinentry-touchid:

2022/06/06 10:49:49 main.go:105: Ready!
2022/06/06 10:49:49 main.go:211: Confirm was called!
2022/06/06 10:49:50 main.go:105: Ready!
2022/06/06 10:49:50 main.go:211: Confirm was called!
2022/06/06 10:50:15 main.go:105: Ready!
2022/06/06 10:51:10 main.go:105: Ready!
2022/06/06 10:51:45 main.go:105: Ready!
2022/06/06 10:54:16 main.go:105: Ready!
2022/06/06 10:57:46 main.go:105: Ready!
2022/06/06 10:57:59 main.go:105: Ready!
jorgelbg commented 2 years ago

@stffndtz can you give it a try to the latest release (v0.0.3) and check if it fixes the issue?

stffndtz commented 2 years ago

@jorgelbg It seems I am still getting the same error after the update:

echo "GETPIN" | pinentry-touchid
OK Hi from pinentry-touchid!
D hello
OK
Pinentry Serve returned error: EOF

Post update (via brew) I ran

/usr/local/opt/pinentry-touchid/bin/pinentry-touchid -fix && gpg-connect-agent reloadagent /bye

Let me know if I can help!

Myridium commented 2 years ago

I have the same issue. I can use pinentry-touchid for gpg-agent, but I cannot use it generally for other programs.

$ echo GETPIN | pinentry-touchid
OK Hi from pinentry-touchid!
D abc
OK
Pinentry Serve returned error: EOF
Myridium commented 2 years ago

Could this be the culprit?

https://github.com/jorgelbg/pinentry-touchid/blob/0235243df4d2795475cc2067b89a0f69f1e6b798/main.go#L428-L431

Note the response from pinentry-mac is not Hi from pinentry-mac!. In fact it is Pleased to meet you. Viz.

$ echo GETPIN | pinentry-mac
OK Pleased to meet you
D abc
OK

So maybe line 428 should be changed to:

if err := pinentry.Serve(callbacks, "Pleased to meet you"); err != nil { 

I am just guessing.

jorgelbg commented 2 years ago

Just to be clear, if you are only checking directly with:

$ echo GETPIN | pinentry-touchid

This is rightfully showing the pinentry-mac GUI. This happens because you haven't followed the full assuan protocol to talk to pinentry. Yes, GETPIN is a valid assuan command that pinentry-touchid understands but since no information about your GPG key has been provided, it assumes that there is no item in the keychain and asks you to provide one (as a fallback mechanism).

You need to set pinentry-touchid as your pinentry-program in ~/.gnupg/gpg-agent.conf by adding the following line:

pinentry-program /usr/local/bin/pinentry-touchid

and also reload your gpg-agent:

$ gpg-connect-agent reloadagent /bye

For checking that it is working you can use:

$ echo 1234 | gpg -as -

You can also interact directly via stdin with pinentry-touchid, but you need to do the full dance of providing the potions that the gpg-agent usually sends to any pinentry program.

Myridium commented 2 years ago

Just to be clear, if you are only checking directly with:

$ echo GETPIN | pinentry-touchid

This is rightfully showing the pinentry-mac GUI. This happens because you haven't followed the full assuan protocol to talk to pinentry. Yes, GETPIN is a valid assuan command that pinentry-touchid understands but since no information about your GPG key has been provided, it assumes that there is no item in the keychain and asks you to provide one (as a fallback mechanism).

You need to set pinentry-touchid as your pinentry-program in ~/.gnupg/gpg-agent.conf by adding the following line:

pinentry-program /usr/local/bin/pinentry-touchid

and also reload your gpg-agent:

$ gpg-connect-agent reloadagent /bye

For checking that it is working you can use:

$ echo 1234 | gpg -as -

You can also interact directly via stdin with pinentry-touchid, but you need to do the full dance of providing the potions that the gpg-agent usually sends to any pinentry program.

This comment doesn't appear to have any relation to this bug report.

jorgelbg commented 2 years ago

What I meant is that just sending GETPIN to pinentry-touchid is not enough and it does not considered a valid assuan session, the gpg-agent (and I would expect the same from the program that is trying to use it) should do the valid session.

In this case EOF is just a report of an early disconnection. That being said it is true that pinentry-mac does not consider this early termination as an unexpected error. I've adjusted the behaviour of the the error check in 82cc231b007b822b4eb11a9d8ba2e773aaae063c and also set the default prompt in case none is passed, which provides a more similar experience to what pinentry-mac does. Can you give it a try?

Myridium commented 2 years ago

What I meant is that just sending GETPIN to pinentry-touchid is not enough and it does not considered a valid assuan session, the gpg-agent (and I would expect the same from the program that is trying to use it) should do the valid session.

In this case EOF is just a report of an early disconnection. That being said it is true that pinentry-mac does not consider this early termination as an unexpected error. I've adjusted the behaviour of the the error check in 82cc231 and also set the default prompt in case none is passed, which provides a more similar experience to what pinentry-mac does. Can you give it a try?

Thanks, I'll give it a try soon.

To clarify, the problem (for me at least) is that echo GETPIN | pinentry-touchid is a perfectly valid way to use a pinentry program, however it returns an error in this case even though a PIN can be fetched from the user. So, the program succeeds in collecting a PIN, but it prints an error message and, more importantly, returns a non-zero exit code.

In my case, I am attempting to use software called passage which in return uses software called rage which calls pinentry to ask for a PIN. I symlinked pinentry to pinentry-touchid because I want to use it for everything. It fails however. I assume what is happening is that pinentry-touchid falls back to pinentry-mac but doesn't return the exit code from the latter. And it also prints that error.

Myridium commented 2 years ago

I don't know anything about Assuan commands, but the way that rage uses pinentry triggers this error, so I assume that something about pinentry-touchid is not conforming to spec here. I'll try the fix soon.

Myridium commented 2 years ago

Sorry, I don't use go and don't know how to compile this project, so I can't try your fix. If you add a Makefile for dummies, I can make.

jorgelbg commented 2 years ago

I've added a step to the CI that should build & upload the binaries. You could grab the built binaries from the summary page of the workflow. For instance for the latest commit: https://github.com/jorgelbg/pinentry-touchid/actions/runs/2831484675 (at the bottom).

jorgelbg commented 2 years ago

I had a chance to take a look under the hood at what rage sends to the pinentry program and I think that pinentry-touchid will keep falling back always to pinentry-mac.

When used with PGP the gpg-agent sends some identifying metadata about the key that is requesting the passphrase. This metadata is used by pinentry-touchid to tag the passphrase when it is stored in the keychain, since it is possible for a user to have multiple keys. Since this metadata it is not provided at all by rage pinentry-touchid cannot identify (nor currently store) an item in the keychain and falls back to pinentry-mac for the user to provide a password.

One way to go is to use a default label/name for tagging the keychain item. The downside of this is that you wouldn't be able to have more than 1 keys stored in the keychain (for rage) because there wouldn't be any way of distinguishing them apart.

Anyhow, this is my interpretation of what I've seen on the interaction between rage and whatever pinentry program is configured.

Myridium commented 2 years ago

Excellent, thanks for that clear explanation. I had wondered how pinentry-touchid knows which secret is being requested. That explains it.

The gpg-agent must pass an identifier string to the Assuan GETPIN instruction, or use a different instruction. Is it possible for pinentry-touchid to support caching of secrets with arbitrary identifiers/tags? Could you give an example of a shell command which does this, like echo GETPIN MY_ID | pinentry-touchid?

This would not help with rage specifically, but it would allow other programs to easily use pinentry-touchid. This may pose an additional security risk, if programs supplied predictable (guessable) identifiers. This could be somewhat reduced by having the pinentry-touchid program display clearly which program is requesting which secret (process name, tag/identifier requested).

jorgelbg commented 2 years ago

The majority of the information is passed via the SETDESC command. The pinentry program is expected to extract the metadata from the description. For instance: https://github.com/jorgelbg/pinentry-touchid/blob/1170eb6bc7b23313aee622887b47b77be6e5fb5f/main.go#L52-L53

If the description contains any of the info that matches those regexes (plus the key ID) it will be used to label the keychain item.

If rage passes down some sort of key id or anything else that could be used to label and retrieve the password, pinentry-touchid should work similarly to how it works with the gpg-agent. This could be also shared via the description that rage sets when calling the pinentry program.

This would not help with rage specifically, but it would allow other programs to easily use pinentry-touchid. This may pose an additional security risk, if programs supplied predictable (guessable) identifiers. This could be somewhat reduced by having the pinentry-touchid program display clearly which program is requesting which secret (process name, tag/identifier requested).

The info passed down to pinentry-touchid is essentially provided with the intention of helping the user identify which passphrase is needed (or to automatically fetch it from the keychain) so it shouldn't be insecure by default. In the gpg-agent case, the private key is never shared. In any case, is responsibility of the caller that sends the commands to pinentry.

Having said that, I'm not familiar enough with age/rage to say which info could potentially be shared with pinentry-touchid 😅.