This repository has no description
3.5 kB
151 lines
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)
5set -efu
6
7CACHE_KEY="pinentry-rofi-pin"
8CACHE_TIMEOUT=300 # 5 minutes
9
10# Get cached PIN from kernel keyring
11get_cached_pin() {
12 local key_id
13 key_id=$(keyctl search @s user "$CACHE_KEY" 2>/dev/null) || return 1
14 keyctl pipe "$key_id" 2>/dev/null
15}
16
17# Store PIN in kernel keyring with timeout
18cache_pin() {
19 local pin="$1"
20 # Remove old key if exists
21 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 local key_id
24 key_id=$(printf '%s' "$pin" | keyctl padd user "$CACHE_KEY" @s) || return 0
25 keyctl timeout "$key_id" "$CACHE_TIMEOUT" || true
26}
27
28# Extend cache timeout (sliding window)
29extend_cache() {
30 local key_id
31 key_id=$(keyctl search @s user "$CACHE_KEY" 2>/dev/null) || return 0
32 keyctl timeout "$key_id" "$CACHE_TIMEOUT" || true
33}
34
35# Clear cached PIN
36clear_cache() {
37 keyctl purge user "$CACHE_KEY" >/dev/null 2>&1 || true
38}
39
40# Extract SSH args from client PID
41# Groups flags with their arguments on the same line
42get_ssh_args() {
43 local pid="${SSH_CLIENT_PID:-}"
44 [[ -z "$pid" ]] && return 1
45 [[ ! -r "/proc/$pid/cmdline" ]] && return 1
46
47 # Read command line (null-separated) into array
48 local -a args=()
49 while IFS= read -r -d '' arg; do
50 args+=("$arg")
51 done < "/proc/$pid/cmdline"
52
53 # Check first arg is ssh
54 [[ "${args[0]}" == "ssh" || "${args[0]}" == */ssh ]] || return 1
55
56 # Output args, keeping flags and their values on the same line
57 local i=1
58 local line=""
59 while [[ $i -lt ${#args[@]} ]]; do
60 local arg="${args[$i]}"
61 if [[ "$arg" == -* ]]; then
62 # Print previous line if exists
63 [[ -n "$line" ]] && echo "$line"
64 line="$arg"
65 else
66 # Append to current line
67 if [[ -n "$line" ]]; then
68 line="$line $arg"
69 else
70 line="$arg"
71 fi
72 fi
73 ((i++))
74 done
75 # Print last line
76 [[ -n "$line" ]] && echo "$line"
77}
78
79# Escape string for pango markup
80escape_pango() {
81 sed 's/&/\&/g; s/</\</g; s/>/\>/g'
82}
83
84# Get SSH args
85ssh_args=""
86if ssh_args=$(get_ssh_args 2>/dev/null | escape_pango); then
87 : # got args
88fi
89
90# Build the rofi message
91mesg_args=()
92if [[ -n "$ssh_args" ]]; then
93 mesg="<b>SSH:</b>
94$ssh_args"
95 mesg_args=(-mesg "$mesg")
96fi
97
98# Determine theme
99theme_file="/var/theme/config/rofi-theme"
100theme_args=()
101if [[ -r "$theme_file" ]]; then
102 theme=$(cat "$theme_file")
103 theme_args=(-theme "$theme")
104fi
105
106# Check for cached PIN
107cached_pin=""
108if 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 \
113 -no-fixed-num-lines \
114 "${mesg_args[@]}" \
115 "${theme_args[@]}" \
116 2>/dev/null) || exit 1
117
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
132fi
133
134# Use rofi in password mode
135result=$(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
144if [[ -z "$result" ]]; then
145 exit 1
146fi
147
148# Cache the PIN
149cache_pin "$result"
150
151echo "$result"