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