vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / system / boot / luksroot.nix
blob70b455871b4b309551291d311ef86ae398e15169
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         echo
507         rm /crypt-ramfs/device
508         echo -n "$passphrase" > /crypt-ramfs/passphrase
509     done
510   '';
512   preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
513   postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
516   stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatLines (lib.mapAttrsToList (n: v: let
517     opts = v.crypttabExtraOpts
518       ++ optional v.allowDiscards "discard"
519       ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ]
520       ++ optional (v.header != null) "header=${v.header}"
521       ++ optional (v.keyFileOffset != null) "keyfile-offset=${toString v.keyFileOffset}"
522       ++ optional (v.keyFileSize != null) "keyfile-size=${toString v.keyFileSize}"
523       ++ optional (v.keyFileTimeout != null) "keyfile-timeout=${builtins.toString v.keyFileTimeout}s"
524       ++ optional (v.tryEmptyPassphrase) "try-empty-password=true"
525     ;
526   in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices));
530   imports = [
531     (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "")
532   ];
534   options = {
536     boot.initrd.luks.mitigateDMAAttacks = mkOption {
537       type = types.bool;
538       default = true;
539       description = ''
540         Unless enabled, encryption keys can be easily recovered by an attacker with physical
541         access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port.
542         More information is available at <https://en.wikipedia.org/wiki/DMA_attack>.
544         This option blacklists FireWire drivers, but doesn't remove them. You can manually
545         load the drivers if you need to use a FireWire device, but don't forget to unload them!
546       '';
547     };
549     boot.initrd.luks.cryptoModules = mkOption {
550       type = types.listOf types.str;
551       default =
552         [ "aes" "aes_generic" "blowfish" "twofish"
553           "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512"
554           "af_alg" "algif_skcipher"
555         ];
556       description = ''
557         A list of cryptographic kernel modules needed to decrypt the root device(s).
558         The default includes all common modules.
559       '';
560     };
562     boot.initrd.luks.forceLuksSupportInInitrd = mkOption {
563       type = types.bool;
564       default = false;
565       internal = true;
566       description = ''
567         Whether to configure luks support in the initrd, when no luks
568         devices are configured.
569       '';
570     };
572     boot.initrd.luks.reusePassphrases = mkOption {
573       type = types.bool;
574       default = true;
575       description = ''
576         When opening a new LUKS device try reusing last successful
577         passphrase.
579         Useful for mounting a number of devices that use the same
580         passphrase without retyping it several times.
582         Such setup can be useful if you use {command}`cryptsetup luksSuspend`.
583         Different LUKS devices will still have
584         different master keys even when using the same passphrase.
585       '';
586     };
588     boot.initrd.luks.devices = mkOption {
589       default = { };
590       example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
591       description = ''
592         The encrypted disk that should be opened before the root
593         filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM
594         setups are supported. The unencrypted devices can be accessed as
595         {file}`/dev/mapper/«name»`.
596       '';
598       type = with types; attrsOf (submodule (
599         { config, name, ... }: { options = {
601           name = mkOption {
602             visible = false;
603             default = name;
604             example = "luksroot";
605             type = types.str;
606             description = "Name of the unencrypted device in {file}`/dev/mapper`.";
607           };
609           device = mkOption {
610             example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08";
611             type = types.str;
612             description = "Path of the underlying encrypted block device.";
613           };
615           header = mkOption {
616             default = null;
617             example = "/root/header.img";
618             type = types.nullOr types.str;
619             description = ''
620               The name of the file or block device that
621               should be used as header for the encrypted device.
622             '';
623           };
625           keyFile = mkOption {
626             default = null;
627             example = "/dev/sdb1";
628             type = types.nullOr types.str;
629             description = ''
630               The name of the file (can be a raw device or a partition) that
631               should be used as the decryption key for the encrypted device. If
632               not specified, you will be prompted for a passphrase instead.
633             '';
634           };
636           tryEmptyPassphrase = mkOption {
637             default = false;
638             type = types.bool;
639             description = ''
640               If keyFile fails then try an empty passphrase first before
641               prompting for password.
642             '';
643           };
645           keyFileTimeout = mkOption {
646             default = null;
647             example = 5;
648             type = types.nullOr types.int;
649             description = ''
650               The amount of time in seconds for a keyFile to appear before
651               timing out and trying passwords.
652             '';
653           };
655           keyFileSize = mkOption {
656             default = null;
657             example = 4096;
658             type = types.nullOr types.int;
659             description = ''
660               The size of the key file. Use this if only the beginning of the
661               key file should be used as a key (often the case if a raw device
662               or partition is used as key file). If not specified, the whole
663               `keyFile` will be used decryption, instead of just
664               the first `keyFileSize` bytes.
665             '';
666           };
668           keyFileOffset = mkOption {
669             default = null;
670             example = 4096;
671             type = types.nullOr types.int;
672             description = ''
673               The offset of the key file. Use this in combination with
674               `keyFileSize` to use part of a file as key file
675               (often the case if a raw device or partition is used as a key file).
676               If not specified, the key begins at the first byte of
677               `keyFile`.
678             '';
679           };
681           # FIXME: get rid of this option.
682           preLVM = mkOption {
683             default = true;
684             type = types.bool;
685             description = "Whether the luksOpen will be attempted before LVM scan or after it.";
686           };
688           allowDiscards = mkOption {
689             default = false;
690             type = types.bool;
691             description = ''
692               Whether to allow TRIM requests to the underlying device. This option
693               has security implications; please read the LUKS documentation before
694               activating it.
695               This option is incompatible with authenticated encryption (dm-crypt
696               stacked over dm-integrity).
697             '';
698           };
700           bypassWorkqueues = mkOption {
701             default = false;
702             type = types.bool;
703             description = ''
704               Whether to bypass dm-crypt's internal read and write workqueues.
705               Enabling this should improve performance on SSDs; see
706               [here](https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance)
707               for more information. Needs Linux 5.9 or later.
708             '';
709           };
711           fallbackToPassword = mkOption {
712             default = false;
713             type = types.bool;
714             description = ''
715               Whether to fallback to interactive passphrase prompt if the keyfile
716               cannot be found. This will prevent unattended boot should the keyfile
717               go missing.
718             '';
719           };
721           gpgCard = mkOption {
722             default = null;
723             description = ''
724               The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard.
725               If null (the default), GPG-Smartcard will be disabled for this device.
726             '';
728             type = with types; nullOr (submodule {
729               options = {
730                 gracePeriod = mkOption {
731                   default = 10;
732                   type = types.int;
733                   description = "Time in seconds to wait for the GPG Smartcard.";
734                 };
736                 encryptedPass = mkOption {
737                   type = types.path;
738                   description = "Path to the GPG encrypted passphrase.";
739                 };
741                 publicKey = mkOption {
742                   type = types.path;
743                   description = "Path to the Public Key.";
744                 };
745               };
746             });
747           };
749           fido2 = {
750             credential = mkOption {
751               default = null;
752               example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2";
753               type = types.nullOr types.str;
754               description = "The FIDO2 credential ID.";
755             };
757             credentials = mkOption {
758               default = [];
759               example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ];
760               type = types.listOf types.str;
761               description = ''
762                 List of FIDO2 credential IDs.
764                 Use this if you have multiple FIDO2 keys you want to use for the same luks device.
765               '';
766             };
768             gracePeriod = mkOption {
769               default = 10;
770               type = types.int;
771               description = "Time in seconds to wait for the FIDO2 key.";
772             };
774             passwordLess = mkOption {
775               default = false;
776               type = types.bool;
777               description = ''
778                 Defines whatever to use an empty string as a default salt.
780                 Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/).
781               '';
782             };
783           };
785           yubikey = mkOption {
786             default = null;
787             description = ''
788               The options to use for this LUKS device in YubiKey-PBA.
789               If null (the default), YubiKey-PBA will be disabled for this device.
790             '';
792             type = with types; nullOr (submodule {
793               options = {
794                 twoFactor = mkOption {
795                   default = true;
796                   type = types.bool;
797                   description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false).";
798                 };
800                 slot = mkOption {
801                   default = 2;
802                   type = types.int;
803                   description = "Which slot on the YubiKey to challenge.";
804                 };
806                 saltLength = mkOption {
807                   default = 16;
808                   type = types.int;
809                   description = "Length of the new salt in byte (64 is the effective maximum).";
810                 };
812                 keyLength = mkOption {
813                   default = 64;
814                   type = types.int;
815                   description = "Length of the LUKS slot key derived with PBKDF2 in byte.";
816                 };
818                 iterationStep = mkOption {
819                   default = 0;
820                   type = types.int;
821                   description = "How much the iteration count for PBKDF2 is increased at each successful authentication.";
822                 };
824                 gracePeriod = mkOption {
825                   default = 10;
826                   type = types.int;
827                   description = "Time in seconds to wait for the YubiKey.";
828                 };
830                 /* TODO: Add to the documentation of the current module:
832                    Options related to the storing the salt.
833                 */
834                 storage = {
835                   device = mkOption {
836                     default = "/dev/sda1";
837                     type = types.path;
838                     description = ''
839                       An unencrypted device that will temporarily be mounted in stage-1.
840                       Must contain the current salt to create the challenge for this LUKS device.
841                     '';
842                   };
844                   fsType = mkOption {
845                     default = "vfat";
846                     type = types.str;
847                     description = "The filesystem of the unencrypted device.";
848                   };
850                   path = mkOption {
851                     default = "/crypt-storage/default";
852                     type = types.str;
853                     description = ''
854                       Absolute path of the salt on the unencrypted device with
855                       that device's root directory as "/".
856                     '';
857                   };
858                 };
859               };
860             });
861           };
863           preOpenCommands = mkOption {
864             type = types.lines;
865             default = "";
866             example = ''
867               mkdir -p /tmp/persistent
868               mount -t zfs rpool/safe/persistent /tmp/persistent
869             '';
870             description = ''
871               Commands that should be run right before we try to mount our LUKS device.
872               This can be useful, if the keys needed to open the drive is on another partition.
873             '';
874           };
876           postOpenCommands = mkOption {
877             type = types.lines;
878             default = "";
879             example = ''
880               umount /tmp/persistent
881             '';
882             description = ''
883               Commands that should be run right after we have mounted our LUKS device.
884             '';
885           };
887           crypttabExtraOpts = mkOption {
888             type = with types; listOf singleLineStr;
889             default = [];
890             example = [ "_netdev" ];
891             visible = false;
892             description = ''
893               Only used with systemd stage 1.
895               Extra options to append to the last column of the generated crypttab file.
896             '';
897           };
898         };
900         config = mkIf (clevis.enable && (hasAttr name clevis.devices)) {
901           preOpenCommands = mkIf (!systemd.enable) ''
902             mkdir -p /clevis-${name}
903             mount -t ramfs none /clevis-${name}
904             clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted
905           '';
906           keyFile = "/clevis-${name}/decrypted";
907           fallbackToPassword = !systemd.enable;
908           postOpenCommands = mkIf (!systemd.enable) ''
909             umount /clevis-${name}
910           '';
911         };
912       }));
913     };
915     boot.initrd.luks.gpgSupport = mkOption {
916       default = false;
917       type = types.bool;
918       description = ''
919         Enables support for authenticating with a GPG encrypted password.
920       '';
921     };
923     boot.initrd.luks.yubikeySupport = mkOption {
924       default = false;
925       type = types.bool;
926       description = ''
927             Enables support for authenticating with a YubiKey on LUKS devices.
928             See the NixOS wiki for information on how to properly setup a LUKS device
929             and a YubiKey to work with this feature.
930           '';
931     };
933     boot.initrd.luks.fido2Support = mkOption {
934       default = false;
935       type = types.bool;
936       description = ''
937         Enables support for authenticating with FIDO2 devices.
938       '';
939     };
941   };
943   config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) {
945     assertions =
946       [ { assertion = !(luks.gpgSupport && luks.yubikeySupport);
947           message = "YubiKey and GPG Card may not be used at the same time.";
948         }
950         { assertion = !(luks.gpgSupport && luks.fido2Support);
951           message = "FIDO2 and GPG Card may not be used at the same time.";
952         }
954         { assertion = !(luks.fido2Support && luks.yubikeySupport);
955           message = "FIDO2 and YubiKey may not be used at the same time.";
956         }
958         { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices)
959                       -> versionAtLeast kernelPackages.kernel.version "5.9";
960           message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
961         }
963         { assertion = !config.boot.initrd.systemd.enable -> all (x: x.keyFileTimeout == null) (attrValues luks.devices);
964           message = "boot.initrd.luks.devices.<name>.keyFileTimeout is only supported for systemd initrd";
965         }
967         { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices);
968           message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1.";
969         }
970         { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices);
971           message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1.";
972         }
973         { assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio;
974           message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1.";
975         }
976         { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices);
977           message = "boot.initrd.luks.devices.<name>.preOpenCommands and postOpenCommands is not supported by systemd stage 1. Please bind a service to cryptsetup.target or cryptsetup-pre.target instead.";
978         }
979         # TODO
980         { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport;
981           message = "systemd stage 1 does not support GPG smartcards yet.";
982         }
983         { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support;
984           message = ''
985             systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.fido2Support`.
986             Use systemd-cryptenroll(1) to configure FIDO2 support, and set
987             `boot.initrd.luks.devices.''${DEVICE}.crypttabExtraOpts` as appropriate per crypttab(5)
988             (e.g. `fido2-device=auto`).
989           '';
990         }
991         # TODO
992         { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport;
993           message = "systemd stage 1 does not support Yubikeys yet.";
994         }
995       ];
997     # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested
998     boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks
999       ["firewire_ohci" "firewire_core" "firewire_sbp2"];
1001     # Some modules that may be needed for mounting anything ciphered
1002     boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ]
1003       ++ luks.cryptoModules
1004       # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged
1005       # remove once 'modprobe --show-depends xts' shows ecb as a dependency
1006       ++ (optional (builtins.elem "xts" luks.cryptoModules) "ecb");
1008     # copy the cryptsetup binary and it's dependencies
1009     boot.initrd.extraUtilsCommands = let
1010       pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } ''
1011         mkdir -p "$out/bin"
1012         cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512"
1013         strip -s "$out/bin/pbkdf2-sha512"
1014       '';
1015     in
1016     mkIf (!config.boot.initrd.systemd.enable) ''
1017       copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
1018       copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
1019       sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
1021       ${optionalString luks.yubikeySupport ''
1022         copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
1023         copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo
1024         copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl
1026         copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512
1028         mkdir -p $out/etc/ssl
1029         cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl
1031         cat > $out/bin/openssl-wrap <<EOF
1032         #!$out/bin/sh
1033         export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
1034         $out/bin/openssl "\$@"
1035         EOF
1036         chmod +x $out/bin/openssl-wrap
1037       ''}
1039       ${optionalString luks.fido2Support ''
1040         copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks
1041       ''}
1044       ${optionalString luks.gpgSupport ''
1045         copy_bin_and_libs ${pkgs.gnupg}/bin/gpg
1046         copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent
1047         copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon
1049         ${concatMapStringsSep "\n" (x:
1050           optionalString (x.gpgCard != null)
1051             ''
1052               mkdir -p $out/secrets/gpg-keys/${x.device}
1053               cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg
1054               cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc
1055             ''
1056           ) (attrValues luks.devices)
1057         }
1058       ''}
1059     '';
1061     boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
1062       $out/bin/cryptsetup --version
1063       ${optionalString luks.yubikeySupport ''
1064         $out/bin/ykchalresp -V
1065         $out/bin/ykinfo -V
1066         $out/bin/openssl-wrap version
1067       ''}
1068       ${optionalString luks.gpgSupport ''
1069         $out/bin/gpg --version
1070         $out/bin/gpg-agent --version
1071         $out/bin/scdaemon --version
1072       ''}
1073       ${optionalString luks.fido2Support ''
1074         $out/bin/fido2luks --version
1075       ''}
1076     '';
1078     boot.initrd.systemd = {
1079       contents."/etc/crypttab".source = stage1Crypttab;
1081       extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/bin/systemd-cryptsetup";
1083       additionalUpstreamUnits = [
1084         "cryptsetup-pre.target"
1085         "cryptsetup.target"
1086         "remote-cryptsetup.target"
1087       ];
1088       storePaths = [
1089         "${config.boot.initrd.systemd.package}/bin/systemd-cryptsetup"
1090         "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator"
1091       ] ++ lib.optionals config.boot.initrd.systemd.tpm2.enable [
1092         "${config.boot.initrd.systemd.package}/lib/cryptsetup/libcryptsetup-token-systemd-tpm2.so"
1093       ];
1095     };
1096     # We do this because we need the udev rules from the package
1097     boot.initrd.services.lvm.enable = true;
1099     boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands;
1100     boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands);
1101     boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands);
1103     boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; in
1104       mkIf (clevis.enable && systemd.enable) (
1105         (mapAttrs'
1106           (name: _: nameValuePair "cryptsetup-clevis-${name}" {
1107             wantedBy = [ "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" ];
1108             before = [
1109               "systemd-cryptsetup@${utils.escapeSystemdPath name}.service"
1110               "initrd-switch-root.target"
1111               "shutdown.target"
1112             ];
1113             wants = [ "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target";
1114             after = [ "systemd-modules-load.service" "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target";
1115             script = ''
1116               mkdir -p /clevis-${name}
1117               mount -t ramfs none /clevis-${name}
1118               umask 277
1119               clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted
1120             '';
1121             conflicts = [ "initrd-switch-root.target" "shutdown.target" ];
1122             unitConfig.DefaultDependencies = "no";
1123             serviceConfig = {
1124               Type = "oneshot";
1125               RemainAfterExit = true;
1126               ExecStop = "${config.boot.initrd.systemd.package.util-linux}/bin/umount /clevis-${name}";
1127             };
1128           })
1129           devicesWithClevis)
1130       );
1132     environment.systemPackages = [ pkgs.cryptsetup ];
1133   };