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 "$k_user" ]; then
355 new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
356 else
357 new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
358 fi
359
360 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
361 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
362
363 if [ $? == 0 ]; then
364 echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path}
365 sync /crypt-storage${dev.yubikey.storage.path}
366 else
367 echo "Warning: Could not update LUKS key, current challenge persists!"
368 fi
369
370 rm -f /crypt-ramfs/new_key
371 umount /crypt-storage
372 }
373
374 open_with_hardware() {
375 if wait_yubikey ${toString dev.yubikey.gracePeriod}; then
376 do_open_yubikey
377 else
378 echo "No YubiKey found, falling back to non-YubiKey open procedure"
379 open_normally
380 fi
381 }
382 ''}
383
384 ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) ''
385
386 do_open_gpg_card() {
387 # Make all of these local to this function
388 # to prevent their values being leaked
389 local pin
390 local opened
391
392 gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null
393
394 gpg --card-status > /dev/null 2> /dev/null
395
396 for try in $(seq 3); do
397 echo -n "PIN for GPG Card associated with device ${dev.device}: "
398 pin=
399 while true; do
400 if [ -e /crypt-ramfs/passphrase ]; then
401 echo "reused"
402 pin=$(cat /crypt-ramfs/passphrase)
403 break
404 else
405 # and try reading it from /dev/console with a timeout
406 IFS= read -t 1 -r pin
407 if [ -n "$pin" ]; then
408 ${if luks.reusePassphrases then ''
409 # remember it for the next device
410 echo -n "$pin" > /crypt-ramfs/passphrase
411 '' else ''
412 # Don't save it to ramfs. We are very paranoid
413 ''}
414 echo
415 break
416 fi
417 fi
418 done
419 echo -n "Verifying passphrase for ${dev.device}..."
420 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
421 if [ $? == 0 ]; then
422 echo " - success"
423 ${if luks.reusePassphrases then ''
424 # we don't rm here because we might reuse it for the next device
425 '' else ''
426 rm -f /crypt-ramfs/passphrase
427 ''}
428 break
429 else
430 echo " - failure"
431 # ask for a different one
432 rm -f /crypt-ramfs/passphrase
433 fi
434 done
435
436 [ "$opened" == false ] && die "Maximum authentication errors reached"
437 }
438
439 open_with_hardware() {
440 if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then
441 do_open_gpg_card
442 else
443 echo "No GPG Card found, falling back to normal open procedure"
444 open_normally
445 fi
446 }
447 ''}
448
449 ${optionalString (luks.fido2Support && fido2luksCredentials != []) ''
450
451 open_with_hardware() {
452 local passsphrase
453
454 ${if dev.fido2.passwordLess then ''
455 export passphrase=""
456 '' else ''
457 read -rsp "FIDO2 salt for ${dev.device}: " passphrase
458 echo
459 ''}
460 ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
461 echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest."
462 echo "Please move your mouse to create needed randomness."
463 ''}
464 echo "Waiting for your FIDO2 device..."
465 fido2luks open${optionalString dev.allowDiscards " --allow-discards"} ${dev.device} ${dev.name} "${builtins.concatStringsSep "," fido2luksCredentials}" --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase
466 if [ $? -ne 0 ]; then
467 echo "No FIDO2 key found, falling back to normal open procedure"
468 open_normally
469 fi
470 }
471 ''}
472
473 # commands to run right before we mount our device
474 ${dev.preOpenCommands}
475
476 ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && fido2luksCredentials != []) then ''
477 open_with_hardware
478 '' else ''
479 open_normally
480 ''}
481
482 # commands to run right after we mounted our device
483 ${dev.postOpenCommands}
484 '';
485
486 askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
487 #!/bin/sh
488
489 ${commonFunctions}
490
491 while true; do
492 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
493 device=$(cat /crypt-ramfs/device)
494
495 echo -n "Passphrase for $device: "
496 IFS= read -rs passphrase
497 echo
498
499 rm /crypt-ramfs/device
500 echo -n "$passphrase" > /crypt-ramfs/passphrase
501 done
502 '';
503
504 preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
505 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
506
507
508 stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let
509 opts = v.crypttabExtraOpts
510 ++ optional v.allowDiscards "discard"
511 ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
512 ++ optional (v.header != null) "header=${v.header}"
513 ++ optional (v.keyFileOffset != null) "keyfile-offset=${toString v.keyFileOffset}"
514 ++ optional (v.keyFileSize != null) "keyfile-size=${toString v.keyFileSize}"
515 ++ optional (v.keyFileTimeout != null) "keyfile-timeout=${builtins.toString v.keyFileTimeout}s"
516 ++ optional (v.tryEmptyPassphrase) "try-empty-password=true"
517 ;
518 in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
519
520in
521{
522 imports = [
523 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
524 ];
525
526 options = {
527
528 boot.initrd.luks.mitigateDMAAttacks = mkOption {
529 type = types.bool;
530 default = true;
531 description = lib.mdDoc ''
532 Unless enabled, encryption keys can be easily recovered by an attacker with physical
533 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
534 More information is available at <http://en.wikipedia.org/wiki/DMA_attack>.
535
536 This option blacklists FireWire drivers, but doesn't remove them. You can manually
537 load the drivers if you need to use a FireWire device, but don't forget to unload them!
538 '';
539 };
540
541 boot.initrd.luks.cryptoModules = mkOption {
542 type = types.listOf types.str;
543 default =
544 [ "aes" "aes_generic" "blowfish" "twofish"
545 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
546 "af_alg" "algif_skcipher"
547 ];
548 description = lib.mdDoc ''
549 A list of cryptographic kernel modules needed to decrypt the root device(s).
550 The default includes all common modules.
551 '';
552 };
553
554 boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
555 type = types.bool;
556 default = false;
557 internal = true;
558 description = lib.mdDoc ''
559 Whether to configure luks support in the initrd, when no luks
560 devices are configured.
561 '';
562 };
563
564 boot.initrd.luks.reusePassphrases = mkOption {
565 type = types.bool;
566 default = true;
567 description = lib.mdDoc ''
568 When opening a new LUKS device try reusing last successful
569 passphrase.
570
571 Useful for mounting a number of devices that use the same
572 passphrase without retyping it several times.
573
574 Such setup can be useful if you use {command}`cryptsetup luksSuspend`.
575 Different LUKS devices will still have
576 different master keys even when using the same passphrase.
577 '';
578 };
579
580 boot.initrd.luks.devices = mkOption {
581 default = { };
582 example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
583 description = lib.mdDoc ''
584 The encrypted disk that should be opened before the root
585 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
586 setups are supported. The unencrypted devices can be accessed as
587 {file}`/dev/mapper/«name»`.
588 '';
589
590 type = with types; attrsOf (submodule (
591 { name, ... }: { options = {
592
593 name = mkOption {
594 visible = false;
595 default = name;
596 example = "luksroot";
597 type = types.str;
598 description = lib.mdDoc "Name of the unencrypted device in {file}`/dev/mapper`.";
599 };
600
601 device = mkOption {
602 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
603 type = types.str;
604 description = lib.mdDoc "Path of the underlying encrypted block device.";
605 };
606
607 header = mkOption {
608 default = null;
609 example = "/root/header.img";
610 type = types.nullOr types.str;
611 description = lib.mdDoc ''
612 The name of the file or block device that
613 should be used as header for the encrypted device.
614 '';
615 };
616
617 keyFile = mkOption {
618 default = null;
619 example = "/dev/sdb1";
620 type = types.nullOr types.str;
621 description = lib.mdDoc ''
622 The name of the file (can be a raw device or a partition) that
623 should be used as the decryption key for the encrypted device. If
624 not specified, you will be prompted for a passphrase instead.
625 '';
626 };
627
628 tryEmptyPassphrase = mkOption {
629 default = false;
630 type = types.bool;
631 description = lib.mdDoc ''
632 If keyFile fails then try an empty passphrase first before
633 prompting for password.
634 '';
635 };
636
637 keyFileTimeout = mkOption {
638 default = null;
639 example = 5;
640 type = types.nullOr types.int;
641 description = lib.mdDoc ''
642 The amount of time in seconds for a keyFile to appear before
643 timing out and trying passwords.
644 '';
645 };
646
647 keyFileSize = mkOption {
648 default = null;
649 example = 4096;
650 type = types.nullOr types.int;
651 description = lib.mdDoc ''
652 The size of the key file. Use this if only the beginning of the
653 key file should be used as a key (often the case if a raw device
654 or partition is used as key file). If not specified, the whole
655 `keyFile` will be used decryption, instead of just
656 the first `keyFileSize` bytes.
657 '';
658 };
659
660 keyFileOffset = mkOption {
661 default = null;
662 example = 4096;
663 type = types.nullOr types.int;
664 description = lib.mdDoc ''
665 The offset of the key file. Use this in combination with
666 `keyFileSize` to use part of a file as key file
667 (often the case if a raw device or partition is used as a key file).
668 If not specified, the key begins at the first byte of
669 `keyFile`.
670 '';
671 };
672
673 # FIXME: get rid of this option.
674 preLVM = mkOption {
675 default = true;
676 type = types.bool;
677 description = lib.mdDoc "Whether the luksOpen will be attempted before LVM scan or after it.";
678 };
679
680 allowDiscards = mkOption {
681 default = false;
682 type = types.bool;
683 description = lib.mdDoc ''
684 Whether to allow TRIM requests to the underlying device. This option
685 has security implications; please read the LUKS documentation before
686 activating it.
687 This option is incompatible with authenticated encryption (dm-crypt
688 stacked over dm-integrity).
689 '';
690 };
691
692 bypassWorkqueues = mkOption {
693 default = false;
694 type = types.bool;
695 description = lib.mdDoc ''
696 Whether to bypass dm-crypt's internal read and write workqueues.
697 Enabling this should improve performance on SSDs; see
698 [here](https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance)
699 for more information. Needs Linux 5.9 or later.
700 '';
701 };
702
703 fallbackToPassword = mkOption {
704 default = false;
705 type = types.bool;
706 description = lib.mdDoc ''
707 Whether to fallback to interactive passphrase prompt if the keyfile
708 cannot be found. This will prevent unattended boot should the keyfile
709 go missing.
710 '';
711 };
712
713 gpgCard = mkOption {
714 default = null;
715 description = lib.mdDoc ''
716 The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
717 If null (the default), GPG-Smartcard will be disabled for this device.
718 '';
719
720 type = with types; nullOr (submodule {
721 options = {
722 gracePeriod = mkOption {
723 default = 10;
724 type = types.int;
725 description = lib.mdDoc "Time in seconds to wait for the GPG Smartcard.";
726 };
727
728 encryptedPass = mkOption {
729 type = types.path;
730 description = lib.mdDoc "Path to the GPG encrypted passphrase.";
731 };
732
733 publicKey = mkOption {
734 type = types.path;
735 description = lib.mdDoc "Path to the Public Key.";
736 };
737 };
738 });
739 };
740
741 fido2 = {
742 credential = mkOption {
743 default = null;
744 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
745 type = types.nullOr types.str;
746 description = lib.mdDoc "The FIDO2 credential ID.";
747 };
748
749 credentials = mkOption {
750 default = [];
751 example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ];
752 type = types.listOf types.str;
753 description = lib.mdDoc ''
754 List of FIDO2 credential IDs.
755
756 Use this if you have multiple FIDO2 keys you want to use for the same luks device.
757 '';
758 };
759
760 gracePeriod = mkOption {
761 default = 10;
762 type = types.int;
763 description = lib.mdDoc "Time in seconds to wait for the FIDO2 key.";
764 };
765
766 passwordLess = mkOption {
767 default = false;
768 type = types.bool;
769 description = lib.mdDoc ''
770 Defines whatever to use an empty string as a default salt.
771
772 Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/).
773 '';
774 };
775 };
776
777 yubikey = mkOption {
778 default = null;
779 description = lib.mdDoc ''
780 The options to use for this LUKS device in YubiKey-PBA.
781 If null (the default), YubiKey-PBA will be disabled for this device.
782 '';
783
784 type = with types; nullOr (submodule {
785 options = {
786 twoFactor = mkOption {
787 default = true;
788 type = types.bool;
789 description = lib.mdDoc "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
790 };
791
792 slot = mkOption {
793 default = 2;
794 type = types.int;
795 description = lib.mdDoc "Which slot on the YubiKey to challenge.";
796 };
797
798 saltLength = mkOption {
799 default = 16;
800 type = types.int;
801 description = lib.mdDoc "Length of the new salt in byte (64 is the effective maximum).";
802 };
803
804 keyLength = mkOption {
805 default = 64;
806 type = types.int;
807 description = lib.mdDoc "Length of the LUKS slot key derived with PBKDF2 in byte.";
808 };
809
810 iterationStep = mkOption {
811 default = 0;
812 type = types.int;
813 description = lib.mdDoc "How much the iteration count for PBKDF2 is increased at each successful authentication.";
814 };
815
816 gracePeriod = mkOption {
817 default = 10;
818 type = types.int;
819 description = lib.mdDoc "Time in seconds to wait for the YubiKey.";
820 };
821
822 /* TODO: Add to the documentation of the current module:
823
824 Options related to the storing the salt.
825 */
826 storage = {
827 device = mkOption {
828 default = "/dev/sda1";
829 type = types.path;
830 description = lib.mdDoc ''
831 An unencrypted device that will temporarily be mounted in stage-1.
832 Must contain the current salt to create the challenge for this LUKS device.
833 '';
834 };
835
836 fsType = mkOption {
837 default = "vfat";
838 type = types.str;
839 description = lib.mdDoc "The filesystem of the unencrypted device.";
840 };
841
842 path = mkOption {
843 default = "/crypt-storage/default";
844 type = types.str;
845 description = lib.mdDoc ''
846 Absolute path of the salt on the unencrypted device with
847 that device's root directory as "/".
848 '';
849 };
850 };
851 };
852 });
853 };
854
855 preOpenCommands = mkOption {
856 type = types.lines;
857 default = "";
858 example = ''
859 mkdir -p /tmp/persistent
860 mount -t zfs rpool/safe/persistent /tmp/persistent
861 '';
862 description = lib.mdDoc ''
863 Commands that should be run right before we try to mount our LUKS device.
864 This can be useful, if the keys needed to open the drive is on another partition.
865 '';
866 };
867
868 postOpenCommands = mkOption {
869 type = types.lines;
870 default = "";
871 example = ''
872 umount /tmp/persistent
873 '';
874 description = lib.mdDoc ''
875 Commands that should be run right after we have mounted our LUKS device.
876 '';
877 };
878
879 crypttabExtraOpts = mkOption {
880 type = with types; listOf singleLineStr;
881 default = [];
882 example = [ "_netdev" ];
883 visible = false;
884 description = lib.mdDoc ''
885 Only used with systemd stage 1.
886
887 Extra options to append to the last column of the generated crypttab file.
888 '';
889 };
890 };
891 }));
892 };
893
894 boot.initrd.luks.gpgSupport = mkOption {
895 default = false;
896 type = types.bool;
897 description = lib.mdDoc ''
898 Enables support for authenticating with a GPG encrypted password.
899 '';
900 };
901
902 boot.initrd.luks.yubikeySupport = mkOption {
903 default = false;
904 type = types.bool;
905 description = lib.mdDoc ''
906 Enables support for authenticating with a YubiKey on LUKS devices.
907 See the NixOS wiki for information on how to properly setup a LUKS device
908 and a YubiKey to work with this feature.
909 '';
910 };
911
912 boot.initrd.luks.fido2Support = mkOption {
913 default = false;
914 type = types.bool;
915 description = lib.mdDoc ''
916 Enables support for authenticating with FIDO2 devices.
917 '';
918 };
919
920 };
921
922 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
923
924 assertions =
925 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
926 message = "YubiKey and GPG Card may not be used at the same time.";
927 }
928
929 { assertion = !(luks.gpgSupport && luks.fido2Support);
930 message = "FIDO2 and GPG Card may not be used at the same time.";
931 }
932
933 { assertion = !(luks.fido2Support && luks.yubikeySupport);
934 message = "FIDO2 and YubiKey may not be used at the same time.";
935 }
936
937 { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices)
938 -> versionAtLeast kernelPackages.kernel.version "5.9";
939 message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
940 }
941
942 { assertion = !config.boot.initrd.systemd.enable -> all (x: x.keyFileTimeout == null) (attrValues luks.devices);
943 message = "boot.initrd.luks.devices.<name>.keyFileTimeout is only supported for systemd initrd";
944 }
945
946 { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices);
947 message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1.";
948 }
949 { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices);
950 message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1.";
951 }
952 { assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio;
953 message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1.";
954 }
955 { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices);
956 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.";
957 }
958 # TODO
959 { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport;
960 message = "systemd stage 1 does not support GPG smartcards yet.";
961 }
962 { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support;
963 message = ''
964 systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.devices.<name>.fido2`.
965 Use systemd-cryptenroll(1) to configure FIDO2 support.
966 '';
967 }
968 # TODO
969 { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport;
970 message = "systemd stage 1 does not support Yubikeys yet.";
971 }
972 ];
973
974 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
975 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
976 ["firewire_ohci" "firewire_core" "firewire_sbp2"];
977
978 # Some modules that may be needed for mounting anything ciphered
979 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
980 ++ luks.cryptoModules
981 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
982 # remove once 'modprobe --show-depends xts' shows ecb as a dependency
983 ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
984
985 # copy the cryptsetup binary and it's dependencies
986 boot.initrd.extraUtilsCommands = let
987 pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } ''
988 mkdir -p "$out/bin"
989 cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512"
990 strip -s "$out/bin/pbkdf2-sha512"
991 '';
992 in
993 mkIf (!config.boot.initrd.systemd.enable) ''
994 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
995 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
996 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
997
998 ${optionalString luks.yubikeySupport ''
999 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
1000 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
1001 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
1002
1003 copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512
1004
1005 mkdir -p $out/etc/ssl
1006 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
1007
1008 cat > $out/bin/openssl-wrap <<EOF
1009 #!$out/bin/sh
1010 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
1011 $out/bin/openssl "\$@"
1012 EOF
1013 chmod +x $out/bin/openssl-wrap
1014 ''}
1015
1016 ${optionalString luks.fido2Support ''
1017 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
1018 ''}
1019
1020
1021 ${optionalString luks.gpgSupport ''
1022 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
1023 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
1024 copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
1025
1026 ${concatMapStringsSep "\n" (x:
1027 optionalString (x.gpgCard != null)
1028 ''
1029 mkdir -p $out/secrets/gpg-keys/${x.device}
1030 cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
1031 cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
1032 ''
1033 ) (attrValues luks.devices)
1034 }
1035 ''}
1036 '';
1037
1038 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
1039 $out/bin/cryptsetup --version
1040 ${optionalString luks.yubikeySupport ''
1041 $out/bin/ykchalresp -V
1042 $out/bin/ykinfo -V
1043 $out/bin/openssl-wrap version
1044 ''}
1045 ${optionalString luks.gpgSupport ''
1046 $out/bin/gpg --version
1047 $out/bin/gpg-agent --version
1048 $out/bin/scdaemon --version
1049 ''}
1050 ${optionalString luks.fido2Support ''
1051 $out/bin/fido2luks --version
1052 ''}
1053 '';
1054
1055 boot.initrd.systemd = {
1056 contents."/etc/crypttab".source = stage1Crypttab;
1057
1058 extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup";
1059
1060 additionalUpstreamUnits = [
1061 "cryptsetup-pre.target"
1062 "cryptsetup.target"
1063 "remote-cryptsetup.target"
1064 ];
1065 storePaths = [
1066 "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup"
1067 "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator"
1068 ];
1069
1070 };
1071 # We do this because we need the udev rules from the package
1072 boot.initrd.services.lvm.enable = true;
1073
1074 boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands;
1075 boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands);
1076 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands);
1077
1078 environment.systemPackages = [ pkgs.cryptsetup ];
1079 };
1080}