blob: d64a1f612b5d2d7cdefe6a6c010590fcf2dfd898 (
plain) (
tree)
|
|
#!/bin/bash
set -eu
# Using inspiration from
# https://github.com/plattfot/pinentry-rofis
# by (c) 2018-2023 Fredrik Salomonsson <plattfot <at> posteo <dot> net>
# which was using inspiration from
# https://gist.github.com/sardemff7/759cbf956bea20d382a6128c641d2746
# by (c) 2016 Quentin "Sardem FF7" Glidic
# however no line of code was actually taken from either script
# (they are in different language anyway).
# Copyright (c) 2023 Matěj Cepl <mcepl <at> cepl <dot> eu>
# Licensed under the terms of the include license in the file LICENSE.
# Some documentation
# https://www.gnupg.org/documentation/manuals/assuan/Client-requests.html#Client-requests
# 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
#
# This looks interesting
# https://velvetcache.org/2023/03/26/a-peek-inside-pinentry/
# 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
}
assuan_send() {
log_debug "assuan_send: $*"
echo "$@"
}
split_line() {
rmfirst=${2:-0}
log_debug "args: \"$1\", ${rmfirst}"
read -ra out_arr <<< "$1"
log_debug "out: $(declare -p out_arr)"
if [ "$rmfirst" -ne 1 ] ; then
unset "out_arr[0]"
fi
echo "${out_arr[@]}"
}
# Originally from https://github.com/sfinktah/bash/blob/master/rawurlencode.inc.sh
# There is also more explanation and documentation there
rawurlencode() {
local string="${1}"
local strlen=${#string}
local encoded=""
local pos c o
for (( pos=0 ; pos<strlen ; pos++ )); do
c=${string:$pos:1}
case "$c" in
[-_.~a-zA-Z0-9] ) o="${c}" ;;
* ) printf -v o '%%%02x' "'$c"
esac
encoded+="${o}"
done
printf "%s" "${encoded}" # You can either set a return variable (FASTER)
}
rawurldecode() {
# This is perhaps a risky gambit, but since all escape characters must be
# encoded, we can replace %NN with \xNN and pass the lot to printf -b, which
# will decode hex for us
printf '%b' "${1//%/\\x}"
}
# Except Assuan apparently doesn't want really encoded strings, but some kind of bastard
basturlencode () {
echo "$1" | sed -e 's/</\</g' -e 's/>/\>/g'
}
rofi_cmd="rofi -dmenu -theme-str 'listview {lines: 0;}' -input /dev/null -password"
INSIDE_BATS=${INSIDE_BATS:-0}
win_title="Prompt for password"
win_prompt="Password"
win_mesg=""
keyinfo=""
main () {
local temp_str
assuan_send "OK Please go ahead"
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=" " line_arr=($(split_line "$line"))
log_debug "line_arr: ${line_arr[*]}"
subcommand=${line_arr[0]}
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=" " line_arr=($(split_line "$line"))
log_debug "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
IFS=" " line_arr=($(split_line "$line"))
log_debug "line_arr: ${line_arr[*]}"
temp_str="$(rawurldecode "${line_arr[*]}")"
log_debug "temp_str: ${temp_str}"
win_mesg="$(basturlencode "${temp_str}")"
assuan_send "OK"
elif [[ "$line" =~ ^SETPROMPT ]] ; then
#SETPROMPT Passphrase:
IFS=" " line_arr=($(split_line "$line"))
log_debug "line_arr: ${line_arr[*]}"
win_prompt="${line_arr[0]}"
assuan_send "OK"
elif [[ "$line" =~ ^SETTITLE ]] ; then
IFS=" " line_arr=($(split_line "$line"))
log_debug "line_arr: ${line_arr[*]}"
temp_str="$(rawurldecode "${line_arr[*]}")"
log_debug "temp_str: ${temp_str}"
win_title="$(basturlencode "${temp_str}")"
assuan_send "OK"
elif [[ "$line" =~ ^GETPIN ]] ; then
passw=None
sys_env="$(systemctl --user show-environment | tr -s " \t\n" " ")"
IFS=" " sys_env_arr=($(split_line "$sys_env" 1))
log_debug "sys_env_arr: ${sys_env_arr[*]}"
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 <rofi>"
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
}
if [ "$0" = "$BASH_SOURCE" ]; then
if [ "$XDG_SESSION_TYPE" != "tty" ] ; then
main
else
pinentry-tty "$@"
fi
fi
|