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