From 023b548ea1be286c3efe7f52e6999328f031e4cd Mon Sep 17 00:00:00 2001 From: Ron Shavit Date: Fri, 17 Jul 2026 14:50:11 +0300 Subject: [PATCH] gpg: use fuzzel for pinentry Signed-off-by: Ron Shavit --- .gnupg/gpg-agent.conf | 2 +- .local/bin/pinentry-fuzzel | 134 +++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) create mode 100755 .local/bin/pinentry-fuzzel diff --git a/.gnupg/gpg-agent.conf b/.gnupg/gpg-agent.conf index 87bfb9dc..6785796e 100644 --- a/.gnupg/gpg-agent.conf +++ b/.gnupg/gpg-agent.conf @@ -1,4 +1,4 @@ -pinentry-program /usr/bin/pinentry-gnome3 +pinentry-program ~/.local/bin/pinentry-fuzzel allow-preset-passphrase max-cache-ttl 60480000 default-cache-ttl 60480000 diff --git a/.local/bin/pinentry-fuzzel b/.local/bin/pinentry-fuzzel new file mode 100755 index 00000000..6f2a80c6 --- /dev/null +++ b/.local/bin/pinentry-fuzzel @@ -0,0 +1,134 @@ +#!/usr/bin/env fish + +# General information about the pinentry protocol: +# https://gnupg.org/ftp/manuals/0.1/pinentry.html +# https://www.gnupg.org/documentation/manuals/assuan/Assuan-Protocol.html +# +# Based on pinentry-dmenu: +# https://github.com/inco-cc/pinentry-dmenu/blob/master/pinentry-dmenu +# +# Optional debug output: PINENTRY_DEBUG=/path/to/log + +set -l ERR_CANCELLED 83886179 + +set -gx DBG_FILE $PINENTRY_DEBUG +if test -n "$DBG_FILE" + : >$DBG_FILE +end +function debug + test -n "$DBG_FILE"; or return 0 + printf '%s\n' $argv >>$DBG_FILE +end + +function pct_decode --argument-names s + echo $s | python3 -c "import sys, urllib.parse as ul; print(ul.unquote_plus(sys.stdin.read().strip()))" +end + +set -l KEY_NAME '' +set -l KEY_ID '' +set -l DESC_RAW '' + +# Send greeting. No options advertised; client sends its commands next. +echo 'OK Pleased to meet you' + +while read -l line + debug "< $line" + # pinentry/gpg-agent commands are case-insensitive; normalize for matching. + switch (string upper -- $line) + case '*BYE*' + break + + case '*RESET*' + set KEY_NAME '' + set KEY_ID '' + set DESC_RAW '' + echo OK + + case '*OPTION*' + # Accept all options silently. Common: ttyname, ttytype, display, + # lc-ctype, lc-messages, parent-window, touch-file. + echo OK + + case '*SETDESC*' + # SETDESC . gpg-agent sends: + # Please enter passphrase to unlock the OpenPGP secret key:%0A%22Key%22%0A%22User %22%0A + set -l parts (string split -m 1 ' ' -- $line) + set DESC_RAW $parts[2] + # Best-effort extract the quoted key name for the short prompt. + set -l after (string split -m 1 ':%0A%22' -- $DESC_RAW) + if test (count $after) -gt 1 + set KEY_NAME (string split -m 1 '%22%0A' -- $after[2])[1] + end + echo OK + + case '*SETKEYINFO*' + # SETKEYINFO . Used as short-prompt fallback. + set -l parts (string split -m 1 ' ' -- $line) + if test (count $parts) -gt 1; and test -n "$parts[2]" + set KEY_ID $parts[2] + end + echo OK + + # State-only setters we don't interpret; just acknowledge. + case '*SETTITLE*' '*SETPROMPT*' '*SETERROR*' '*SETOK*' '*SETCANCEL*' '*SETNOTOK*' '*SETDISPLAY*' '*SETTTYINFO*' '*SETTTY*' + echo OK + + case '*GETPIN*' + # Short prompt: prefer key name, fall back to key id, then generic. + if test -n "$KEY_NAME" + set prompt "GPG $KEY_NAME: " + else if test -n "$KEY_ID" + set prompt "GPG $KEY_ID: " + else + set prompt "GPG passphrase: " + end + + # Render the agent's description above the prompt via fuzzel --mesg. + set -l mesg_args + if test -n "$DESC_RAW" + set -l decoded (echo $DESC_RAW | python3 -c "import sys, urllib.parse as ul; print(ul.unquote_plus(sys.stdin.read().strip()))") + if test -n "$decoded" + set mesg_args --mesg "$decoded" + end + end + + set -l PASS_INPUT '' + set -l ok no + if set PASS_INPUT (fuzzel --dmenu --password --prompt-only "$prompt" --width 60 $mesg_args 2>/dev/null) + set ok yes + end + + if test "$ok" = yes; and test -n "$PASS_INPUT" + # Assuan D line cannot contain newlines; passphrases normally + # don't, but reject defensively rather than emit malformed data. + if string match -q -- '*\n*' "$PASS_INPUT" + echo "ERR $ERR_CANCELLED Operation cancelled" + debug "> ERR passphrase had newline" + else + printf 'D %s\nOK\n' "$PASS_INPUT" + debug "> D " + end + else + echo "ERR $ERR_CANCELLED Operation cancelled" + debug "> ERR cancelled" + end + + case '*CONFIRM*' + echo "ERR $ERR_CANCELLED Operation cancelled" + + case '*MESSAGE*' + echo OK + + case '*GETINFO*' + # We don't implement any sub-queries; decline per Assuan. + echo "ERR $ERR_CANCELLED Operation cancelled" + + case '' + # Blank line: required to be accepted with OK per Assuan. + echo OK + + case '*' + # Unknown command: respond OK rather than hang the agent. + echo OK + end +end -- 2.51.2