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