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