1{ config, options, lib, pkgs, ... }:
2
3with lib;
4
5let
6 luks = config.boot.initrd.luks;
7 kernelPackages = config.boot.kernelPackages;
8 defaultPrio = (mkOptionDefault {}).priority;
9
10 commonFunctions = ''
11 die() {
12 echo "$@" >&2
13 exit 1
14 }
15
16 dev_exist() {
17 local target="$1"
18 if [ -e $target ]; then
19 return 0
20 else
21 local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g')
22 blkid --uuid $uuid >/dev/null
23 return $?
24 fi
25 }
26
27 wait_target() {
28 local name="$1"
29 local target="$2"
30 local secs="''${3:-10}"
31 local desc="''${4:-$name $target to appear}"
32
33 if ! dev_exist $target; then
34 echo -n "Waiting $secs seconds for $desc..."
35 local success=false;
36 for try in $(seq $secs); do
37 echo -n "."
38 sleep 1
39 if dev_exist $target; then
40 success=true
41 break
42 fi
43 done
44 if [ $success == true ]; then
45 echo " - success";
46 return 0
47 else
48 echo " - failure";
49 return 1
50 fi
51 fi
52 return 0
53 }
54
55 wait_yubikey() {
56 local secs="''${1:-10}"
57
58 ykinfo -v 1>/dev/null 2>&1
59 if [ $? != 0 ]; then
60 echo -n "Waiting $secs seconds for YubiKey to appear..."
61 local success=false
62 for try in $(seq $secs); do
63 echo -n .
64 sleep 1
65 ykinfo -v 1>/dev/null 2>&1
66 if [ $? == 0 ]; then
67 success=true
68 break
69 fi
70 done
71 if [ $success == true ]; then
72 echo " - success";
73 return 0
74 else
75 echo " - failure";
76 return 1
77 fi
78 fi
79 return 0
80 }
81
82 wait_gpgcard() {
83 local secs="''${1:-10}"
84
85 gpg --card-status > /dev/null 2> /dev/null
86 if [ $? != 0 ]; then
87 echo -n "Waiting $secs seconds for GPG Card to appear"
88 local success=false
89 for try in $(seq $secs); do
90 echo -n .
91 sleep 1
92 gpg --card-status > /dev/null 2> /dev/null
93 if [ $? == 0 ]; then
94 success=true
95 break
96 fi
97 done
98 if [ $success == true ]; then
99 echo " - success";
100 return 0
101 else
102 echo " - failure";
103 return 1
104 fi
105 fi
106 return 0
107 }
108 '';
109
110 preCommands = ''
111 # A place to store crypto things
112
113 # A ramfs is used here to ensure that the file used to update
114 # the key slot with cryptsetup will never get swapped out.
115 # Warning: Do NOT replace with tmpfs!
116 mkdir -p /crypt-ramfs
117 mount -t ramfs none /crypt-ramfs
118
119 # Cryptsetup locking directory
120 mkdir -p /run/cryptsetup
121
122 # For YubiKey salt storage
123 mkdir -p /crypt-storage
124
125 ${optionalString luks.gpgSupport ''
126 export GPG_TTY=$(tty)
127 export GNUPGHOME=/crypt-ramfs/.gnupg
128
129 gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null
130 ''}
131
132 # Disable all input echo for the whole stage. We could use read -s
133 # instead but that would occasionally leak characters between read
134 # invocations.
135 stty -echo
136 '';
137
138 postCommands = ''
139 stty echo
140 umount /crypt-storage 2>/dev/null
141 umount /crypt-ramfs 2>/dev/null
142 '';
143
144 openCommand = name: dev: assert name == dev.name;
145 let
146 csopen = "cryptsetup luksOpen ${dev.device} ${dev.name}"
147 + optionalString dev.allowDiscards " --allow-discards"
148 + optionalString dev.bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue"
149 + optionalString (dev.header != null) " --header=${dev.header}";
150 cschange = "cryptsetup luksChangeKey ${dev.device} ${optionalString (dev.header != null) "--header=${dev.header}"}";
151 fido2luksCredentials = dev.fido2.credentials ++ optional (dev.fido2.credential != null) dev.fido2.credential;
152 in ''
153 # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
154 # if on a USB drive.
155 wait_target "device" ${dev.device} || die "${dev.device} is unavailable"
156
157 ${optionalString (dev.header != null) ''
158 wait_target "header" ${dev.header} || die "${dev.header} is unavailable"
159 ''}
160
161 try_empty_passphrase() {
162 ${if dev.tryEmptyPassphrase then ''
163 echo "Trying empty passphrase!"
164 echo "" | ${csopen}
165 cs_status=$?
166 if [ $cs_status -eq 0 ]; then
167 return 0
168 else
169 return 1
170 fi
171 '' else "return 1"}
172 }
173
174
175 do_open_passphrase() {
176 local passphrase
177
178 while true; do
179 echo -n "Passphrase for ${dev.device}: "
180 passphrase=
181 while true; do
182 if [ -e /crypt-ramfs/passphrase ]; then
183 echo "reused"
184 passphrase=$(cat /crypt-ramfs/passphrase)
185 break
186 else
187 # ask cryptsetup-askpass
188 echo -n "${dev.device}" > /crypt-ramfs/device
189
190 # and try reading it from /dev/console with a timeout
191 IFS= read -t 1 -r passphrase
192 if [ -n "$passphrase" ]; then
193 ${if luks.reusePassphrases then ''
194 # remember it for the next device
195 echo -n "$passphrase" > /crypt-ramfs/passphrase
196 '' else ''
197 # Don't save it to ramfs. We are very paranoid
198 ''}
199 echo
200 break
201 fi
202 fi
203 done
204 echo -n "Verifying passphrase for ${dev.device}..."
205 echo -n "$passphrase" | ${csopen} --key-file=-
206 if [ $? == 0 ]; then
207 echo " - success"
208 ${if luks.reusePassphrases then ''
209 # we don't rm here because we might reuse it for the next device
210 '' else ''
211 rm -f /crypt-ramfs/passphrase
212 ''}
213 break
214 else
215 echo " - failure"
216 # ask for a different one
217 rm -f /crypt-ramfs/passphrase
218 fi
219 done
220 }
221
222 # LUKS
223 open_normally() {
224 ${if (dev.keyFile != null) then ''
225 if wait_target "key file" ${dev.keyFile}; then
226 ${csopen} --key-file=${dev.keyFile} \
227 ${optionalString (dev.keyFileSize != null) "--keyfile-size=${toString dev.keyFileSize}"} \
228 ${optionalString (dev.keyFileOffset != null) "--keyfile-offset=${toString dev.keyFileOffset}"}
229 cs_status=$?
230 if [ $cs_status -ne 0 ]; then
231 echo "Key File ${dev.keyFile} failed!"
232 if ! try_empty_passphrase; then
233 ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
234 echo " - failing back to interactive password prompt"
235 do_open_passphrase
236 fi
237 fi
238 else
239 # If the key file never shows up we should also try the empty passphrase
240 if ! try_empty_passphrase; then
241 ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
242 echo " - failing back to interactive password prompt"
243 do_open_passphrase
244 fi
245 fi
246 '' else ''
247 if ! try_empty_passphrase; then
248 do_open_passphrase
249 fi
250 ''}
251 }
252
253 ${optionalString (luks.yubikeySupport && (dev.yubikey != null)) ''
254 # YubiKey
255 rbtohex() {
256 ( od -An -vtx1 | tr -d ' \n' )
257 }
258
259 hextorb() {
260 ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
261 }
262
263 do_open_yubikey() {
264 # Make all of these local to this function
265 # to prevent their values being leaked
266 local salt
267 local iterations
268 local k_user
269 local challenge
270 local response
271 local k_luks
272 local opened
273 local new_salt
274 local new_iterations
275 local new_challenge
276 local new_response
277 local new_k_luks
278
279 mount -t ${dev.yubikey.storage.fsType} ${dev.yubikey.storage.device} /crypt-storage || \
280 die "Failed to mount YubiKey salt storage device"
281
282 salt="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 1p | tr -d '\n')"
283 iterations="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 2p | tr -d '\n')"
284 challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
285 response="$(ykchalresp -${toString dev.yubikey.slot} -x $challenge 2>/dev/null)"
286
287 for try in $(seq 3); do
288 ${optionalString dev.yubikey.twoFactor ''
289 echo -n "Enter two-factor passphrase: "
290 k_user=
291 while true; do
292 if [ -e /crypt-ramfs/passphrase ]; then
293 echo "reused"
294 k_user=$(cat /crypt-ramfs/passphrase)
295 break
296 else
297 # Try reading it from /dev/console with a timeout
298 IFS= read -t 1 -r k_user
299 if [ -n "$k_user" ]; then
300 ${if luks.reusePassphrases then ''
301 # Remember it for the next device
302 echo -n "$k_user" > /crypt-ramfs/passphrase
303 '' else ''
304 # Don't save it to ramfs. We are very paranoid
305 ''}
306 echo
307 break
308 fi
309 fi
310 done
311 ''}
312
313 if [ ! -z "$k_user" ]; then
314 k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)"
315 else
316 k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)"
317 fi
318
319 echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
320
321 if [ $? == 0 ]; then
322 opened=true
323 ${if luks.reusePassphrases then ''
324 # We don't rm here because we might reuse it for the next device
325 '' else ''
326 rm -f /crypt-ramfs/passphrase
327 ''}
328 break
329 else
330 opened=false
331 echo "Authentication failed!"
332 fi
333 done
334
335 [ "$opened" == false ] && die "Maximum authentication errors reached"
336
337 echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
338 for i in $(seq ${toString dev.yubikey.saltLength}); do
339 byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
340 new_salt="$new_salt$byte";
341 echo -n .
342 done;
343 echo "ok"
344
345 new_iterations="$iterations"
346 ${optionalString (dev.yubikey.iterationStep > 0) ''
347 new_iterations="$(($new_iterations + ${toString dev.yubikey.iterationStep}))"
348 ''}
349
350 new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
351
352 new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)"
353
354 if [ -z "$new_response" ]; then
355 echo "Warning: Unable to generate new challenge response, current challenge persists!"
356 umount /crypt-storage
357 return
358 fi
359
360 if [ ! -z "$k_user" ]; then
361 new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
362 else
363 new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
364 fi
365
366 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
367 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
368
369 if [ $? == 0 ]; then
370 echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path}
371 sync /crypt-storage${dev.yubikey.storage.path}
372 else
373 echo "Warning: Could not update LUKS key, current challenge persists!"
374 fi
375
376 rm -f /crypt-ramfs/new_key
377 umount /crypt-storage
378 }
379
380 open_with_hardware() {
381 if wait_yubikey ${toString dev.yubikey.gracePeriod}; then
382 do_open_yubikey
383 else
384 echo "No YubiKey found, falling back to non-YubiKey open procedure"
385 open_normally
386 fi
387 }
388 ''}
389
390 ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) ''
391
392 do_open_gpg_card() {
393 # Make all of these local to this function
394 # to prevent their values being leaked
395 local pin
396 local opened
397
398 gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null
399
400 gpg --card-status > /dev/null 2> /dev/null
401
402 for try in $(seq 3); do
403 echo -n "PIN for GPG Card associated with device ${dev.device}: "
404 pin=
405 while true; do
406 if [ -e /crypt-ramfs/passphrase ]; then
407 echo "reused"
408 pin=$(cat /crypt-ramfs/passphrase)
409 break
410 else
411 # and try reading it from /dev/console with a timeout
412 IFS= read -t 1 -r pin
413 if [ -n "$pin" ]; then
414 ${if luks.reusePassphrases then ''
415 # remember it for the next device
416 echo -n "$pin" > /crypt-ramfs/passphrase
417 '' else ''
418 # Don't save it to ramfs. We are very paranoid
419 ''}
420 echo
421 break
422 fi
423 fi
424 done
425 echo -n "Verifying passphrase for ${dev.device}..."
426 echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${dev.device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
427 if [ $? == 0 ]; then
428 echo " - success"
429 ${if luks.reusePassphrases then ''
430 # we don't rm here because we might reuse it for the next device
431 '' else ''
432 rm -f /crypt-ramfs/passphrase
433 ''}
434 break
435 else
436 echo " - failure"
437 # ask for a different one
438 rm -f /crypt-ramfs/passphrase
439 fi
440 done
441
442 [ "$opened" == false ] && die "Maximum authentication errors reached"
443 }
444
445 open_with_hardware() {
446 if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then
447 do_open_gpg_card
448 else
449 echo "No GPG Card found, falling back to normal open procedure"
450 open_normally
451 fi
452 }
453 ''}
454
455 ${optionalString (luks.fido2Support && fido2luksCredentials != []) ''
456
457 open_with_hardware() {
458 local passsphrase
459
460 ${if dev.fido2.passwordLess then ''
461 export passphrase=""
462 '' else ''
463 read -rsp "FIDO2 salt for ${dev.device}: " passphrase
464 echo
465 ''}
466 ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
467 echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest."
468 echo "Please move your mouse to create needed randomness."
469 ''}
470 echo "Waiting for your FIDO2 device..."
471 fido2luks open${optionalString dev.allowDiscards " --allow-discards"} ${dev.device} ${dev.name} "${builtins.concatStringsSep "," fido2luksCredentials}" --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase
472 if [ $? -ne 0 ]; then
473 echo "No FIDO2 key found, falling back to normal open procedure"
474 open_normally
475 fi
476 }
477 ''}
478
479 # commands to run right before we mount our device
480 ${dev.preOpenCommands}
481
482 ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && fido2luksCredentials != []) then ''
483 open_with_hardware
484 '' else ''
485 open_normally
486 ''}
487
488 # commands to run right after we mounted our device
489 ${dev.postOpenCommands}
490 '';
491
492 askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
493 #!/bin/sh
494
495 ${commonFunctions}
496
497 while true; do
498 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
499 device=$(cat /crypt-ramfs/device)
500
501 echo -n "Passphrase for $device: "
502 IFS= read -rs passphrase
503 echo
504
505 rm /crypt-ramfs/device
506 echo -n "$passphrase" > /crypt-ramfs/passphrase
507 done
508 '';
509
510 preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
511 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
512
513
514 stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let
515 opts = v.crypttabExtraOpts
516 ++ optional v.allowDiscards "discard"
517 ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
518 ++ optional (v.header != null) "header=${v.header}"
519 ++ optional (v.keyFileOffset != null) "keyfile-offset=${toString v.keyFileOffset}"
520 ++ optional (v.keyFileSize != null) "keyfile-size=${toString v.keyFileSize}"
521 ++ optional (v.keyFileTimeout != null) "keyfile-timeout=${builtins.toString v.keyFileTimeout}s"
522 ++ optional (v.tryEmptyPassphrase) "try-empty-password=true"
523 ;
524 in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
525
526in
527{
528 imports = [
529 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
530 ];
531
532 options = {
533
534 boot.initrd.luks.mitigateDMAAttacks = mkOption {
535 type = types.bool;
536 default = true;
537 description = lib.mdDoc ''
538 Unless enabled, encryption keys can be easily recovered by an attacker with physical
539 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
540 More information is available at <https://en.wikipedia.org/wiki/DMA_attack>.
541
542 This option blacklists FireWire drivers, but doesn't remove them. You can manually
543 load the drivers if you need to use a FireWire device, but don't forget to unload them!
544 '';
545 };
546
547 boot.initrd.luks.cryptoModules = mkOption {
548 type = types.listOf types.str;
549 default =
550 [ "aes" "aes_generic" "blowfish" "twofish"
551 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
552 "af_alg" "algif_skcipher"
553 ];
554 description = lib.mdDoc ''
555 A list of cryptographic kernel modules needed to decrypt the root device(s).
556 The default includes all common modules.
557 '';
558 };
559
560 boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
561 type = types.bool;
562 default = false;
563 internal = true;
564 description = lib.mdDoc ''
565 Whether to configure luks support in the initrd, when no luks
566 devices are configured.
567 '';
568 };
569
570 boot.initrd.luks.reusePassphrases = mkOption {
571 type = types.bool;
572 default = true;
573 description = lib.mdDoc ''
574 When opening a new LUKS device try reusing last successful
575 passphrase.
576
577 Useful for mounting a number of devices that use the same
578 passphrase without retyping it several times.
579
580 Such setup can be useful if you use {command}`cryptsetup luksSuspend`.
581 Different LUKS devices will still have
582 different master keys even when using the same passphrase.
583 '';
584 };
585
586 boot.initrd.luks.devices = mkOption {
587 default = { };
588 example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
589 description = lib.mdDoc ''
590 The encrypted disk that should be opened before the root
591 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
592 setups are supported. The unencrypted devices can be accessed as
593 {file}`/dev/mapper/«name»`.
594 '';
595
596 type = with types; attrsOf (submodule (
597 { name, ... }: { options = {
598
599 name = mkOption {
600 visible = false;
601 default = name;
602 example = "luksroot";
603 type = types.str;
604 description = lib.mdDoc "Name of the unencrypted device in {file}`/dev/mapper`.";
605 };
606
607 device = mkOption {
608 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
609 type = types.str;
610 description = lib.mdDoc "Path of the underlying encrypted block device.";
611 };
612
613 header = mkOption {
614 default = null;
615 example = "/root/header.img";
616 type = types.nullOr types.str;
617 description = lib.mdDoc ''
618 The name of the file or block device that
619 should be used as header for the encrypted device.
620 '';
621 };
622
623 keyFile = mkOption {
624 default = null;
625 example = "/dev/sdb1";
626 type = types.nullOr types.str;
627 description = lib.mdDoc ''
628 The name of the file (can be a raw device or a partition) that
629 should be used as the decryption key for the encrypted device. If
630 not specified, you will be prompted for a passphrase instead.
631 '';
632 };
633
634 tryEmptyPassphrase = mkOption {
635 default = false;
636 type = types.bool;
637 description = lib.mdDoc ''
638 If keyFile fails then try an empty passphrase first before
639 prompting for password.
640 '';
641 };
642
643 keyFileTimeout = mkOption {
644 default = null;
645 example = 5;
646 type = types.nullOr types.int;
647 description = lib.mdDoc ''
648 The amount of time in seconds for a keyFile to appear before
649 timing out and trying passwords.
650 '';
651 };
652
653 keyFileSize = mkOption {
654 default = null;
655 example = 4096;
656 type = types.nullOr types.int;
657 description = lib.mdDoc ''
658 The size of the key file. Use this if only the beginning of the
659 key file should be used as a key (often the case if a raw device
660 or partition is used as key file). If not specified, the whole
661 `keyFile` will be used decryption, instead of just
662 the first `keyFileSize` bytes.
663 '';
664 };
665
666 keyFileOffset = mkOption {
667 default = null;
668 example = 4096;
669 type = types.nullOr types.int;
670 description = lib.mdDoc ''
671 The offset of the key file. Use this in combination with
672 `keyFileSize` to use part of a file as key file
673 (often the case if a raw device or partition is used as a key file).
674 If not specified, the key begins at the first byte of
675 `keyFile`.
676 '';
677 };
678
679 # FIXME: get rid of this option.
680 preLVM = mkOption {
681 default = true;
682 type = types.bool;
683 description = lib.mdDoc "Whether the luksOpen will be attempted before LVM scan or after it.";
684 };
685
686 allowDiscards = mkOption {
687 default = false;
688 type = types.bool;
689 description = lib.mdDoc ''
690 Whether to allow TRIM requests to the underlying device. This option
691 has security implications; please read the LUKS documentation before
692 activating it.
693 This option is incompatible with authenticated encryption (dm-crypt
694 stacked over dm-integrity).
695 '';
696 };
697
698 bypassWorkqueues = mkOption {
699 default = false;
700 type = types.bool;
701 description = lib.mdDoc ''
702 Whether to bypass dm-crypt's internal read and write workqueues.
703 Enabling this should improve performance on SSDs; see
704 [here](https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance)
705 for more information. Needs Linux 5.9 or later.
706 '';
707 };
708
709 fallbackToPassword = mkOption {
710 default = false;
711 type = types.bool;
712 description = lib.mdDoc ''
713 Whether to fallback to interactive passphrase prompt if the keyfile
714 cannot be found. This will prevent unattended boot should the keyfile
715 go missing.
716 '';
717 };
718
719 gpgCard = mkOption {
720 default = null;
721 description = lib.mdDoc ''
722 The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
723 If null (the default), GPG-Smartcard will be disabled for this device.
724 '';
725
726 type = with types; nullOr (submodule {
727 options = {
728 gracePeriod = mkOption {
729 default = 10;
730 type = types.int;
731 description = lib.mdDoc "Time in seconds to wait for the GPG Smartcard.";
732 };
733
734 encryptedPass = mkOption {
735 type = types.path;
736 description = lib.mdDoc "Path to the GPG encrypted passphrase.";
737 };
738
739 publicKey = mkOption {
740 type = types.path;
741 description = lib.mdDoc "Path to the Public Key.";
742 };
743 };
744 });
745 };
746
747 fido2 = {
748 credential = mkOption {
749 default = null;
750 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
751 type = types.nullOr types.str;
752 description = lib.mdDoc "The FIDO2 credential ID.";
753 };
754
755 credentials = mkOption {
756 default = [];
757 example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ];
758 type = types.listOf types.str;
759 description = lib.mdDoc ''
760 List of FIDO2 credential IDs.
761
762 Use this if you have multiple FIDO2 keys you want to use for the same luks device.
763 '';
764 };
765
766 gracePeriod = mkOption {
767 default = 10;
768 type = types.int;
769 description = lib.mdDoc "Time in seconds to wait for the FIDO2 key.";
770 };
771
772 passwordLess = mkOption {
773 default = false;
774 type = types.bool;
775 description = lib.mdDoc ''
776 Defines whatever to use an empty string as a default salt.
777
778 Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/).
779 '';
780 };
781 };
782
783 yubikey = mkOption {
784 default = null;
785 description = lib.mdDoc ''
786 The options to use for this LUKS device in YubiKey-PBA.
787 If null (the default), YubiKey-PBA will be disabled for this device.
788 '';
789
790 type = with types; nullOr (submodule {
791 options = {
792 twoFactor = mkOption {
793 default = true;
794 type = types.bool;
795 description = lib.mdDoc "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
796 };
797
798 slot = mkOption {
799 default = 2;
800 type = types.int;
801 description = lib.mdDoc "Which slot on the YubiKey to challenge.";
802 };
803
804 saltLength = mkOption {
805 default = 16;
806 type = types.int;
807 description = lib.mdDoc "Length of the new salt in byte (64 is the effective maximum).";
808 };
809
810 keyLength = mkOption {
811 default = 64;
812 type = types.int;
813 description = lib.mdDoc "Length of the LUKS slot key derived with PBKDF2 in byte.";
814 };
815
816 iterationStep = mkOption {
817 default = 0;
818 type = types.int;
819 description = lib.mdDoc "How much the iteration count for PBKDF2 is increased at each successful authentication.";
820 };
821
822 gracePeriod = mkOption {
823 default = 10;
824 type = types.int;
825 description = lib.mdDoc "Time in seconds to wait for the YubiKey.";
826 };
827
828 /* TODO: Add to the documentation of the current module:
829
830 Options related to the storing the salt.
831 */
832 storage = {
833 device = mkOption {
834 default = "/dev/sda1";
835 type = types.path;
836 description = lib.mdDoc ''
837 An unencrypted device that will temporarily be mounted in stage-1.
838 Must contain the current salt to create the challenge for this LUKS device.
839 '';
840 };
841
842 fsType = mkOption {
843 default = "vfat";
844 type = types.str;
845 description = lib.mdDoc "The filesystem of the unencrypted device.";
846 };
847
848 path = mkOption {
849 default = "/crypt-storage/default";
850 type = types.str;
851 description = lib.mdDoc ''
852 Absolute path of the salt on the unencrypted device with
853 that device's root directory as "/".
854 '';
855 };
856 };
857 };
858 });
859 };
860
861 preOpenCommands = mkOption {
862 type = types.lines;
863 default = "";
864 example = ''
865 mkdir -p /tmp/persistent
866 mount -t zfs rpool/safe/persistent /tmp/persistent
867 '';
868 description = lib.mdDoc ''
869 Commands that should be run right before we try to mount our LUKS device.
870 This can be useful, if the keys needed to open the drive is on another partition.
871 '';
872 };
873
874 postOpenCommands = mkOption {
875 type = types.lines;
876 default = "";
877 example = ''
878 umount /tmp/persistent
879 '';
880 description = lib.mdDoc ''
881 Commands that should be run right after we have mounted our LUKS device.
882 '';
883 };
884
885 crypttabExtraOpts = mkOption {
886 type = with types; listOf singleLineStr;
887 default = [];
888 example = [ "_netdev" ];
889 visible = false;
890 description = lib.mdDoc ''
891 Only used with systemd stage 1.
892
893 Extra options to append to the last column of the generated crypttab file.
894 '';
895 };
896 };
897 }));
898 };
899
900 boot.initrd.luks.gpgSupport = mkOption {
901 default = false;
902 type = types.bool;
903 description = lib.mdDoc ''
904 Enables support for authenticating with a GPG encrypted password.
905 '';
906 };
907
908 boot.initrd.luks.yubikeySupport = mkOption {
909 default = false;
910 type = types.bool;
911 description = lib.mdDoc ''
912 Enables support for authenticating with a YubiKey on LUKS devices.
913 See the NixOS wiki for information on how to properly setup a LUKS device
914 and a YubiKey to work with this feature.
915 '';
916 };
917
918 boot.initrd.luks.fido2Support = mkOption {
919 default = false;
920 type = types.bool;
921 description = lib.mdDoc ''
922 Enables support for authenticating with FIDO2 devices.
923 '';
924 };
925
926 };
927
928 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
929
930 assertions =
931 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
932 message = "YubiKey and GPG Card may not be used at the same time.";
933 }
934
935 { assertion = !(luks.gpgSupport && luks.fido2Support);
936 message = "FIDO2 and GPG Card may not be used at the same time.";
937 }
938
939 { assertion = !(luks.fido2Support && luks.yubikeySupport);
940 message = "FIDO2 and YubiKey may not be used at the same time.";
941 }
942
943 { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices)
944 -> versionAtLeast kernelPackages.kernel.version "5.9";
945 message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
946 }
947
948 { assertion = !config.boot.initrd.systemd.enable -> all (x: x.keyFileTimeout == null) (attrValues luks.devices);
949 message = "boot.initrd.luks.devices.<name>.keyFileTimeout is only supported for systemd initrd";
950 }
951
952 { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices);
953 message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1.";
954 }
955 { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices);
956 message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1.";
957 }
958 { assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio;
959 message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1.";
960 }
961 { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices);
962 message = "boot.initrd.luks.devices.<name>.preOpenCommands and postOpenCommands is not supported by systemd stage 1. Please bind a service to cryptsetup.target or cryptsetup-pre.target instead.";
963 }
964 # TODO
965 { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport;
966 message = "systemd stage 1 does not support GPG smartcards yet.";
967 }
968 { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support;
969 message = ''
970 systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.devices.<name>.fido2`.
971 Use systemd-cryptenroll(1) to configure FIDO2 support.
972 '';
973 }
974 # TODO
975 { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport;
976 message = "systemd stage 1 does not support Yubikeys yet.";
977 }
978 ];
979
980 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
981 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
982 ["firewire_ohci" "firewire_core" "firewire_sbp2"];
983
984 # Some modules that may be needed for mounting anything ciphered
985 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
986 ++ luks.cryptoModules
987 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
988 # remove once 'modprobe --show-depends xts' shows ecb as a dependency
989 ++ (optional (builtins.elem "xts" luks.cryptoModules) "ecb");
990
991 # copy the cryptsetup binary and it's dependencies
992 boot.initrd.extraUtilsCommands = let
993 pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } ''
994 mkdir -p "$out/bin"
995 cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512"
996 strip -s "$out/bin/pbkdf2-sha512"
997 '';
998 in
999 mkIf (!config.boot.initrd.systemd.enable) ''
1000 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
1001 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
1002 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
1003
1004 ${optionalString luks.yubikeySupport ''
1005 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
1006 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
1007 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
1008
1009 copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512
1010
1011 mkdir -p $out/etc/ssl
1012 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
1013
1014 cat > $out/bin/openssl-wrap <<EOF
1015 #!$out/bin/sh
1016 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
1017 $out/bin/openssl "\$@"
1018 EOF
1019 chmod +x $out/bin/openssl-wrap
1020 ''}
1021
1022 ${optionalString luks.fido2Support ''
1023 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
1024 ''}
1025
1026
1027 ${optionalString luks.gpgSupport ''
1028 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
1029 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
1030 copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
1031
1032 ${concatMapStringsSep "\n" (x:
1033 optionalString (x.gpgCard != null)
1034 ''
1035 mkdir -p $out/secrets/gpg-keys/${x.device}
1036 cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
1037 cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
1038 ''
1039 ) (attrValues luks.devices)
1040 }
1041 ''}
1042 '';
1043
1044 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
1045 $out/bin/cryptsetup --version
1046 ${optionalString luks.yubikeySupport ''
1047 $out/bin/ykchalresp -V
1048 $out/bin/ykinfo -V
1049 $out/bin/openssl-wrap version
1050 ''}
1051 ${optionalString luks.gpgSupport ''
1052 $out/bin/gpg --version
1053 $out/bin/gpg-agent --version
1054 $out/bin/scdaemon --version
1055 ''}
1056 ${optionalString luks.fido2Support ''
1057 $out/bin/fido2luks --version
1058 ''}
1059 '';
1060
1061 boot.initrd.systemd = {
1062 contents."/etc/crypttab".source = stage1Crypttab;
1063
1064 extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup";
1065
1066 additionalUpstreamUnits = [
1067 "cryptsetup-pre.target"
1068 "cryptsetup.target"
1069 "remote-cryptsetup.target"
1070 ];
1071 storePaths = [
1072 "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup"
1073 "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator"
1074 ];
1075
1076 };
1077 # We do this because we need the udev rules from the package
1078 boot.initrd.services.lvm.enable = true;
1079
1080 boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands;
1081 boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands);
1082 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands);
1083
1084 environment.systemPackages = [ pkgs.cryptsetup ];
1085 };
1086}