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