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