This repository has no description
4.6 kB
122 lines
1#!/usr/bin/env bash
2set -euo pipefail
3
4# age-detect: Detects connected age-compatible security keys
5# Returns identity file path and detected key information
6
7# Function to detect connected security keys and their age identities
8detect_age_keys() {
9 local temp_identities
10 temp_identities=$(mktemp)
11
12 # Check for Secure Enclave on macOS (highest priority)
13 if [[ "$(uname)" == "Darwin" ]] && command -v age-plugin-se >/dev/null 2>&1; then
14 rm -f "$temp_identities"
15 echo "DETECTED_KEY=se"
16 echo "KEY_TYPE=se"
17 return
18 fi
19
20 # Check for TPM on Linux (second priority - built-in, no USB needed)
21 # Requires: TPM device, hostname.age with age1tpm1/age1tag1 recipient, and identity in ~/.config/age/
22 # Newer age-plugin-tpm emits native p256tag (age1tag1) recipients by default.
23 if [[ -c /dev/tpmrm0 ]] && [[ -f ~/.config/age/tpm-identity.txt ]]; then
24 local hostname_key
25 hostname_key="${AGE_DETECT_KEYS_DIR:-}/age/$(hostname).age"
26 if [[ -f "$hostname_key" ]] && grep -qE "^age1(tpm|tag)1" "$hostname_key" 2>/dev/null; then
27 rm -f "$temp_identities"
28 echo "DETECTED_KEY=tpm"
29 echo "KEY_TYPE=tpm"
30 return
31 fi
32 fi
33
34 # Check for a running phonetpm daemon (third priority - phone over iroh,
35 # every decryption asks for a fingerprint on the phone).
36 # Requires: control socket of configs/phonetpm.nix and phonetpm.identity.
37 local phone_identity="${AGE_DETECT_KEYS_DIR:-}/age/phonetpm.identity"
38 if [[ -S "${XDG_RUNTIME_DIR:-}/phonetpm/control.sock" ]] && [[ -f "$phone_identity" ]]; then
39 cp "$phone_identity" "$temp_identities"
40 echo "IDENTITY_FILE=$temp_identities"
41 echo "DETECTED_KEY=phonetpm"
42 echo "KEY_TYPE=phone"
43 return
44 fi
45
46 # Check for gpg-agent with an encryption subkey (fourth priority).
47 # Frictionless after the first decrypt because gpg-agent caches the
48 # passphrase / smartcard PIN, so we prefer it over yubikey/solo2.
49 if command -v gpg-connect-agent >/dev/null 2>&1 \
50 && gpg-connect-agent /bye >/dev/null 2>&1 \
51 && gpg --list-secret-keys --with-colons 2>/dev/null \
52 | awk -F: '$1 == "ssb" && $12 ~ /e/ { found=1 } END { exit !found }'; then
53 echo "AGE-PLUGIN-GPG-1H40XPM" > "$temp_identities"
54 echo "IDENTITY_FILE=$temp_identities"
55 echo "DETECTED_KEY=gpg"
56 echo "KEY_TYPE=gpg"
57 return
58 fi
59
60 # Check for YubiKeys (fifth priority)
61 if command -v ykman >/dev/null 2>&1; then
62 local yubikey_serial
63 yubikey_serial=$(ykman info 2>/dev/null | grep "Serial number:" | cut -d: -f2 | tr -d ' ' || true)
64 if [[ -n "$yubikey_serial" ]]; then
65 echo "AGE-PLUGIN-YUBIKEY-1M2PLUQYZ242EPUSSHDFWJ" > "$temp_identities"
66 echo "IDENTITY_FILE=$temp_identities"
67 case "$yubikey_serial" in
68 "16679898")
69 echo "DETECTED_KEY=yubi1"
70 ;;
71 "11342136")
72 echo "DETECTED_KEY=yubi2"
73 ;;
74 *)
75 echo "DETECTED_KEY=yubikey-${yubikey_serial}"
76 ;;
77 esac
78 echo "KEY_TYPE=yubikey"
79 return
80 fi
81 fi
82
83 # Check for Solo2 (lowest priority)
84 if command -v fido2-token >/dev/null 2>&1; then
85 if fido2-token -L 2>/dev/null | grep -q "Solo 2"; then
86 # FIDO2-HMAC doesn't use identity file
87 rm -f "$temp_identities"
88 echo "IDENTITY_FILE="
89 echo "DETECTED_KEY=solo2"
90 echo "KEY_TYPE=solo2"
91 return
92 fi
93 fi
94
95 # No keys detected
96 rm -f "$temp_identities"
97 echo "IDENTITY_FILE="
98 echo "DETECTED_KEY="
99 echo "KEY_TYPE="
100}
101
102# Parse command line arguments
103case "${1:-detect}" in
104 detect)
105 detect_age_keys
106 ;;
107 --help|-h)
108 echo "Usage: age-detect [detect]"
109 echo ""
110 echo "Detects the first available age-compatible security key and outputs:"
111 echo " IDENTITY_FILE=<path> - Path to temporary identity file for age (empty for Solo2)"
112 echo " DETECTED_KEY=<name> - Name of the detected key (e.g., yubi1, solo2, se)"
113 echo " KEY_TYPE=<type> - Type of key (yubikey, solo2, se, tpm, phone, gpg)"
114 echo ""
115 echo "Priority order: se > tpm > phone > gpg > yubikey > solo2"
116 ;;
117 *)
118 echo "Unknown command: $1" >&2
119 echo "Use --help for usage information" >&2
120 exit 1
121 ;;
122esac