at 24.11-pre 42 kB view raw
1{ config, options, lib, utils, pkgs, ... }: 2 3with lib; 4 5let 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; 11 12 commonFunctions = '' 13 die() { 14 echo "$@" >&2 15 exit 1 16 } 17 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 } 28 29 wait_target() { 30 local name="$1" 31 local target="$2" 32 local secs="''${3:-10}" 33 local desc="''${4:-$name $target to appear}" 34 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 } 56 57 wait_yubikey() { 58 local secs="''${1:-10}" 59 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 } 83 84 wait_gpgcard() { 85 local secs="''${1:-10}" 86 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 ''; 111 112 preCommands = '' 113 # A place to store crypto things 114 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 120 121 # Cryptsetup locking directory 122 mkdir -p /run/cryptsetup 123 124 # For YubiKey salt storage 125 mkdir -p /crypt-storage 126 127 ${optionalString luks.gpgSupport '' 128 export GPG_TTY=$(tty) 129 export GNUPGHOME=/crypt-ramfs/.gnupg 130 131 gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null 132 ''} 133 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 ''; 139 140 postCommands = '' 141 stty echo 142 umount /crypt-storage 2>/dev/null 143 umount /crypt-ramfs 2>/dev/null 144 ''; 145 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" 158 159 ${optionalString (dev.header != null) '' 160 wait_target "header" ${dev.header} || die "${dev.header} is unavailable" 161 ''} 162 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 } 175 176 177 do_open_passphrase() { 178 local passphrase 179 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 191 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 } 223 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 } 254 255 ${optionalString (luks.yubikeySupport && (dev.yubikey != null)) '' 256 # YubiKey 257 rbtohex() { 258 ( od -An -vtx1 | tr -d ' \n' ) 259 } 260 261 hextorb() { 262 ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) 263 } 264 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 280 281 mount -t ${dev.yubikey.storage.fsType} ${dev.yubikey.storage.device} /crypt-storage || \ 282 die "Failed to mount YubiKey salt storage device" 283 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)" 288 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 ''} 314 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 320 321 echo -n "$k_luks" | hextorb | ${csopen} --key-file=- 322 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 336 337 [ "$opened" == false ] && die "Maximum authentication errors reached" 338 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" 346 347 new_iterations="$iterations" 348 ${optionalString (dev.yubikey.iterationStep > 0) '' 349 new_iterations="$(($new_iterations + ${toString dev.yubikey.iterationStep}))" 350 ''} 351 352 new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" 353 354 new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)" 355 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 361 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 367 368 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key 369 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key 370 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 377 378 rm -f /crypt-ramfs/new_key 379 umount /crypt-storage 380 } 381 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 ''} 391 392 ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) '' 393 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 399 400 gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null 401 402 gpg --card-status > /dev/null 2> /dev/null 403 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 443 444 [ "$opened" == false ] && die "Maximum authentication errors reached" 445 } 446 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 ''} 456 457 ${optionalString (luks.fido2Support && fido2luksCredentials != []) '' 458 459 open_with_hardware() { 460 local passsphrase 461 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 ''} 480 481 # commands to run right before we mount our device 482 ${dev.preOpenCommands} 483 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 ''} 489 490 # commands to run right after we mounted our device 491 ${dev.postOpenCommands} 492 ''; 493 494 askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' 495 #!/bin/sh 496 497 ${commonFunctions} 498 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) 502 503 echo -n "Passphrase for $device: " 504 IFS= read -rs passphrase 505 echo 506 507 rm /crypt-ramfs/device 508 echo -n "$passphrase" > /crypt-ramfs/passphrase 509 done 510 ''; 511 512 preLVM = filterAttrs (n: v: v.preLVM) luks.devices; 513 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; 514 515 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)); 527 528in 529{ 530 imports = [ 531 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") 532 ]; 533 534 options = { 535 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>. 543 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 }; 548 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 }; 561 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 }; 571 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. 578 579 Useful for mounting a number of devices that use the same 580 passphrase without retyping it several times. 581 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 }; 587 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 ''; 597 598 type = with types; attrsOf (submodule ( 599 { config, name, ... }: { options = { 600 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 }; 608 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 }; 614 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 }; 624 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 }; 635 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 }; 644 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 }; 654 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 }; 667 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 }; 680 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 }; 687 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 }; 699 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 }; 710 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 }; 720 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 ''; 727 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 }; 735 736 encryptedPass = mkOption { 737 type = types.path; 738 description = "Path to the GPG encrypted passphrase."; 739 }; 740 741 publicKey = mkOption { 742 type = types.path; 743 description = "Path to the Public Key."; 744 }; 745 }; 746 }); 747 }; 748 749 fido2 = { 750 credential = mkOption { 751 default = null; 752 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; 753 type = types.nullOr types.str; 754 description = "The FIDO2 credential ID."; 755 }; 756 757 credentials = mkOption { 758 default = []; 759 example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ]; 760 type = types.listOf types.str; 761 description = '' 762 List of FIDO2 credential IDs. 763 764 Use this if you have multiple FIDO2 keys you want to use for the same luks device. 765 ''; 766 }; 767 768 gracePeriod = mkOption { 769 default = 10; 770 type = types.int; 771 description = "Time in seconds to wait for the FIDO2 key."; 772 }; 773 774 passwordLess = mkOption { 775 default = false; 776 type = types.bool; 777 description = '' 778 Defines whatever to use an empty string as a default salt. 779 780 Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/). 781 ''; 782 }; 783 }; 784 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 ''; 791 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 }; 799 800 slot = mkOption { 801 default = 2; 802 type = types.int; 803 description = "Which slot on the YubiKey to challenge."; 804 }; 805 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 }; 811 812 keyLength = mkOption { 813 default = 64; 814 type = types.int; 815 description = "Length of the LUKS slot key derived with PBKDF2 in byte."; 816 }; 817 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 }; 823 824 gracePeriod = mkOption { 825 default = 10; 826 type = types.int; 827 description = "Time in seconds to wait for the YubiKey."; 828 }; 829 830 /* TODO: Add to the documentation of the current module: 831 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 }; 843 844 fsType = mkOption { 845 default = "vfat"; 846 type = types.str; 847 description = "The filesystem of the unencrypted device."; 848 }; 849 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 }; 862 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 }; 875 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 }; 886 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. 894 895 Extra options to append to the last column of the generated crypttab file. 896 ''; 897 }; 898 }; 899 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 }; 914 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 }; 922 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 }; 932 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 }; 940 941 }; 942 943 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { 944 945 assertions = 946 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); 947 message = "YubiKey and GPG Card may not be used at the same time."; 948 } 949 950 { assertion = !(luks.gpgSupport && luks.fido2Support); 951 message = "FIDO2 and GPG Card may not be used at the same time."; 952 } 953 954 { assertion = !(luks.fido2Support && luks.yubikeySupport); 955 message = "FIDO2 and YubiKey may not be used at the same time."; 956 } 957 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 } 962 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 } 966 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 ]; 996 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"]; 1000 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"); 1007 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 1020 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 1025 1026 copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512 1027 1028 mkdir -p $out/etc/ssl 1029 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl 1030 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 ''} 1038 1039 ${optionalString luks.fido2Support '' 1040 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks 1041 ''} 1042 1043 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 1048 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 ''; 1060 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 ''; 1077 1078 boot.initrd.systemd = { 1079 contents."/etc/crypttab".source = stage1Crypttab; 1080 1081 extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/bin/systemd-cryptsetup"; 1082 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 ]; 1092 1093 }; 1094 # We do this because we need the udev rules from the package 1095 boot.initrd.services.lvm.enable = true; 1096 1097 boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands; 1098 boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands); 1099 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands); 1100 1101 boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; in 1102 mkIf (clevis.enable && systemd.enable) ( 1103 (mapAttrs' 1104 (name: _: nameValuePair "cryptsetup-clevis-${name}" { 1105 wantedBy = [ "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" ]; 1106 before = [ 1107 "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" 1108 "initrd-switch-root.target" 1109 "shutdown.target" 1110 ]; 1111 wants = [ "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; 1112 after = [ "systemd-modules-load.service" "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; 1113 script = '' 1114 mkdir -p /clevis-${name} 1115 mount -t ramfs none /clevis-${name} 1116 umask 277 1117 clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted 1118 ''; 1119 conflicts = [ "initrd-switch-root.target" "shutdown.target" ]; 1120 unitConfig.DefaultDependencies = "no"; 1121 serviceConfig = { 1122 Type = "oneshot"; 1123 RemainAfterExit = true; 1124 ExecStop = "${config.boot.initrd.systemd.package.util-linux}/bin/umount /clevis-${name}"; 1125 }; 1126 }) 1127 devicesWithClevis) 1128 ); 1129 1130 environment.systemPackages = [ pkgs.cryptsetup ]; 1131 }; 1132}