at 23.11-beta 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 "$new_response" ]; then 355 echo "Warning: Unable to generate new challenge response, current challenge persists!" 356 umount /crypt-storage 357 return 358 fi 359 360 if [ ! -z "$k_user" ]; then 361 new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)" 362 else 363 new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)" 364 fi 365 366 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key 367 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key 368 369 if [ $? == 0 ]; then 370 echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path} 371 sync /crypt-storage${dev.yubikey.storage.path} 372 else 373 echo "Warning: Could not update LUKS key, current challenge persists!" 374 fi 375 376 rm -f /crypt-ramfs/new_key 377 umount /crypt-storage 378 } 379 380 open_with_hardware() { 381 if wait_yubikey ${toString dev.yubikey.gracePeriod}; then 382 do_open_yubikey 383 else 384 echo "No YubiKey found, falling back to non-YubiKey open procedure" 385 open_normally 386 fi 387 } 388 ''} 389 390 ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) '' 391 392 do_open_gpg_card() { 393 # Make all of these local to this function 394 # to prevent their values being leaked 395 local pin 396 local opened 397 398 gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null 399 400 gpg --card-status > /dev/null 2> /dev/null 401 402 for try in $(seq 3); do 403 echo -n "PIN for GPG Card associated with device ${dev.device}: " 404 pin= 405 while true; do 406 if [ -e /crypt-ramfs/passphrase ]; then 407 echo "reused" 408 pin=$(cat /crypt-ramfs/passphrase) 409 break 410 else 411 # and try reading it from /dev/console with a timeout 412 IFS= read -t 1 -r pin 413 if [ -n "$pin" ]; then 414 ${if luks.reusePassphrases then '' 415 # remember it for the next device 416 echo -n "$pin" > /crypt-ramfs/passphrase 417 '' else '' 418 # Don't save it to ramfs. We are very paranoid 419 ''} 420 echo 421 break 422 fi 423 fi 424 done 425 echo -n "Verifying passphrase for ${dev.device}..." 426 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 427 if [ $? == 0 ]; then 428 echo " - success" 429 ${if luks.reusePassphrases then '' 430 # we don't rm here because we might reuse it for the next device 431 '' else '' 432 rm -f /crypt-ramfs/passphrase 433 ''} 434 break 435 else 436 echo " - failure" 437 # ask for a different one 438 rm -f /crypt-ramfs/passphrase 439 fi 440 done 441 442 [ "$opened" == false ] && die "Maximum authentication errors reached" 443 } 444 445 open_with_hardware() { 446 if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then 447 do_open_gpg_card 448 else 449 echo "No GPG Card found, falling back to normal open procedure" 450 open_normally 451 fi 452 } 453 ''} 454 455 ${optionalString (luks.fido2Support && fido2luksCredentials != []) '' 456 457 open_with_hardware() { 458 local passsphrase 459 460 ${if dev.fido2.passwordLess then '' 461 export passphrase="" 462 '' else '' 463 read -rsp "FIDO2 salt for ${dev.device}: " passphrase 464 echo 465 ''} 466 ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' 467 echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest." 468 echo "Please move your mouse to create needed randomness." 469 ''} 470 echo "Waiting for your FIDO2 device..." 471 fido2luks open${optionalString dev.allowDiscards " --allow-discards"} ${dev.device} ${dev.name} "${builtins.concatStringsSep "," fido2luksCredentials}" --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase 472 if [ $? -ne 0 ]; then 473 echo "No FIDO2 key found, falling back to normal open procedure" 474 open_normally 475 fi 476 } 477 ''} 478 479 # commands to run right before we mount our device 480 ${dev.preOpenCommands} 481 482 ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && fido2luksCredentials != []) then '' 483 open_with_hardware 484 '' else '' 485 open_normally 486 ''} 487 488 # commands to run right after we mounted our device 489 ${dev.postOpenCommands} 490 ''; 491 492 askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' 493 #!/bin/sh 494 495 ${commonFunctions} 496 497 while true; do 498 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" 499 device=$(cat /crypt-ramfs/device) 500 501 echo -n "Passphrase for $device: " 502 IFS= read -rs passphrase 503 echo 504 505 rm /crypt-ramfs/device 506 echo -n "$passphrase" > /crypt-ramfs/passphrase 507 done 508 ''; 509 510 preLVM = filterAttrs (n: v: v.preLVM) luks.devices; 511 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; 512 513 514 stage1Crypttab = pkgs.writeText "initrd-crypttab" (lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: let 515 opts = v.crypttabExtraOpts 516 ++ optional v.allowDiscards "discard" 517 ++ optionals v.bypassWorkqueues [ "no-read-workqueue" "no-write-workqueue" ] 518 ++ optional (v.header != null) "header=${v.header}" 519 ++ optional (v.keyFileOffset != null) "keyfile-offset=${toString v.keyFileOffset}" 520 ++ optional (v.keyFileSize != null) "keyfile-size=${toString v.keyFileSize}" 521 ++ optional (v.keyFileTimeout != null) "keyfile-timeout=${builtins.toString v.keyFileTimeout}s" 522 ++ optional (v.tryEmptyPassphrase) "try-empty-password=true" 523 ; 524 in "${n} ${v.device} ${if v.keyFile == null then "-" else v.keyFile} ${lib.concatStringsSep "," opts}") luks.devices)); 525 526in 527{ 528 imports = [ 529 (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") 530 ]; 531 532 options = { 533 534 boot.initrd.luks.mitigateDMAAttacks = mkOption { 535 type = types.bool; 536 default = true; 537 description = lib.mdDoc '' 538 Unless enabled, encryption keys can be easily recovered by an attacker with physical 539 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port. 540 More information is available at <https://en.wikipedia.org/wiki/DMA_attack>. 541 542 This option blacklists FireWire drivers, but doesn't remove them. You can manually 543 load the drivers if you need to use a FireWire device, but don't forget to unload them! 544 ''; 545 }; 546 547 boot.initrd.luks.cryptoModules = mkOption { 548 type = types.listOf types.str; 549 default = 550 [ "aes" "aes_generic" "blowfish" "twofish" 551 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" 552 "af_alg" "algif_skcipher" 553 ]; 554 description = lib.mdDoc '' 555 A list of cryptographic kernel modules needed to decrypt the root device(s). 556 The default includes all common modules. 557 ''; 558 }; 559 560 boot.initrd.luks.forceLuksSupportInInitrd = mkOption { 561 type = types.bool; 562 default = false; 563 internal = true; 564 description = lib.mdDoc '' 565 Whether to configure luks support in the initrd, when no luks 566 devices are configured. 567 ''; 568 }; 569 570 boot.initrd.luks.reusePassphrases = mkOption { 571 type = types.bool; 572 default = true; 573 description = lib.mdDoc '' 574 When opening a new LUKS device try reusing last successful 575 passphrase. 576 577 Useful for mounting a number of devices that use the same 578 passphrase without retyping it several times. 579 580 Such setup can be useful if you use {command}`cryptsetup luksSuspend`. 581 Different LUKS devices will still have 582 different master keys even when using the same passphrase. 583 ''; 584 }; 585 586 boot.initrd.luks.devices = mkOption { 587 default = { }; 588 example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; }; 589 description = lib.mdDoc '' 590 The encrypted disk that should be opened before the root 591 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM 592 setups are supported. The unencrypted devices can be accessed as 593 {file}`/dev/mapper/«name»`. 594 ''; 595 596 type = with types; attrsOf (submodule ( 597 { name, ... }: { options = { 598 599 name = mkOption { 600 visible = false; 601 default = name; 602 example = "luksroot"; 603 type = types.str; 604 description = lib.mdDoc "Name of the unencrypted device in {file}`/dev/mapper`."; 605 }; 606 607 device = mkOption { 608 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; 609 type = types.str; 610 description = lib.mdDoc "Path of the underlying encrypted block device."; 611 }; 612 613 header = mkOption { 614 default = null; 615 example = "/root/header.img"; 616 type = types.nullOr types.str; 617 description = lib.mdDoc '' 618 The name of the file or block device that 619 should be used as header for the encrypted device. 620 ''; 621 }; 622 623 keyFile = mkOption { 624 default = null; 625 example = "/dev/sdb1"; 626 type = types.nullOr types.str; 627 description = lib.mdDoc '' 628 The name of the file (can be a raw device or a partition) that 629 should be used as the decryption key for the encrypted device. If 630 not specified, you will be prompted for a passphrase instead. 631 ''; 632 }; 633 634 tryEmptyPassphrase = mkOption { 635 default = false; 636 type = types.bool; 637 description = lib.mdDoc '' 638 If keyFile fails then try an empty passphrase first before 639 prompting for password. 640 ''; 641 }; 642 643 keyFileTimeout = mkOption { 644 default = null; 645 example = 5; 646 type = types.nullOr types.int; 647 description = lib.mdDoc '' 648 The amount of time in seconds for a keyFile to appear before 649 timing out and trying passwords. 650 ''; 651 }; 652 653 keyFileSize = mkOption { 654 default = null; 655 example = 4096; 656 type = types.nullOr types.int; 657 description = lib.mdDoc '' 658 The size of the key file. Use this if only the beginning of the 659 key file should be used as a key (often the case if a raw device 660 or partition is used as key file). If not specified, the whole 661 `keyFile` will be used decryption, instead of just 662 the first `keyFileSize` bytes. 663 ''; 664 }; 665 666 keyFileOffset = mkOption { 667 default = null; 668 example = 4096; 669 type = types.nullOr types.int; 670 description = lib.mdDoc '' 671 The offset of the key file. Use this in combination with 672 `keyFileSize` to use part of a file as key file 673 (often the case if a raw device or partition is used as a key file). 674 If not specified, the key begins at the first byte of 675 `keyFile`. 676 ''; 677 }; 678 679 # FIXME: get rid of this option. 680 preLVM = mkOption { 681 default = true; 682 type = types.bool; 683 description = lib.mdDoc "Whether the luksOpen will be attempted before LVM scan or after it."; 684 }; 685 686 allowDiscards = mkOption { 687 default = false; 688 type = types.bool; 689 description = lib.mdDoc '' 690 Whether to allow TRIM requests to the underlying device. This option 691 has security implications; please read the LUKS documentation before 692 activating it. 693 This option is incompatible with authenticated encryption (dm-crypt 694 stacked over dm-integrity). 695 ''; 696 }; 697 698 bypassWorkqueues = mkOption { 699 default = false; 700 type = types.bool; 701 description = lib.mdDoc '' 702 Whether to bypass dm-crypt's internal read and write workqueues. 703 Enabling this should improve performance on SSDs; see 704 [here](https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance) 705 for more information. Needs Linux 5.9 or later. 706 ''; 707 }; 708 709 fallbackToPassword = mkOption { 710 default = false; 711 type = types.bool; 712 description = lib.mdDoc '' 713 Whether to fallback to interactive passphrase prompt if the keyfile 714 cannot be found. This will prevent unattended boot should the keyfile 715 go missing. 716 ''; 717 }; 718 719 gpgCard = mkOption { 720 default = null; 721 description = lib.mdDoc '' 722 The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard. 723 If null (the default), GPG-Smartcard will be disabled for this device. 724 ''; 725 726 type = with types; nullOr (submodule { 727 options = { 728 gracePeriod = mkOption { 729 default = 10; 730 type = types.int; 731 description = lib.mdDoc "Time in seconds to wait for the GPG Smartcard."; 732 }; 733 734 encryptedPass = mkOption { 735 type = types.path; 736 description = lib.mdDoc "Path to the GPG encrypted passphrase."; 737 }; 738 739 publicKey = mkOption { 740 type = types.path; 741 description = lib.mdDoc "Path to the Public Key."; 742 }; 743 }; 744 }); 745 }; 746 747 fido2 = { 748 credential = mkOption { 749 default = null; 750 example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; 751 type = types.nullOr types.str; 752 description = lib.mdDoc "The FIDO2 credential ID."; 753 }; 754 755 credentials = mkOption { 756 default = []; 757 example = [ "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2" ]; 758 type = types.listOf types.str; 759 description = lib.mdDoc '' 760 List of FIDO2 credential IDs. 761 762 Use this if you have multiple FIDO2 keys you want to use for the same luks device. 763 ''; 764 }; 765 766 gracePeriod = mkOption { 767 default = 10; 768 type = types.int; 769 description = lib.mdDoc "Time in seconds to wait for the FIDO2 key."; 770 }; 771 772 passwordLess = mkOption { 773 default = false; 774 type = types.bool; 775 description = lib.mdDoc '' 776 Defines whatever to use an empty string as a default salt. 777 778 Enable only when your device is PIN protected, such as [Trezor](https://trezor.io/). 779 ''; 780 }; 781 }; 782 783 yubikey = mkOption { 784 default = null; 785 description = lib.mdDoc '' 786 The options to use for this LUKS device in YubiKey-PBA. 787 If null (the default), YubiKey-PBA will be disabled for this device. 788 ''; 789 790 type = with types; nullOr (submodule { 791 options = { 792 twoFactor = mkOption { 793 default = true; 794 type = types.bool; 795 description = lib.mdDoc "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false)."; 796 }; 797 798 slot = mkOption { 799 default = 2; 800 type = types.int; 801 description = lib.mdDoc "Which slot on the YubiKey to challenge."; 802 }; 803 804 saltLength = mkOption { 805 default = 16; 806 type = types.int; 807 description = lib.mdDoc "Length of the new salt in byte (64 is the effective maximum)."; 808 }; 809 810 keyLength = mkOption { 811 default = 64; 812 type = types.int; 813 description = lib.mdDoc "Length of the LUKS slot key derived with PBKDF2 in byte."; 814 }; 815 816 iterationStep = mkOption { 817 default = 0; 818 type = types.int; 819 description = lib.mdDoc "How much the iteration count for PBKDF2 is increased at each successful authentication."; 820 }; 821 822 gracePeriod = mkOption { 823 default = 10; 824 type = types.int; 825 description = lib.mdDoc "Time in seconds to wait for the YubiKey."; 826 }; 827 828 /* TODO: Add to the documentation of the current module: 829 830 Options related to the storing the salt. 831 */ 832 storage = { 833 device = mkOption { 834 default = "/dev/sda1"; 835 type = types.path; 836 description = lib.mdDoc '' 837 An unencrypted device that will temporarily be mounted in stage-1. 838 Must contain the current salt to create the challenge for this LUKS device. 839 ''; 840 }; 841 842 fsType = mkOption { 843 default = "vfat"; 844 type = types.str; 845 description = lib.mdDoc "The filesystem of the unencrypted device."; 846 }; 847 848 path = mkOption { 849 default = "/crypt-storage/default"; 850 type = types.str; 851 description = lib.mdDoc '' 852 Absolute path of the salt on the unencrypted device with 853 that device's root directory as "/". 854 ''; 855 }; 856 }; 857 }; 858 }); 859 }; 860 861 preOpenCommands = mkOption { 862 type = types.lines; 863 default = ""; 864 example = '' 865 mkdir -p /tmp/persistent 866 mount -t zfs rpool/safe/persistent /tmp/persistent 867 ''; 868 description = lib.mdDoc '' 869 Commands that should be run right before we try to mount our LUKS device. 870 This can be useful, if the keys needed to open the drive is on another partition. 871 ''; 872 }; 873 874 postOpenCommands = mkOption { 875 type = types.lines; 876 default = ""; 877 example = '' 878 umount /tmp/persistent 879 ''; 880 description = lib.mdDoc '' 881 Commands that should be run right after we have mounted our LUKS device. 882 ''; 883 }; 884 885 crypttabExtraOpts = mkOption { 886 type = with types; listOf singleLineStr; 887 default = []; 888 example = [ "_netdev" ]; 889 visible = false; 890 description = lib.mdDoc '' 891 Only used with systemd stage 1. 892 893 Extra options to append to the last column of the generated crypttab file. 894 ''; 895 }; 896 }; 897 })); 898 }; 899 900 boot.initrd.luks.gpgSupport = mkOption { 901 default = false; 902 type = types.bool; 903 description = lib.mdDoc '' 904 Enables support for authenticating with a GPG encrypted password. 905 ''; 906 }; 907 908 boot.initrd.luks.yubikeySupport = mkOption { 909 default = false; 910 type = types.bool; 911 description = lib.mdDoc '' 912 Enables support for authenticating with a YubiKey on LUKS devices. 913 See the NixOS wiki for information on how to properly setup a LUKS device 914 and a YubiKey to work with this feature. 915 ''; 916 }; 917 918 boot.initrd.luks.fido2Support = mkOption { 919 default = false; 920 type = types.bool; 921 description = lib.mdDoc '' 922 Enables support for authenticating with FIDO2 devices. 923 ''; 924 }; 925 926 }; 927 928 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { 929 930 assertions = 931 [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); 932 message = "YubiKey and GPG Card may not be used at the same time."; 933 } 934 935 { assertion = !(luks.gpgSupport && luks.fido2Support); 936 message = "FIDO2 and GPG Card may not be used at the same time."; 937 } 938 939 { assertion = !(luks.fido2Support && luks.yubikeySupport); 940 message = "FIDO2 and YubiKey may not be used at the same time."; 941 } 942 943 { assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices) 944 -> versionAtLeast kernelPackages.kernel.version "5.9"; 945 message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9"; 946 } 947 948 { assertion = !config.boot.initrd.systemd.enable -> all (x: x.keyFileTimeout == null) (attrValues luks.devices); 949 message = "boot.initrd.luks.devices.<name>.keyFileTimeout is only supported for systemd initrd"; 950 } 951 952 { assertion = config.boot.initrd.systemd.enable -> all (dev: !dev.fallbackToPassword) (attrValues luks.devices); 953 message = "boot.initrd.luks.devices.<name>.fallbackToPassword is implied by systemd stage 1."; 954 } 955 { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preLVM) (attrValues luks.devices); 956 message = "boot.initrd.luks.devices.<name>.preLVM is not used by systemd stage 1."; 957 } 958 { assertion = config.boot.initrd.systemd.enable -> options.boot.initrd.luks.reusePassphrases.highestPrio == defaultPrio; 959 message = "boot.initrd.luks.reusePassphrases has no effect with systemd stage 1."; 960 } 961 { assertion = config.boot.initrd.systemd.enable -> all (dev: dev.preOpenCommands == "" && dev.postOpenCommands == "") (attrValues luks.devices); 962 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."; 963 } 964 # TODO 965 { assertion = config.boot.initrd.systemd.enable -> !luks.gpgSupport; 966 message = "systemd stage 1 does not support GPG smartcards yet."; 967 } 968 { assertion = config.boot.initrd.systemd.enable -> !luks.fido2Support; 969 message = '' 970 systemd stage 1 does not support configuring FIDO2 unlocking through `boot.initrd.luks.devices.<name>.fido2`. 971 Use systemd-cryptenroll(1) to configure FIDO2 support. 972 ''; 973 } 974 # TODO 975 { assertion = config.boot.initrd.systemd.enable -> !luks.yubikeySupport; 976 message = "systemd stage 1 does not support Yubikeys yet."; 977 } 978 ]; 979 980 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested 981 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks 982 ["firewire_ohci" "firewire_core" "firewire_sbp2"]; 983 984 # Some modules that may be needed for mounting anything ciphered 985 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ] 986 ++ luks.cryptoModules 987 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged 988 # remove once 'modprobe --show-depends xts' shows ecb as a dependency 989 ++ (optional (builtins.elem "xts" luks.cryptoModules) "ecb"); 990 991 # copy the cryptsetup binary and it's dependencies 992 boot.initrd.extraUtilsCommands = let 993 pbkdf2-sha512 = pkgs.runCommandCC "pbkdf2-sha512" { buildInputs = [ pkgs.openssl ]; } '' 994 mkdir -p "$out/bin" 995 cc -O3 -lcrypto ${./pbkdf2-sha512.c} -o "$out/bin/pbkdf2-sha512" 996 strip -s "$out/bin/pbkdf2-sha512" 997 ''; 998 in 999 mkIf (!config.boot.initrd.systemd.enable) '' 1000 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup 1001 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass 1002 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass 1003 1004 ${optionalString luks.yubikeySupport '' 1005 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp 1006 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo 1007 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl 1008 1009 copy_bin_and_libs ${pbkdf2-sha512}/bin/pbkdf2-sha512 1010 1011 mkdir -p $out/etc/ssl 1012 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl 1013 1014 cat > $out/bin/openssl-wrap <<EOF 1015 #!$out/bin/sh 1016 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf 1017 $out/bin/openssl "\$@" 1018 EOF 1019 chmod +x $out/bin/openssl-wrap 1020 ''} 1021 1022 ${optionalString luks.fido2Support '' 1023 copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks 1024 ''} 1025 1026 1027 ${optionalString luks.gpgSupport '' 1028 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg 1029 copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent 1030 copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon 1031 1032 ${concatMapStringsSep "\n" (x: 1033 optionalString (x.gpgCard != null) 1034 '' 1035 mkdir -p $out/secrets/gpg-keys/${x.device} 1036 cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg 1037 cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc 1038 '' 1039 ) (attrValues luks.devices) 1040 } 1041 ''} 1042 ''; 1043 1044 boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) '' 1045 $out/bin/cryptsetup --version 1046 ${optionalString luks.yubikeySupport '' 1047 $out/bin/ykchalresp -V 1048 $out/bin/ykinfo -V 1049 $out/bin/openssl-wrap version 1050 ''} 1051 ${optionalString luks.gpgSupport '' 1052 $out/bin/gpg --version 1053 $out/bin/gpg-agent --version 1054 $out/bin/scdaemon --version 1055 ''} 1056 ${optionalString luks.fido2Support '' 1057 $out/bin/fido2luks --version 1058 ''} 1059 ''; 1060 1061 boot.initrd.systemd = { 1062 contents."/etc/crypttab".source = stage1Crypttab; 1063 1064 extraBin.systemd-cryptsetup = "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup"; 1065 1066 additionalUpstreamUnits = [ 1067 "cryptsetup-pre.target" 1068 "cryptsetup.target" 1069 "remote-cryptsetup.target" 1070 ]; 1071 storePaths = [ 1072 "${config.boot.initrd.systemd.package}/lib/systemd/systemd-cryptsetup" 1073 "${config.boot.initrd.systemd.package}/lib/systemd/system-generators/systemd-cryptsetup-generator" 1074 ]; 1075 1076 }; 1077 # We do this because we need the udev rules from the package 1078 boot.initrd.services.lvm.enable = true; 1079 1080 boot.initrd.preFailCommands = mkIf (!config.boot.initrd.systemd.enable) postCommands; 1081 boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands); 1082 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands); 1083 1084 environment.systemPackages = [ pkgs.cryptsetup ]; 1085 }; 1086}