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