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