summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--accounts/gkleen@installer.nix1
-rw-r--r--installer.nix18
-rw-r--r--modules/luksroot.nix1020
3 files changed, 1038 insertions, 1 deletions
diff --git a/accounts/gkleen@installer.nix b/accounts/gkleen@installer.nix
new file mode 100644
index 00000000..64629674
--- /dev/null
+++ b/accounts/gkleen@installer.nix
@@ -0,0 +1 @@
{...}: {}
diff --git a/installer.nix b/installer.nix
index 64629674..3987e8ab 100644
--- a/installer.nix
+++ b/installer.nix
@@ -1 +1,17 @@
1{...}: {} 1{ flake, ... }: {
2 imports = with flake.nixosModules.systemProfiles; [
3 default-locale
4 ];
5
6 config = {
7 networking.firewall = {
8 enable = true;
9 allowedTCPPorts = [ 22 # ssh
10 ];
11 };
12
13 systemd.services."sshd".wantedBy = ["multi-user.target"];
14
15 services.qemuGuest.enable = true;
16 };
17}
diff --git a/modules/luksroot.nix b/modules/luksroot.nix
new file mode 100644
index 00000000..e1a910d7
--- /dev/null
+++ b/modules/luksroot.nix
@@ -0,0 +1,1020 @@
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, 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 # commands to run right before we mount our device
455 ${preOpenCommands}
456
457 ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) || (luks.clevisSupport && clevis) then ''
458 open_with_hardware
459 '' else ''
460 open_normally
461 ''}
462
463 # commands to run right after we mounted our device
464 ${postOpenCommands}
465 '';
466
467 askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
468 #!/bin/sh
469
470 ${commonFunctions}
471
472 while true; do
473 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
474 device=$(cat /crypt-ramfs/device)
475
476 echo -n "Passphrase for $device: "
477 IFS= read -rs passphrase
478 echo
479
480 rm /crypt-ramfs/device
481 echo -n "$passphrase" > /crypt-ramfs/passphrase
482 done
483 '';
484
485 preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
486 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
487
488in
489{
490 disabledModules = [ "system/boot/luksroot.nix" ];
491
492 imports = [
493 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
494 ];
495
496 options = {
497
498 boot.initrd.luks.mitigateDMAAttacks = mkOption {
499 type = types.bool;
500 default = true;
501 description = ''
502 Unless enabled, encryption keys can be easily recovered by an attacker with physical
503 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
504 More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>.
505
506 This option blacklists FireWire drivers, but doesn't remove them. You can manually
507 load the drivers if you need to use a FireWire device, but don't forget to unload them!
508 '';
509 };
510
511 boot.initrd.luks.cryptoModules = mkOption {
512 type = types.listOf types.str;
513 default =
514 [ "aes" "aes_generic" "blowfish" "twofish"
515 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
516 "af_alg" "algif_skcipher"
517 ];
518 description = ''
519 A list of cryptographic kernel modules needed to decrypt the root device(s).
520 The default includes all common modules.
521 '';
522 };
523
524 boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
525 type = types.bool;
526 default = false;
527 internal = true;
528 description = ''
529 Whether to configure luks support in the initrd, when no luks
530 devices are configured.
531 '';
532 };
533
534 boot.initrd.luks.reusePassphrases = mkOption {
535 type = types.bool;
536 default = true;
537 description = ''
538 When opening a new LUKS device try reusing last successful
539 passphrase.
540
541 Useful for mounting a number of devices that use the same
542 passphrase without retyping it several times.
543
544 Such setup can be useful if you use <command>cryptsetup
545 luksSuspend</command>. Different LUKS devices will still have
546 different master keys even when using the same passphrase.
547 '';
548 };
549
550 boot.initrd.luks.devices = mkOption {
551 default = { };
552 example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
553 description = ''
554 The encrypted disk that should be opened before the root
555 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
556 setups are supported. The unencrypted devices can be accessed as
557 <filename>/dev/mapper/<replaceable>name</replaceable></filename>.
558 '';
559
560 type = with types; attrsOf (submodule (
561 { name, ... }: { options = {
562
563 name = mkOption {
564 visible = false;
565 default = name;
566 example = "luksroot";
567 type = types.str;
568 description = "Name of the unencrypted device in <filename>/dev/mapper</filename>.";
569 };
570
571 device = mkOption {
572 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
573 type = types.str;
574 description = "Path of the underlying encrypted block device.";
575 };
576
577 header = mkOption {
578 default = null;
579 example = "/root/header.img";
580 type = types.nullOr types.str;
581 description = ''
582 The name of the file or block device that
583 should be used as header for the encrypted device.
584 '';
585 };
586
587 keyFile = mkOption {
588 default = null;
589 example = "/dev/sdb1";
590 type = types.nullOr types.str;
591 description = ''
592 The name of the file (can be a raw device or a partition) that
593 should be used as the decryption key for the encrypted device. If
594 not specified, you will be prompted for a passphrase instead.
595 '';
596 };
597
598 keyFileSize = mkOption {
599 default = null;
600 example = 4096;
601 type = types.nullOr types.int;
602 description = ''
603 The size of the key file. Use this if only the beginning of the
604 key file should be used as a key (often the case if a raw device
605 or partition is used as key file). If not specified, the whole
606 <literal>keyFile</literal> will be used decryption, instead of just
607 the first <literal>keyFileSize</literal> bytes.
608 '';
609 };
610
611 keyFileOffset = mkOption {
612 default = null;
613 example = 4096;
614 type = types.nullOr types.int;
615 description = ''
616 The offset of the key file. Use this in combination with
617 <literal>keyFileSize</literal> to use part of a file as key file
618 (often the case if a raw device or partition is used as a key file).
619 If not specified, the key begins at the first byte of
620 <literal>keyFile</literal>.
621 '';
622 };
623
624 # FIXME: get rid of this option.
625 preLVM = mkOption {
626 default = true;
627 type = types.bool;
628 description = "Whether the luksOpen will be attempted before LVM scan or after it.";
629 };
630
631 allowDiscards = mkOption {
632 default = false;
633 type = types.bool;
634 description = ''
635 Whether to allow TRIM requests to the underlying device. This option
636 has security implications; please read the LUKS documentation before
637 activating it.
638 '';
639 };
640
641 fallbackToPassword = mkOption {
642 default = false;
643 type = types.bool;
644 description = ''
645 Whether to fallback to interactive passphrase prompt if the keyfile
646 cannot be found. This will prevent unattended boot should the keyfile
647 go missing.
648 '';
649 };
650
651 gpgCard = mkOption {
652 default = null;
653 description = ''
654 The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
655 If null (the default), GPG-Smartcard will be disabled for this device.
656 '';
657
658 type = with types; nullOr (submodule {
659 options = {
660 gracePeriod = mkOption {
661 default = 10;
662 type = types.int;
663 description = "Time in seconds to wait for the GPG Smartcard.";
664 };
665
666 encryptedPass = mkOption {
667 default = "";
668 type = types.path;
669 description = "Path to the GPG encrypted passphrase.";
670 };
671
672 publicKey = mkOption {
673 default = "";
674 type = types.path;
675 description = "Path to the Public Key.";
676 };
677 };
678 });
679 };
680
681 fido2 = {
682 credential = mkOption {
683 default = null;
684 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
685 type = types.nullOr types.str;
686 description = "The FIDO2 credential ID.";
687 };
688
689 gracePeriod = mkOption {
690 default = 10;
691 type = types.int;
692 description = "Time in seconds to wait for the FIDO2 key.";
693 };
694
695 passwordLess = mkOption {
696 default = false;
697 type = types.bool;
698 description = ''
699 Defines whatever to use an empty string as a default salt.
700
701 Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>.
702 '';
703 };
704 };
705
706 yubikey = mkOption {
707 default = null;
708 description = ''
709 The options to use for this LUKS device in YubiKey-PBA.
710 If null (the default), YubiKey-PBA will be disabled for this device.
711 '';
712
713 type = with types; nullOr (submodule {
714 options = {
715 twoFactor = mkOption {
716 default = true;
717 type = types.bool;
718 description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
719 };
720
721 slot = mkOption {
722 default = 2;
723 type = types.int;
724 description = "Which slot on the YubiKey to challenge.";
725 };
726
727 saltLength = mkOption {
728 default = 16;
729 type = types.int;
730 description = "Length of the new salt in byte (64 is the effective maximum).";
731 };
732
733 keyLength = mkOption {
734 default = 64;
735 type = types.int;
736 description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
737 };
738
739 iterationStep = mkOption {
740 default = 0;
741 type = types.int;
742 description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
743 };
744
745 gracePeriod = mkOption {
746 default = 10;
747 type = types.int;
748 description = "Time in seconds to wait for the YubiKey.";
749 };
750
751 /* TODO: Add to the documentation of the current module:
752
753 Options related to the storing the salt.
754 */
755 storage = {
756 device = mkOption {
757 default = "/dev/sda1";
758 type = types.path;
759 description = ''
760 An unencrypted device that will temporarily be mounted in stage-1.
761 Must contain the current salt to create the challenge for this LUKS device.
762 '';
763 };
764
765 fsType = mkOption {
766 default = "vfat";
767 type = types.str;
768 description = "The filesystem of the unencrypted device.";
769 };
770
771 path = mkOption {
772 default = "/crypt-storage/default";
773 type = types.str;
774 description = ''
775 Absolute path of the salt on the unencrypted device with
776 that device's root directory as "/".
777 '';
778 };
779 };
780 };
781 });
782 };
783
784 clevis = mkOption {
785 type = types.bool;
786 default = false;
787 description = ''
788 Unlock device via clevis (e.g. with a tpm)
789 '';
790 };
791
792 preOpenCommands = mkOption {
793 type = types.lines;
794 default = "";
795 example = ''
796 mkdir -p /tmp/persistent
797 mount -t zfs rpool/safe/persistent /tmp/persistent
798 '';
799 description = ''
800 Commands that should be run right before we try to mount our LUKS device.
801 This can be useful, if the keys needed to open the drive is on another partion.
802 '';
803 };
804
805 postOpenCommands = mkOption {
806 type = types.lines;
807 default = "";
808 example = ''
809 umount /tmp/persistent
810 '';
811 description = ''
812 Commands that should be run right after we have mounted our LUKS device.
813 '';
814 };
815 };
816 }));
817 };
818
819 boot.initrd.luks.gpgSupport = mkOption {
820 default = false;
821 type = types.bool;
822 description = ''
823 Enables support for authenticating with a GPG encrypted password.
824 '';
825 };
826
827 boot.initrd.luks.yubikeySupport = mkOption {
828 default = false;
829 type = types.bool;
830 description = ''
831 Enables support for authenticating with a YubiKey on LUKS devices.
832 See the NixOS wiki for information on how to properly setup a LUKS device
833 and a YubiKey to work with this feature.
834 '';
835 };
836
837 boot.initrd.luks.fido2Support = mkOption {
838 default = false;
839 type = types.bool;
840 description = ''
841 Enables support for authenticating with FIDO2 devices.
842 '';
843 };
844
845 boot.initrd.luks.clevisSupport = mkOption {
846 default = false;
847 type = types.bool;
848 description = ''
849 Enables support for unlocking luks volumes via clevis (e.g. with a tpm)
850 '';
851 };
852
853 };
854
855 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
856
857 assertions =
858 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
859 message = "YubiKey and GPG Card may not be used at the same time.";
860 }
861
862 { assertion = !(luks.gpgSupport && luks.fido2Support);
863 message = "FIDO2 and GPG Card may not be used at the same time.";
864 }
865
866 { assertion = !(luks.gpgSupport && luks.clevisSupport);
867 message = "Clevis and GPG Card may not be used at the same time.";
868 }
869
870 { assertion = !(luks.fido2Support && luks.yubikeySupport);
871 message = "FIDO2 and YubiKey may not be used at the same time.";
872 }
873
874 { assertion = !(luks.fido2Support && luks.clevisSupport);
875 message = "FIDO2 and Clevis may not be used at the same time.";
876 }
877
878 { assertion = !(luks.yubikeySupport && luks.clevisSupport);
879 message = "Clevis and YubiKey may not be used at the same time.";
880 }
881
882 ];
883
884 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
885 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
886 ["firewire_ohci" "firewire_core" "firewire_sbp2"];
887
888 # Some modules that may be needed for mounting anything ciphered
889 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
890 ++ luks.cryptoModules
891 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
892 # remove once 'modprobe --show-depends xts' shows ecb as a dependency
893 ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []);
894
895 # copy the cryptsetup binary and it's dependencies
896 boot.initrd.extraUtilsCommands =
897 let
898 extraUtils = config.system.build.extraUtils;
899
900 ipkgs = pkgs.appendOverlays [
901 (final: prev: {
902 tpm2-tss = prev.tpm2-tss.overrideAttrs (oldAttrs: {
903 doCheck = false;
904 patches = [];
905 postPatch = ''
906 patchShebangs script
907 '';
908 configureFlags = [];
909 });
910 })
911 ];
912 in ''
913 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
914 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
915 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
916
917 ${optionalString luks.yubikeySupport ''
918 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
919 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
920 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
921
922 cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto
923 strip -s pbkdf2-sha512
924 copy_bin_and_libs pbkdf2-sha512
925
926 mkdir -p $out/etc/ssl
927 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
928
929 cat > $out/bin/openssl-wrap <<EOF
930 #!$out/bin/sh
931 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
932 $out/bin/openssl "\$@"
933 EOF
934 chmod +x $out/bin/openssl-wrap
935 ''}
936
937 ${optionalString luks.fido2Support ''
938 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
939 ''}
940
941
942 ${optionalString luks.gpgSupport ''
943 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
944 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
945 copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
946
947 ${concatMapStringsSep "\n" (x:
948 if x.gpgCard != null then
949 ''
950 mkdir -p $out/secrets/gpg-keys/${x.device}
951 cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
952 cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
953 ''
954 else ""
955 ) (attrValues luks.devices)
956 }
957 ''}
958
959 ${optionalString luks.clevisSupport ''
960 for bin in ${ipkgs.tpm2-tools}/bin/* ${ipkgs.jose}/bin/* ${ipkgs.libpwquality}/bin/*; do
961 if [ -L $bin ]; then
962 cp -v $bin $out/bin
963 else
964 copy_bin_and_libs $bin
965 fi
966 done
967
968 copy_bin_and_libs ${ipkgs.bash}/bin/bash
969 for bin in ${ipkgs.clevis}/bin/* ${ipkgs.clevis}/bin/.*; do
970 [ -f $bin -o -L $bin ] || continue
971
972 substitute $bin $out/bin/$(basename $bin) \
973 --replace ${ipkgs.bash}/bin $out/bin \
974 --replace ${ipkgs.clevis}/bin $out/bin \
975 --replace ${ipkgs.tpm2-tools}/bin $out/bin \
976 --replace ${ipkgs.jose}/bin $out/bin \
977 --replace ${ipkgs.libpwquality}/bin $out/bin \
978 --replace ${ipkgs.coreutils}/bin $out/bin
979
980 [ -x $bin ] && chmod +x $out/bin/$(basename $bin)
981 done
982
983 for lib in ${ipkgs.tpm2-tss}/lib/*.so*; do
984 if [ -f $lib ]; then
985 patchelf --output $out/lib/$(basename $lib) $lib \
986 --set-rpath $out/lib
987 else
988 cp -pdv $lib $out/lib
989 fi
990 done
991 ''}
992 '';
993
994 boot.initrd.extraUtilsCommandsTest = ''
995 $out/bin/cryptsetup --version
996 ${optionalString luks.yubikeySupport ''
997 $out/bin/ykchalresp -V
998 $out/bin/ykinfo -V
999 $out/bin/openssl-wrap version
1000 ''}
1001 ${optionalString luks.gpgSupport ''
1002 $out/bin/gpg --version
1003 $out/bin/gpg-agent --version
1004 $out/bin/scdaemon --version
1005 ''}
1006 ${optionalString luks.fido2Support ''
1007 $out/bin/fido2luks --version
1008 ''}
1009 ${optionalString luks.clevisSupport ''
1010 $out/bin/jose alg
1011 ''}
1012 '';
1013
1014 boot.initrd.preFailCommands = postCommands;
1015 boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
1016 boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
1017
1018 environment.systemPackages = [ pkgs.cryptsetup ];
1019 };
1020}