This repository has no description
0

Configure Feed

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

tools.pinentry-rofi: support Assuan protocol for rbw/gpg-agent

The script was previously SSH_ASKPASS-only (raw PIN on stdout), which
broke rbw and gpg-agent: they speak the Assuan protocol over stdin/stdout
and could not parse the output ('failed to read password from pinentry').

Dispatch by argv:
- no args, or first arg starts with '-' (real pinentry flags like
--timeout 0 --ttyname ... --display ... --no-global-grab) -> Assuan mode
- bare prompt arg (SSH_ASKPASS convention used by ssh-tpm-agent)
-> raw PIN on stdout (existing behaviour)

Assuan mode handles SETTITLE/SETPROMPT/SETDESC/SETERROR/SETKEYINFO/OPTION/
RESET/NOP/BYE, percent-decodes incoming descriptions for the rofi -mesg,
and percent-encodes the D-line PIN.

Kernel-keyring caching is scoped to SSH_ASKPASS mode only; rbw and
gpg-agent do their own caching, and sharing one keyring slot across
unrelated secrets handed back the wrong PIN to whichever caller asked
second.

tools.kagi-search: default to 'rbw get kagi.com -f session_link'

Matches the actual vault layout (a kagi.com login with a session_link
field) instead of expecting a separate 'kagi-session-link' entry.

