This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

superconfig / tools / pinentry-rofi / pinentry-rofi.sh
7.2 kB 277 lines
1#!/usr/bin/env bash 2# pinentry-rofi: A rofi-based pinentry 3# 4# Dual-mode: 5# - SSH_ASKPASS mode: invoked with a prompt string as $1 (e.g. by 6# ssh-tpm-agent via SSH_ASKPASS). Prints the PIN on stdout. 7# - Assuan/pinentry mode: invoked with no args or only "--" options 8# (e.g. by rbw, gpg-agent). Speaks the Assuan protocol on stdin/stdout. 9# 10# Caches PIN in kernel keyring for 5 minutes (sliding window). 11set -efu 12 13CACHE_KEY="pinentry-rofi-pin" 14CACHE_TIMEOUT=300 # 5 minutes 15 16# ---------- cache helpers ---------- 17 18get_cached_pin() { 19 local key_id 20 key_id=$(keyctl search @u user "$CACHE_KEY" 2>/dev/null) || return 1 21 keyctl pipe "$key_id" 2>/dev/null 22} 23 24cache_pin() { 25 local pin="$1" 26 keyctl purge user "$CACHE_KEY" >/dev/null 2>&1 || true 27 local key_id 28 key_id=$(printf '%s' "$pin" | keyctl padd user "$CACHE_KEY" @u) || return 0 29 keyctl timeout "$key_id" "$CACHE_TIMEOUT" || true 30} 31 32extend_cache() { 33 local key_id 34 key_id=$(keyctl search @u user "$CACHE_KEY" 2>/dev/null) || return 0 35 keyctl timeout "$key_id" "$CACHE_TIMEOUT" || true 36} 37 38clear_cache() { 39 keyctl purge user "$CACHE_KEY" >/dev/null 2>&1 || true 40} 41 42# ---------- SSH context (for ssh-tpm-agent) ---------- 43 44get_ssh_args() { 45 local pid="${SSH_CLIENT_PID:-}" 46 [[ -z "$pid" ]] && return 1 47 [[ ! -r "/proc/$pid/cmdline" ]] && return 1 48 49 local -a args=() 50 while IFS= read -r -d '' arg; do 51 args+=("$arg") 52 done < "/proc/$pid/cmdline" 53 54 [[ "${args[0]}" == "ssh" || "${args[0]}" == */ssh ]] || return 1 55 56 local i=1 57 local line="" 58 while [[ $i -lt ${#args[@]} ]]; do 59 local arg="${args[$i]}" 60 if [[ "$arg" == -* ]]; then 61 [[ -n "$line" ]] && echo "$line" 62 line="$arg" 63 else 64 if [[ -n "$line" ]]; then 65 line="$line $arg" 66 else 67 line="$arg" 68 fi 69 fi 70 ((i++)) 71 done 72 [[ -n "$line" ]] && echo "$line" 73} 74 75escape_pango() { 76 sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' 77} 78 79# ---------- rofi prompt ---------- 80# 81# Args: $1 = prompt label (default "PIN") 82# $2 = optional pango-formatted -mesg text 83# $3 = use_cache: "cache" enables kernel-keyring caching, anything 84# else disables it (Assuan callers like rbw/gpg-agent do their 85# own caching, and sharing one keyring slot across unrelated 86# secrets is wrong). 87# Echoes PIN on success, returns non-zero on cancel. 88rofi_prompt() { 89 local prompt_label="${1:-PIN}" 90 local mesg="${2:-}" 91 local use_cache="${3:-}" 92 93 local mesg_args=() 94 [[ -n "$mesg" ]] && mesg_args=(-mesg "$mesg") 95 96 local theme_file="/var/theme/config/rofi-theme" 97 local theme_args=() 98 if [[ -r "$theme_file" ]]; then 99 local theme 100 theme=$(cat "$theme_file") 101 theme_args=(-theme "$theme") 102 fi 103 104 # Cached PIN menu (only in modes that opted in) 105 if [[ "$use_cache" == "cache" ]]; then 106 local cached_pin="" 107 if cached_pin=$(get_cached_pin 2>/dev/null) && [[ -n "$cached_pin" ]]; then 108 local choice 109 choice=$(printf "OK\nCancel\nClear cache" | rofi -dmenu \ 110 -i \ 111 -no-custom \ 112 -no-fixed-num-lines \ 113 "${mesg_args[@]}" \ 114 "${theme_args[@]}" \ 115 2>/dev/null) || return 1 116 117 case "$choice" in 118 "OK") 119 extend_cache 120 printf '%s' "$cached_pin" 121 return 0 122 ;; 123 "Cancel") 124 return 1 125 ;; 126 "Clear cache") 127 clear_cache 128 ;; 129 esac 130 fi 131 fi 132 133 local result 134 result=$(rofi -dmenu \ 135 -password \ 136 -p "$prompt_label" \ 137 -lines 0 \ 138 -no-fixed-num-lines \ 139 "${mesg_args[@]}" \ 140 "${theme_args[@]}" \ 141 < /dev/null 2>/dev/null) || return 1 142 143 [[ -z "$result" ]] && return 1 144 145 if [[ "$use_cache" == "cache" ]]; then 146 cache_pin "$result" 147 fi 148 printf '%s' "$result" 149} 150 151# ---------- mode: SSH_ASKPASS ---------- 152 153askpass_main() { 154 local ssh_args="" 155 ssh_args=$(get_ssh_args 2>/dev/null | escape_pango) || ssh_args="" 156 157 local mesg="" 158 if [[ -n "$ssh_args" ]]; then 159 mesg="<b>SSH:</b> 160$ssh_args" 161 fi 162 163 local pin 164 pin=$(rofi_prompt "PIN" "$mesg" cache) || exit 1 165 printf '%s\n' "$pin" 166} 167 168# ---------- mode: Assuan pinentry ---------- 169# 170# Spec (abridged): https://www.gnupg.org/documentation/manuals/assuan/ 171# Server greets with "OK ...", responds OK to most settings, and on 172# GETPIN replies with "D <pin>" then "OK", or "ERR 83886179 cancelled". 173# Data lines must percent-encode %, CR, LF. 174 175assuan_decode() { 176 # decode %HH escapes from SETDESC/SETPROMPT/SETERROR values 177 printf '%b' "$(printf '%s' "$1" | sed 's/%\([0-9A-Fa-f][0-9A-Fa-f]\)/\\x\1/g')" 178} 179 180assuan_encode_data() { 181 # encode %, CR, LF for D-line output 182 printf '%s' "$1" | sed -e 's/%/%25/g' -e 's/\r/%0D/g' 183} 184 185assuan_main() { 186 printf 'OK Pleased to meet you\n' 187 188 local desc="" prompt="PIN" error="" 189 # title and keyinfo are accepted but unused by the rofi UI 190 local _title="" _keyinfo="" 191 192 local line cmd arg 193 while IFS= read -r line; do 194 line="${line%$'\r'}" 195 if [[ "$line" == *" "* ]]; then 196 cmd="${line%% *}" 197 arg="${line#* }" 198 else 199 cmd="$line" 200 arg="" 201 fi 202 203 case "$cmd" in 204 SETDESC) desc="$(assuan_decode "$arg")"; printf 'OK\n' ;; 205 SETPROMPT) prompt="$(assuan_decode "$arg")"; printf 'OK\n' ;; 206 SETERROR) error="$(assuan_decode "$arg")"; printf 'OK\n' ;; 207 SETTITLE) _title="$(assuan_decode "$arg")"; printf 'OK\n' ;; 208 SETKEYINFO) _keyinfo="$arg"; printf 'OK\n' ;; 209 210 OPTION|SETOK|SETCANCEL|SETNOTOK|SETTIMEOUT|SETQUALITYBAR|SETQUALITYBAR_TT|SETREPEAT|SETREPEATERROR|SETGENPIN|SETGENPIN_TT|CLEARPASSPHRASE|GETINFO) 211 printf 'OK\n' ;; 212 213 GETPIN) 214 # Build rofi -mesg from desc/error 215 local mesg="" 216 if [[ -n "$error" ]]; then 217 mesg="<b>$(printf '%s' "$error" | escape_pango)</b>" 218 error="" 219 fi 220 if [[ -n "$desc" ]]; then 221 [[ -n "$mesg" ]] && mesg+=" 222" 223 mesg+="$(printf '%s' "$desc" | escape_pango)" 224 fi 225 226 local pin 227 # No cache in Assuan mode: each caller (rbw, gpg-agent, ...) 228 # asks for a different secret and does its own caching. 229 if pin=$(rofi_prompt "${prompt:-PIN}" "$mesg" no-cache); then 230 printf 'D %s\n' "$(assuan_encode_data "$pin")" 231 printf 'OK\n' 232 else 233 printf 'ERR 83886179 Operation cancelled\n' 234 fi 235 ;; 236 237 CONFIRM|MESSAGE) 238 printf 'OK\n' ;; 239 240 RESET) 241 desc=""; prompt="PIN"; error=""; _title=""; _keyinfo="" 242 printf 'OK\n' ;; 243 244 NOP) 245 printf 'OK\n' ;; 246 247 BYE) 248 printf 'OK closing connection\n' 249 exit 0 ;; 250 251 "") 252 ;; 253 254 *) 255 printf 'OK\n' ;; 256 esac 257 done 258} 259 260# ---------- dispatch ---------- 261 262is_assuan_mode() { 263 # Assuan/pinentry mode: no args, or first arg is a flag. 264 # Real pinentry callers (rbw, gpg-agent) always lead with flags like 265 # --timeout 0 --ttyname /dev/pts/0 --display :0 --no-global-grab 266 # Note we can't require *every* arg to start with '-', because flag 267 # values ("0", "/dev/pts/0", ":0", ...) don't. 268 # 269 # SSH_ASKPASS mode: first arg is a bare prompt string (no leading '-'). 270 [[ $# -eq 0 || "${1:-}" == -* ]] 271} 272 273if is_assuan_mode "$@"; then 274 assuan_main 275else 276 askpass_main 277fi