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