This repository has no description
6.7 kB
170 lines
1{ self, inputs, ... }:
2{
3 perSystem =
4 {
5 pkgs,
6 system,
7 ...
8 }:
9 {
10 packages.pass = inputs.wrappers.lib.wrapPackage {
11 pkgs = pkgs;
12 package = pkgs.passage;
13 runtimeInputs = [
14 pkgs.age-plugin-fido2-hmac
15 pkgs.age-plugin-yubikey
16 pkgs.age-plugin-tpm
17 self.packages.${system}.age-plugin-gpg
18 self.packages.${system}.pass-otp
19 self.packages.${system}.age-detect
20 ]
21 ++ (pkgs.lib.optionals pkgs.stdenv.isLinux [
22 self.packages.${system}.pinentry-rofi-age
23 ])
24 ++ (pkgs.lib.optionals pkgs.stdenv.isDarwin [
25 pkgs.age-plugin-se
26 ])
27 ++ (pkgs.lib.optionals (!pkgs.stdenv.isDarwin) [
28 # Dummy age-plugin-se for Linux (encryption only, no Secure Enclave hardware)
29 (pkgs.writeShellScriptBin "age-plugin-se" ''
30 exec env PYTHONUNBUFFERED=1 ${
31 pkgs.python3.withPackages (ps: [ ps.cryptography ])
32 }/bin/python3 ${./age-plugin-se-dummy.py} "$@"
33 '')
34 ]);
35 aliases = [ "pass" ];
36 wrapper =
37 {
38 exePath,
39 envString,
40 preHook,
41 ...
42 }:
43 ''
44 ${envString}
45 ${preHook}
46
47 # Determine if the command needs decryption
48 needs_decrypt=true
49 case "''${1:-}" in
50 ls|find|git|insert|generate|rm|mv|cp|help|version|init)
51 needs_decrypt=false
52 ;;
53 esac
54
55 ${
56 if pkgs.stdenv.isLinux then
57 ''
58 maybe_set_tpm_pin() {
59 local target="''${1:-<stdin>}"
60 if [[ "''${KEY_TYPE:-}" != "tpm" ]]; then
61 return 0
62 fi
63 if [[ -n "''${AGE_TPM_PIN:-}" ]]; then
64 return 0
65 fi
66 AGE_TPM_PIN="$(${self.packages.${system}.pinentry-rofi-age}/bin/pinentry-rofi-age "$target")"
67 export AGE_TPM_PIN
68 }
69 ''
70 else
71 ''
72 # No TPM on Darwin, define no-op
73 maybe_set_tpm_pin() { :; }
74 ''
75 }
76
77 setup_identities() {
78 # Always detect available age keys first
79 export AGE_DETECT_KEYS_DIR="${self}/keys"
80 eval "$(age-detect)"
81
82 # Set up identity file if detected
83 if [[ -n "''${IDENTITY_FILE:-}" ]]; then
84 export PASSAGE_IDENTITIES_FILE="$IDENTITY_FILE"
85 trap 'rm -f "$IDENTITY_FILE"' EXIT
86 fi
87
88 # Try to decrypt and cache the bulk key using the detected identity
89 if [[ -n "''${PASS_BULK_KEY_FILE:-}" ]]; then
90 if [[ ! -f "$PASS_BULK_KEY_FILE" ]] || [[ ! -s "$PASS_BULK_KEY_FILE" ]]; then
91 mkdir -p "$(dirname "$PASS_BULK_KEY_FILE")"
92 maybe_set_tpm_pin "bulk-operations/age-key"
93 ${exePath} show bulk-operations/age-key > "$PASS_BULK_KEY_FILE" 2>/dev/null || true
94 fi
95 # Switch to bulk key for subsequent operations if available
96 if [[ -f "$PASS_BULK_KEY_FILE" ]] && [[ -s "$PASS_BULK_KEY_FILE" ]]; then
97 export PASSAGE_IDENTITIES_FILE="$PASS_BULK_KEY_FILE"
98 # Bulk key is a plain age key, no TPM PIN needed for decryption
99 export KEY_TYPE=bulk
100 fi
101 fi
102 }
103
104 if [[ "$needs_decrypt" == true ]]; then
105 setup_identities
106 fi
107
108 ${
109 if pkgs.stdenv.isLinux then
110 ''
111 decrypt_target=""
112 if [[ "''${1:-}" == "show" ]]; then
113 decrypt_target="''${2:-}"
114 elif [[ -n "''${1:-}" ]] && [[ "''${1:-}" != -* ]] && [[ "''${1:-}" != "otp" ]]; then
115 # Skip passage subcommands that don't need decryption
116 case "''${1:-}" in
117 ls|find|git|insert|generate|rm|mv|cp|help|version|init)
118 ;;
119 edit|grep|reencrypt)
120 # These commands do decrypt, prompt with command as target
121 decrypt_target="''${2:-''${1:-}}"
122 ;;
123 *)
124 # Bare pass-name (implicit show)
125 decrypt_target="''${1:-}"
126 ;;
127 esac
128 fi
129
130 if [[ -n "$decrypt_target" ]]; then
131 maybe_set_tpm_pin "$decrypt_target"
132 fi
133 ''
134 else
135 ""
136 }
137
138 # Handle OTP commands by delegating to pass-otp
139 if [[ "''${1:-}" == "otp" ]]; then
140 shift
141 # Extract pass name and options
142 clip_arg=""
143 pass_name=""
144 for arg in "$@"; do
145 case "$arg" in
146 "--clip"|"-c")
147 clip_arg="--clip"
148 ;;
149 *)
150 pass_name="$arg"
151 ;;
152 esac
153 done
154
155 if [[ -z "$pass_name" ]]; then
156 echo "Usage: pass otp [--clip,-c] pass-name"
157 exit 1
158 fi
159
160 # Show pass and pipe to OTP (identities already set up by age-detect)
161 maybe_set_tpm_pin "$pass_name"
162 ${exePath} show "$pass_name" | pass-otp $clip_arg
163 else
164 # Pass all other commands directly to passage (identities already set up)
165 ${exePath} "$@"
166 fi
167 '';
168 };
169 };
170}