#!/bin/bash set -eu # SPDX-FileCopyrightText: 2016 Quentin "Sardem FF7" Glidic # SPDX-FileCopyrightText: 2018-2023 Fredrik Salomonsson # # SPDX-License-Identifier: GPL-3.0-or-later # Some documentation # https://info2html.sourceforge.net/cgi-bin/info2html-demo/info2html?(pinentry)Protocol # https://superuser.com/a/1655428 # TODO https://superuser.com/questions/1457167/i-want-to-make-pinentry-use-gui-locally-and-cli-on-ssh # Even better # https://github.com/gpg/libassuan/blob/master/doc/assuan.texi # Although it is called a PIN-Entry, it does allow to enter reasonably # long strings (at least 2048 characters are supported by every # pinentry). # The client using the PIN-Entry has to check for correctness. # Note that all strings are expected to be encoded as UTF-8; # PINENTRY takes care of converting it to the locally used codeset. # To include linefeeds or other special characters, you may # percent-escape them (i.e. a line feed is encoded as `%0A', the # percent sign itself is encoded as `%25'). VERSION="0.0.1" log_debug() { echo "$@" >> /tmp/pinentry-log.txt } rofi_cmd="rofi -dmenu -input /dev/null -password" assuan_send() { log_debug "assuan_send: $*" echo "$@" } assuan_send "OK Please go ahead" win_title="Prompt for password" win_prompt="Password" win_mesg="" keyinfo="" # gpg-agent[676]: DBG: chan_9 -> OK Pleased to meet you, process 3073 # gpg-agent[676]: DBG: chan_9 <- RESET # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION ttyname=/dev/pts/0 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION ttytype=xterm-256color # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION display=:0 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION putenv=WAYLAND_DISPLAY=wayland-1 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION putenv=XDG_SESSION_TYPE=wayland # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION putenv=DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION lc-ctype=en_US.UTF-8 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION lc-messages=cs_CZ.utf8 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- GETINFO version # gpg-agent[676]: DBG: chan_9 -> D 2.3.8 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION allow-pinentry-notify # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- OPTION agent-awareness=2.1.0 # gpg-agent[676]: DBG: chan_9 -> OK # gpg-agent[676]: DBG: chan_9 <- KEYINFO 8EA96F4B4BD616DA44A2E297F31C0448A74B9D33 while : ; do read -r line log_debug "line=$line" # Set options for the connection. The syntax of such a line is # OPTION name [ [=] value ] # Leading and trailing spaces around name and value are # allowed but should be ignored. For compatibility reasons, name # may be prefixed with two dashes. The use of the equal sign # is optional but suggested if value is given. if [[ "$line" =~ ^OPTION ]] ; then # OPTION grab # OPTION ttyname=/dev/pts/1 # OPTION ttytype=tmux-256color # OPTION lc-messages=C assuan_send "OK" elif [[ "$line" =~ ^GETINFO ]] ; then # https://www.gnupg.org/documentation/manuals/gnupg/Agent-GETINFO.html # version or pid of this script? # gpg-agent --version works but it must be filtered IFS=" " read -ra line_arr <<< "$line" subcommand=${line_arr[1]} log_debug "subcommand=${subcommand}" if [[ "$subcommand" == "version" ]] ; then assuan_send "D ${VERSION}" elif [[ "$subcommand" == "pid" ]] ; then assuan_send "D $$" fi assuan_send "OK" # This command is reserved for future extensions. # True NOOP elif [[ "$line" =~ ^CANCEL ]] ; then assuan_send "OK" # This command is reserved for future extensions. Not yet # specified as we don't implement it in the first phase. See # Werner's mail to gpa-dev on 2001-10-25 about the rationale # for measurements against local attacks. # True NOOP elif [[ "$line" =~ ^AUTH ]] ; then assuan_send "OK" # And this actually is NOOP elif [[ "$line" =~ ^NOP ]] ; then assuan_send "OK" elif [[ "$line" =~ ^KEYINFO ]] ; then assuan_send "${keyinfo}" assuan_send "OK" elif [[ "$line" =~ ^SETKEYINFO ]] ; then IFS=" " read -ra line_arr <<< "$line" unset "line_arr[0]" && line_arr=("${line_arr[@]}") if [[ "${line_arr[0]}" =~ ^--clear ]] ; then keyinfo="" else keyinfo="${line_arr[*]}" fi assuan_send "OK" elif [[ "$line" =~ ^SETOK|^SETNOTOK|^SETERROR|^SETCANCEL|^SETTIMEOUT|^SETQUALITYBAR|^SETGENPIN ]] ; then assuan_send "OK" elif [[ "$line" =~ ^CONFIRM|^MESSAGE ]] ; then assuan_send "OK" # Reset the connection but not any existing authentication. # The server should release all resources associated with the # connection. elif [[ "$line" =~ ^RESET ]] ; then assuan_send "OK" elif [[ "$line" =~ ^SETDESC ]] ; then #SETDESC Please enter the passphrase for the ssh key%0A ke:yf:in:ge:rp:ri:nt # rofi << "-mesg" << $1.gsub("<", "<").gsub(/%([0-9A-Fa-f]{2})/) { $1.to_i(16).chr } IFS=" " read -ra line_arr <<< "$line" unset "line_arr[0]" && line_arr=("${line_arr[@]}") win_mesg="${line_arr[*]}" assuan_send "OK" elif [[ "$line" =~ ^SETPROMPT ]] ; then #SETPROMPT Passphrase: IFS=" " read -ra line_arr <<< "$line" win_prompt="${line_arr[1]}" assuan_send "OK" elif [[ "$line" =~ ^GETPIN ]] ; then passw=None sys_env="$(systemctl --user show-environment | tr -s " \t\n" " ")" IFS=" " read -ra sys_env_arr <<< "$sys_env" for env_line in "${sys_env_arr[@]}" ; do log_debug "env_line=${env_line}" # GPIN_VALID=re.compile(r) if [[ "$env_line" =~ ^([A-Za-z][A-Za-z_]*)=(.+)$ ]] ; then log_debug "env_match=${BASH_REMATCH[*]}" export "${BASH_REMATCH[1]}=${BASH_REMATCH[2]}" fi done rofi_cmd+=" -p '${win_prompt}'" rofi_cmd+=" -title '${win_title}'" if [[ -n "${win_mesg}" ]] ; then rofi_cmd+=" -mesg '${win_mesg}'" fi log_debug "${rofi_cmd}" passw="$(eval "${rofi_cmd}")" passw_err=$? if [[ ${passw_err} -ne 0 ]] ; then # assuan_send "ERR 83886179 Operation cancelled " log_debug "rofi failed to run: ${passw} (${passw_err})" exit $passw_err else if [[ -n ${passw} ]] ; then assuan_send "D ${passw}" fi fi assuan_send "OK" # Close the connection. The server will respond with OK. elif [[ ${line} =~ ^BYE ]] ; then exit 0 else assuan_send "BYE" exit 1 fi done