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