This repository has no description
3.3 kB
140 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 @u 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
23 local key_id
24 key_id=$(keyctl add user "$CACHE_KEY" "$pin" @u)
25 keyctl timeout "$key_id" "$CACHE_TIMEOUT"
26}
27
28# Extend cache timeout (sliding window)
29extend_cache() {
30 local key_id
31 key_id=$(keyctl search @u user "$CACHE_KEY" 2>/dev/null) || return 1
32 keyctl timeout "$key_id" "$CACHE_TIMEOUT"
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 "Use cached PIN\nClear cache" | rofi -dmenu \
111 -p "PIN cached" \
112 -no-fixed-num-lines \
113 "${mesg_args[@]}" \
114 "${theme_args[@]}" \
115 2>/dev/null) || exit 1
116
117 if [[ "$choice" == "Use cached PIN" ]]; then
118 extend_cache
119 echo "$cached_pin"
120 exit 0
121 elif [[ "$choice" == "Clear cache" ]]; then
122 clear_cache
123 fi
124 # Fall through to PIN prompt if cache cleared
125fi
126
127# Use rofi in password mode
128result=$(rofi -dmenu \
129 -password \
130 -p "PIN" \
131 -lines 0 \
132 -no-fixed-num-lines \
133 "${mesg_args[@]}" \
134 "${theme_args[@]}" \
135 < /dev/null 2>/dev/null) || exit 1
136
137# Cache the PIN
138cache_pin "$result"
139
140echo "$result"