|
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, tokenType ? null }: '' |
34 | 48 | cryptsetup open "${config.device}" "${config.name}" \ |
| 49 | + ${lib.optionalString (tokenType != null) "--token-type ${tokenType}"} \ |
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 { |
| 61 | + inherit keyFileArgs; |
| 62 | + tokenType = if config.enrollFido2 then "systemd-fido2" else null; |
| 63 | + }; |
| 64 | + |
| 65 | + # 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). |
| 66 | + formatCryptsetupOpen = createOpenCommand { |
| 67 | + keyFileArgs = formatKeyFileArgs; |
| 68 | + }; |
43 | 69 | in |
44 | 70 | { |
45 | 71 | options = { |
|
71 | 97 | }; |
72 | 98 | askPassword = lib.mkOption { |
73 | 99 | 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"; |
| 100 | + default = config.keyFile == null && config.passwordFile == null && (!config.settings ? "keyFile") && !autogeneratedPassword; |
| 101 | + defaultText = "true if neither keyFile nor passwordFile nor enrollFido2 are set"; |
76 | 102 | description = "Whether to ask for a password for initial encryption"; |
77 | 103 | }; |
| 104 | + enrollFido2 = lib.mkOption { |
| 105 | + type = lib.types.bool; |
| 106 | + default = false; |
| 107 | + description = "Whether to enroll a FIDO2 token and use it"; |
| 108 | + }; |
| 109 | + extraFido2EnrollArgs = lib.mkOption { |
| 110 | + type = lib.types.listOf lib.types.str; |
| 111 | + default = [ ]; |
| 112 | + example = [ |
| 113 | + "--fido2-parameters-in-header=false" |
| 114 | + ]; |
| 115 | + description = "Extra arguments to pass to `systemd-cryptenroll` when enrolling the FIDO2 device"; |
| 116 | + }; |
| 117 | + enrollRecovery = lib.mkOption { |
| 118 | + type = lib.types.bool; |
| 119 | + default = config.enrollFido2; |
| 120 | + defaultText = "true if fido2 is enabled"; |
| 121 | + description = "Whether to enroll an automatic (keyboard layout independent) recovery passphrase with high entropy and print a QR code on screen to take it"; |
| 122 | + }; |
78 | 123 | settings = lib.mkOption { |
79 | 124 | type = lib.types.attrsOf lib.types.anything; |
80 | 125 | default = { }; |
|
159 | 204 | echo "Passwords did not match, please try again." |
160 | 205 | done |
161 | 206 | ''} |
162 | | - cryptsetup -q luksFormat "${config.device}" ${toString config.extraFormatArgs} ${keyFileArgs} |
| 207 | + ${lib.optionalString autogeneratedPassword '' |
| 208 | + # Generate a random throwable key that will be removed later on. |
| 209 | + set +x |
| 210 | + password=$(openssl rand -hex 32) |
| 211 | + export password |
| 212 | + set -x |
| 213 | +
|
| 214 | + # We have the guarantee that slot 0 needs to be deleted later on. |
| 215 | + # If the user had set its own password, we wouldn't create this variable |
| 216 | + # and the script later will not wipe the slot zero. The user keep his password. |
| 217 | + export SLOT_ZERO_TO_DELETE=true |
| 218 | + ''} |
| 219 | + cryptsetup -q luksFormat "${config.device}" ${toString config.extraFormatArgs} ${formatKeyFileArgs} |
163 | 220 | fi |
164 | 221 |
|
165 | 222 | if ! cryptsetup status "${config.name}" >/dev/null; then |
166 | | - ${cryptsetupOpen} --persistent |
| 223 | + ${formatCryptsetupOpen} \ |
| 224 | + --persistent |
167 | 225 | fi |
| 226 | +
|
168 | 227 | ${toString ( |
169 | 228 | lib.forEach config.additionalKeyFiles (keyFile: '' |
170 | | - cryptsetup luksAddKey "${config.device}" ${keyFile} ${keyFileArgs} |
| 229 | + cryptsetup luksAddKey "${config.device}" ${keyFile} ${formatKeyFileArgs} |
171 | 230 | '') |
172 | 231 | )} |
173 | 232 |
|
| 233 | + ${lib.optionalString config.enrollRecovery '' |
| 234 | + systemd-cryptenroll \ |
| 235 | + --recovery-key \ |
| 236 | + --unlock-key-file=${formatKeyFile} \ |
| 237 | + "${config.device}" |
| 238 | +
|
| 239 | + read -p "Press Enter when you scanned the QR code offscreen or that the recovery key is stored securely." |
| 240 | + ''} |
| 241 | + ${lib.optionalString config.enrollFido2 '' |
| 242 | + wait_for_token() { |
| 243 | + echo "Waiting for FIDO2 token insertion..." |
| 244 | +
|
| 245 | + # Check if any FIDO2 device is available via /dev/hidraw* |
| 246 | + while true; do |
| 247 | + if ls /dev/hidraw* &>/dev/null; then |
| 248 | + echo "FIDO2 device detected." |
| 249 | + break |
| 250 | + else |
| 251 | + echo "FIDO2 device not detected, waiting..." |
| 252 | + sleep 2 |
| 253 | + fi |
| 254 | + done |
| 255 | + } |
| 256 | +
|
| 257 | + wait_for_token |
| 258 | + systemd-cryptenroll \ |
| 259 | + --fido2-device=auto \ |
| 260 | + ''${SLOT_ZERO_TO_DELETE:+--wipe-slot=0} \ |
| 261 | + --unlock-key-file=${formatKeyFile} \ |
| 262 | + ${toString config.extraFido2EnrollArgs} \ |
| 263 | + "${config.device}" |
| 264 | + ''} |
174 | 265 | ${lib.optionalString (config.content != null) config.content._create} |
175 | 266 | ''; |
176 | 267 | }; |
|
226 | 317 | { |
227 | 318 | boot.initrd.luks.devices.${config.name} = { |
228 | 319 | inherit (config) device; |
| 320 | + crypttabExtraOpts = lib.mkIf config.enrollFido2 [ "fido2-device=auto" ]; |
229 | 321 | } // config.settings; |
| 322 | + |
| 323 | + # If FIDO2 is used, systemd stage 1 is absolutely necessary. |
| 324 | + # Should we turn this into an assertion? |
| 325 | + boot.initrd.systemd.enable = config.enrollFido2; |
230 | 326 | } |
231 | 327 | ]) |
232 | 328 | ++ (lib.optional (config.content != null) config.content._config); |
|
240 | 336 | pkgs: |
241 | 337 | [ |
242 | 338 | pkgs.gnugrep |
243 | | - pkgs.cryptsetup |
| 339 | + pkgs.openssl |
| 340 | + pkgs.systemd |
| 341 | + # We make cryptsetup aware of token libraries from systemd. |
| 342 | + # We do not have a lot of nice ways to do this... |
| 343 | + (pkgs.runCommandNoCC pkgs.cryptsetup.name { |
| 344 | + nativeBuildInputs = [ pkgs.makeWrapper ]; |
| 345 | + } '' |
| 346 | + mkdir -p $out/bin/ |
| 347 | + makeWrapper ${pkgs.cryptsetup.bin}/bin/cryptsetup $out/bin/cryptsetup \ |
| 348 | + --prefix LD_LIBRARY_PATH : ${pkgs.systemd}/lib/cryptsetup |
| 349 | + '') |
244 | 350 | ] |
245 | 351 | ++ (lib.optionals (config.content != null) (config.content._pkgs pkgs)); |
246 | 352 | description = "Packages"; |
|
0 commit comments