aboutsummaryrefslogblamecommitdiffstats
path: root/pinentry-rofi.sh
blob: f7b3ba2986044fc8c3799095dd0b5eba8db439ac (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
           
       
 










                                                                          
 
                    

                                                                                         
                                                                                                     

                                                              


                                                            












                                                                      
               
 
             
                                                                   
 
 



                               
 

                   
                                        
                             
                                          
                                  




                          















                                                                                  
                                                                               

 






                                                                                
 
                                                                                        
                  


                                                     
                                                                                   

                             


                               
 

          
         
                  
              
 

                                    



































































                                                                                                                


                                                       









                                                    


                                                       




























                                                                              
                  
              






                                                                 
          




                                  

                                       

            
                         
      
  
 
#!/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() {
    [ -z "${DEBUG:-}" ] || echo "$@" >> /tmp/pinentry-log-$USER.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/</\&lt;/g' -e 's/>/\&gt;/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
    umask 0077

    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
    sess_type="${XDG_SESSION_TYPE:-}"
    if [ "$sess_type" != "tty" ] ; then
        main
    else
        pinentry-tty "$@"
    fi
fi