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