+206 -80
+2 -2
tools/kagi-search/kagi_search.py
··· 156 156 # Create default config 157 157 config_file.parent.mkdir(parents=True, exist_ok=True) 158 158 default_config = { 159 - "password_command": "rbw get kagi-session-link", 159 + "password_command": "rbw get kagi.com -f session_link", 160 160 "timeout": 30, 161 161 "max_retries": 5, 162 162 } ··· 172 172 173 173 def _get_session_token(self, config: Dict[str, Any]) -> str: 174 174 """Get session token using password command from config.""" 175 - password_command = config.get("password_command", "rbw get kagi-session-link") 175 + password_command = config.get("password_command", "rbw get kagi.com -f session_link") 176 176 177 177 try: 178 178 # Execute the password command
+204 -78
tools/pinentry-rofi/pinentry-rofi.sh
··· 1 1 #!/usr/bin/env bash 2 - # pinentry-rofi: A rofi-based pinentry for SSH TPM authentication 3 - # Reads SSH_CLIENT_PID to extract SSH command context 4 - # Caches PIN in kernel keyring for 5 minutes (sliding window) 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). 5 11 set -efu 6 12 7 13 CACHE_KEY="pinentry-rofi-pin" 8 14 CACHE_TIMEOUT=300 # 5 minutes 9 15 10 - # Get cached PIN from kernel keyring 16 + # ---------- cache helpers ---------- 17 + 11 18 get_cached_pin() { 12 19 local key_id 13 20 key_id=$(keyctl search @u user "$CACHE_KEY" 2>/dev/null) || return 1 14 21 keyctl pipe "$key_id" 2>/dev/null 15 22 } 16 23 17 - # Store PIN in kernel keyring with timeout 18 24 cache_pin() { 19 25 local pin="$1" 20 - # Remove old key if exists 21 26 keyctl purge user "$CACHE_KEY" >/dev/null 2>&1 || true 22 - # Add new key with timeout (using padd to avoid PIN in /proc/*/cmdline) 23 27 local key_id 24 28 key_id=$(printf '%s' "$pin" | keyctl padd user "$CACHE_KEY" @u) || return 0 25 29 keyctl timeout "$key_id" "$CACHE_TIMEOUT" || true 26 30 } 27 31 28 - # Extend cache timeout (sliding window) 29 32 extend_cache() { 30 33 local key_id 31 34 key_id=$(keyctl search @u user "$CACHE_KEY" 2>/dev/null) || return 0 32 35 keyctl timeout "$key_id" "$CACHE_TIMEOUT" || true 33 36 } 34 37 35 - # Clear cached PIN 36 38 clear_cache() { 37 39 keyctl purge user "$CACHE_KEY" >/dev/null 2>&1 || true 38 40 } 39 41 40 - # Extract SSH args from client PID 41 - # Groups flags with their arguments on the same line 42 + # ---------- SSH context (for ssh-tpm-agent) ---------- 43 + 42 44 get_ssh_args() { 43 45 local pid="${SSH_CLIENT_PID:-}" 44 46 [[ -z "$pid" ]] && return 1 45 47 [[ ! -r "/proc/$pid/cmdline" ]] && return 1 46 48 47 - # Read command line (null-separated) into array 48 49 local -a args=() 49 50 while IFS= read -r -d '' arg; do 50 51 args+=("$arg") 51 52 done < "/proc/$pid/cmdline" 52 53 53 - # Check first arg is ssh 54 54 [[ "${args[0]}" == "ssh" || "${args[0]}" == */ssh ]] || return 1 55 55 56 - # Output args, keeping flags and their values on the same line 57 56 local i=1 58 57 local line="" 59 58 while [[ $i -lt ${#args[@]} ]]; do 60 59 local arg="${args[$i]}" 61 60 if [[ "$arg" == -* ]]; then 62 - # Print previous line if exists 63 61 [[ -n "$line" ]] && echo "$line" 64 62 line="$arg" 65 63 else 66 - # Append to current line 67 64 if [[ -n "$line" ]]; then 68 65 line="$line $arg" 69 66 else ··· 72 69 fi 73 70 ((i++)) 74 71 done 75 - # Print last line 76 72 [[ -n "$line" ]] && echo "$line" 77 73 } 78 74 79 - # Escape string for pango markup 80 75 escape_pango() { 81 76 sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g' 82 77 } 83 78 84 - # Get SSH args 85 - ssh_args="" 86 - if ssh_args=$(get_ssh_args 2>/dev/null | escape_pango); then 87 - : # got args 88 - fi 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. 88 + rofi_prompt() { 89 + local prompt_label="${1:-PIN}" 90 + local mesg="${2:-}" 91 + local use_cache="${3:-}" 89 92 90 - # Build the rofi message 91 - mesg_args=() 92 - if [[ -n "$ssh_args" ]]; then 93 - mesg="<b>SSH:</b> 94 - $ssh_args" 95 - mesg_args=(-mesg "$mesg") 96 - fi 93 + local mesg_args=() 94 + [[ -n "$mesg" ]] && mesg_args=(-mesg "$mesg") 97 95 98 - # Determine theme 99 - theme_file="/var/theme/config/rofi-theme" 100 - theme_args=() 101 - if [[ -r "$theme_file" ]]; then 102 - theme=$(cat "$theme_file") 103 - theme_args=(-theme "$theme") 104 - fi 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 105 103 106 - # Check for cached PIN 107 - cached_pin="" 108 - if cached_pin=$(get_cached_pin 2>/dev/null) && [[ -n "$cached_pin" ]]; then 109 - # Show menu with cache options 110 - choice=$(printf "OK\nCancel\nClear cache" | rofi -dmenu \ 111 - -i \ 112 - -no-custom \ 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 \ 113 138 -no-fixed-num-lines \ 114 139 "${mesg_args[@]}" \ 115 140 "${theme_args[@]}" \ 116 - 2>/dev/null) || exit 1 141 + < /dev/null 2>/dev/null) || return 1 117 142 118 - case "$choice" in 119 - "OK") 120 - extend_cache 121 - echo "$cached_pin" 122 - exit 0 123 - ;; 124 - "Cancel") 125 - exit 1 126 - ;; 127 - "Clear cache") 128 - clear_cache 129 - # Fall through to password prompt below 130 - ;; 131 - esac 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 + 153 + askpass_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 + 175 + assuan_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 + 180 + assuan_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 + 185 + assuan_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 + 262 + is_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 + 273 + if is_assuan_mode "$@"; then 274 + assuan_main 275 + else 276 + askpass_main 132 277 fi 133 - 134 - # Use rofi in password mode 135 - result=$(rofi -dmenu \ 136 - -password \ 137 - -p "PIN" \ 138 - -lines 0 \ 139 - -no-fixed-num-lines \ 140 - "${mesg_args[@]}" \ 141 - "${theme_args[@]}" \ 142 - < /dev/null 2>/dev/null) || exit 1 143 - 144 - if [[ -z "$result" ]]; then 145 - exit 1 146 - fi 147 - 148 - # Cache the PIN 149 - cache_pin "$result" 150 - 151 - echo "$result"