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