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