|
8 | 8 | ... |
9 | 9 | }: |
10 | 10 | let |
| 11 | + # These options will automatically generate a temporary password and remove it later on. |
| 12 | + autogeneratedPassword = config.enrollFido2; |
| 13 | + |
11 | 14 | keyFile = |
12 | 15 | if config.settings ? "keyFile" then |
13 | 16 | config.settings.keyFile |
|
25 | 28 | ) config.keyFile |
26 | 29 | else |
27 | 30 | null; |
28 | | - keyFileArgs = '' |
| 31 | + |
| 32 | + formatKeyFile = |
| 33 | + if autogeneratedPassword then ''<(set +x; echo -n "$password"; set -x)'' else keyFile; |
| 34 | + |
| 35 | + generateKeyFileArgs = keyFile: '' |
29 | 36 | ${lib.optionalString (keyFile != null) "--key-file ${keyFile}"} \ |
30 | 37 | ${lib.optionalString (lib.hasAttr "keyFileSize" config.settings) "--keyfile-size ${builtins.toString config.settings.keyFileSize}"} \ |
31 | 38 | ${lib.optionalString (lib.hasAttr "keyFileOffset" config.settings) "--keyfile-offset ${builtins.toString config.settings.keyFileOffset}"} \ |
32 | 39 | ''; |
33 | | - cryptsetupOpen = '' |
| 40 | + |
| 41 | + # This is the one used for standard one shot formatting and mounting. |
| 42 | + keyFileArgs = generateKeyFileArgs keyFile; |
| 43 | + # This is the one used for 2-staged formatting like FIDO2 and NEVER for mounting. |
| 44 | + formatKeyFileArgs = generateKeyFileArgs formatKeyFile; |
| 45 | + |
| 46 | + # --token-only forces to try FIRST the token then passphrase. |
| 47 | + createOpenCommand = keyFileArgs: '' |
34 | 48 | cryptsetup open "${config.device}" "${config.name}" \ |
| 49 | + ${lib.optionalString (config.enrollFido2 or false) "--token-only"} \ |
35 | 50 | ${lib.optionalString (config.settings.allowDiscards or false) "--allow-discards"} \ |
36 | 51 | ${ |
37 | 52 | lib.optionalString (config.settings.bypassWorkqueues or false |
|
40 | 55 | ${toString config.extraOpenArgs} \ |
41 | 56 | ${keyFileArgs} \ |
42 | 57 | ''; |
| 58 | + |
| 59 | + # Use this open command when you want to open it after full enrollment, e.g. at mount time or in standard enrollments. |
| 60 | + cryptsetupOpen = createOpenCommand keyFileArgs; |
| 61 | + |
| 62 | + # Use this open command when you want to open it immediately after the formatting and before the stage 2 process is finished (i.e. the wipe slot). |
| 63 | + formatCryptsetupOpen = createOpenCommand formatKeyFileArgs; |
43 | 64 | in |
44 | 65 | { |
45 | 66 | options = { |
|
71 | 92 | }; |
72 | 93 | askPassword = lib.mkOption { |
73 | 94 | type = lib.types.bool; |
74 | | - default = config.keyFile == null && config.passwordFile == null && (!config.settings ? "keyFile"); |
75 | | - defaultText = "true if neither keyFile nor passwordFile are set"; |
| 95 | + default = config.keyFile == null && config.passwordFile == null && (!config.settings ? "keyFile") && !autogeneratedPassword; |
| 96 | + defaultText = "true if neither keyFile nor passwordFile nor enrollFido2 are set"; |
76 | 97 | description = "Whether to ask for a password for initial encryption"; |
77 | 98 | }; |
| 99 | + enrollFido2 = lib.mkOption { |
| 100 | + type = lib.types.bool; |
| 101 | + default = false; |
| 102 | + description = "Whether to enroll a FIDO2 token and use it"; |
| 103 | + }; |
| 104 | + extraFido2EnrollArgs = lib.mkOption { |
| 105 | + type = lib.types.listOf lib.types.str; |
| 106 | + default = [ ]; |
| 107 | + example = [ |
| 108 | + "--fido2-parameters-in-header=false" |
| 109 | + ]; |
| 110 | + description = "Extra arguments to pass to `systemd-cryptenroll` when enrolling the FIDO2 device"; |
| 111 | + }; |
| 112 | + enrollRecovery = lib.mkOption { |
| 113 | + type = lib.types.bool; |
| 114 | + default = config.enrollFido2; |
| 115 | + defaultText = "true if fido2 is enabled"; |
| 116 | + description = "Whether to enroll an automatic (keyboard layout independent) recovery passphrase with high entropy and print a QR code on screen to take it"; |
| 117 | + }; |
78 | 118 | settings = lib.mkOption { |
79 | 119 | type = lib.types.attrsOf lib.types.anything; |
80 | 120 | default = { }; |
|
159 | 199 | echo "Passwords did not match, please try again." |
160 | 200 | done |
161 | 201 | ''} |
162 | | - cryptsetup -q luksFormat "${config.device}" ${toString config.extraFormatArgs} ${keyFileArgs} |
| 202 | + ${lib.optionalString autogeneratedPassword '' |
| 203 | + # Generate a random throwable key that will be removed later on. |
| 204 | + set +x |
| 205 | + password=$(openssl rand -hex 32) |
| 206 | + export password |
| 207 | + set -x |
| 208 | +
|
| 209 | + # We have the guarantee that slot 0 needs to be deleted later on. |
| 210 | + # If the user had set its own password, we wouldn't create this variable |
| 211 | + # and the script later will not wipe the slot zero. The user keep his password. |
| 212 | + export SLOT_ZERO_TO_DELETE=true |
| 213 | + ''} |
| 214 | + cryptsetup -q luksFormat "${config.device}" ${toString config.extraFormatArgs} ${formatKeyFileArgs} |
163 | 215 | fi |
164 | 216 |
|
165 | 217 | if ! cryptsetup status "${config.name}" >/dev/null; then |
166 | | - ${cryptsetupOpen} --persistent |
| 218 | + ${formatCryptsetupOpen} \ |
| 219 | + --persistent |
167 | 220 | fi |
| 221 | +
|
168 | 222 | ${toString ( |
169 | 223 | lib.forEach config.additionalKeyFiles (keyFile: '' |
170 | | - cryptsetup luksAddKey "${config.device}" ${keyFile} ${keyFileArgs} |
| 224 | + cryptsetup luksAddKey "${config.device}" ${keyFile} ${formatKeyFileArgs} |
171 | 225 | '') |
172 | 226 | )} |
173 | 227 |
|
| 228 | + ${lib.optionalString config.enrollRecovery '' |
| 229 | + systemd-cryptenroll \ |
| 230 | + --recovery-key \ |
| 231 | + --unlock-key-file=${formatKeyFile} \ |
| 232 | + "${config.device}" |
| 233 | +
|
| 234 | + read -p "Press Enter when you scanned the QR code offscreen or that the recovery key is stored securely." |
| 235 | + ''} |
| 236 | + ${lib.optionalString config.enrollFido2 '' |
| 237 | + wait_for_token() { |
| 238 | + echo "Waiting for FIDO2 token insertion..." |
| 239 | +
|
| 240 | + # Check if any FIDO2 device is available via /dev/hidraw* |
| 241 | + while true; do |
| 242 | + if ls /dev/hidraw* &>/dev/null; then |
| 243 | + echo "FIDO2 device detected." |
| 244 | + break |
| 245 | + else |
| 246 | + echo "FIDO2 device not detected, waiting..." |
| 247 | + sleep 2 |
| 248 | + fi |
| 249 | + done |
| 250 | + } |
| 251 | +
|
| 252 | + wait_for_token |
| 253 | + systemd-cryptenroll \ |
| 254 | + --fido2-device=auto \ |
| 255 | + ''${SLOT_ZERO_TO_DELETE:+--wipe-slot=0} \ |
| 256 | + --unlock-key-file=${formatKeyFile} \ |
| 257 | + ${toString config.extraFido2EnrollArgs} \ |
| 258 | + "${config.device}" |
| 259 | + ''} |
174 | 260 | ${lib.optionalString (config.content != null) config.content._create} |
175 | 261 | ''; |
176 | 262 | }; |
|
226 | 312 | { |
227 | 313 | boot.initrd.luks.devices.${config.name} = { |
228 | 314 | inherit (config) device; |
| 315 | + crypttabExtraOpts = lib.mkIf config.enrollFido2 [ "fido2-device=auto" ]; |
229 | 316 | } // config.settings; |
| 317 | + |
| 318 | + # If FIDO2 is used, systemd stage 1 is absolutely necessary. |
| 319 | + # Should we turn this into an assertion? |
| 320 | + boot.initrd.systemd.enable = config.enrollFido2; |
230 | 321 | } |
231 | 322 | ]) |
232 | 323 | ++ (lib.optional (config.content != null) config.content._config); |
|
240 | 331 | pkgs: |
241 | 332 | [ |
242 | 333 | pkgs.gnugrep |
243 | | - pkgs.cryptsetup |
| 334 | + pkgs.openssl |
| 335 | + pkgs.systemd |
| 336 | + # We make cryptsetup aware of token libraries from systemd. |
| 337 | + # We do not have a lot of nice ways to do this... |
| 338 | + (pkgs.runCommandNoCC pkgs.cryptsetup.name { |
| 339 | + nativeBuildInputs = [ pkgs.makeWrapper ]; |
| 340 | + } '' |
| 341 | + mkdir -p $out/bin/ |
| 342 | + makeWrapper ${pkgs.cryptsetup.bin}/bin/cryptsetup $out/bin/cryptsetup \ |
| 343 | + --prefix LD_LIBRARY_PATH : ${pkgs.systemd}/lib/cryptsetup |
| 344 | + '') |
244 | 345 | ] |
245 | 346 | ++ (lib.optionals (config.content != null) (config.content._pkgs pkgs)); |
246 | 347 | description = "Packages"; |
|
0 commit comments