at 18.09-beta 21 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 luks = config.boot.initrd.luks; 7 8 commonFunctions = '' 9 die() { 10 echo "$@" >&2 11 exit 1 12 } 13 14 wait_target() { 15 local name="$1" 16 local target="$2" 17 local secs="''${3:-10}" 18 local desc="''${4:-$name $target to appear}" 19 20 if [ ! -e $target ]; then 21 echo -n "Waiting $secs seconds for $desc..." 22 local success=false; 23 for try in $(seq $secs); do 24 echo -n "." 25 sleep 1 26 if [ -e $target ]; then 27 success=true 28 break 29 fi 30 done 31 if [ $success == true ]; then 32 echo " - success"; 33 return 0 34 else 35 echo " - failure"; 36 return 1 37 fi 38 fi 39 return 0 40 } 41 42 wait_yubikey() { 43 local secs="''${1:-10}" 44 45 ykinfo -v 1>/dev/null 2>&1 46 if [ $? != 0 ]; then 47 echo -n "Waiting $secs seconds for Yubikey to appear..." 48 local success=false 49 for try in $(seq $secs); do 50 echo -n . 51 sleep 1 52 ykinfo -v 1>/dev/null 2>&1 53 if [ $? == 0 ]; then 54 success=true 55 break 56 fi 57 done 58 if [ $success == true ]; then 59 echo " - success"; 60 return 0 61 else 62 echo " - failure"; 63 return 1 64 fi 65 fi 66 return 0 67 } 68 ''; 69 70 preCommands = '' 71 # A place to store crypto things 72 73 # A ramfs is used here to ensure that the file used to update 74 # the key slot with cryptsetup will never get swapped out. 75 # Warning: Do NOT replace with tmpfs! 76 mkdir -p /crypt-ramfs 77 mount -t ramfs none /crypt-ramfs 78 79 # For Yubikey salt storage 80 mkdir -p /crypt-storage 81 82 # Disable all input echo for the whole stage. We could use read -s 83 # instead but that would ocasionally leak characters between read 84 # invocations. 85 stty -echo 86 ''; 87 88 postCommands = '' 89 stty echo 90 umount /crypt-storage 2>/dev/null 91 umount /crypt-ramfs 2>/dev/null 92 ''; 93 94 openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name; 95 let 96 csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; 97 cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; 98 in '' 99 # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. 100 # if on a USB drive. 101 wait_target "device" ${device} || die "${device} is unavailable" 102 103 ${optionalString (header != null) '' 104 wait_target "header" ${header} || die "${header} is unavailable" 105 ''} 106 107 do_open_passphrase() { 108 local passphrase 109 110 while true; do 111 echo -n "Passphrase for ${device}: " 112 passphrase= 113 while true; do 114 if [ -e /crypt-ramfs/passphrase ]; then 115 echo "reused" 116 passphrase=$(cat /crypt-ramfs/passphrase) 117 break 118 else 119 # ask cryptsetup-askpass 120 echo -n "${device}" > /crypt-ramfs/device 121 122 # and try reading it from /dev/console with a timeout 123 IFS= read -t 1 -r passphrase 124 if [ -n "$passphrase" ]; then 125 ${if luks.reusePassphrases then '' 126 # remember it for the next device 127 echo -n "$passphrase" > /crypt-ramfs/passphrase 128 '' else '' 129 # Don't save it to ramfs. We are very paranoid 130 ''} 131 echo 132 break 133 fi 134 fi 135 done 136 echo -n "Verifiying passphrase for ${device}..." 137 echo -n "$passphrase" | ${csopen} --key-file=- 138 if [ $? == 0 ]; then 139 echo " - success" 140 ${if luks.reusePassphrases then '' 141 # we don't rm here because we might reuse it for the next device 142 '' else '' 143 rm -f /crypt-ramfs/passphrase 144 ''} 145 break 146 else 147 echo " - failure" 148 # ask for a different one 149 rm -f /crypt-ramfs/passphrase 150 fi 151 done 152 } 153 154 # LUKS 155 open_normally() { 156 ${if (keyFile != null) then '' 157 if wait_target "key file" ${keyFile}; then 158 ${csopen} --key-file=${keyFile} \ 159 ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \ 160 ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"} 161 else 162 ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable" 163 echo " - failing back to interactive password prompt" 164 do_open_passphrase 165 fi 166 '' else '' 167 do_open_passphrase 168 ''} 169 } 170 171 ${if luks.yubikeySupport && (yubikey != null) then '' 172 # Yubikey 173 rbtohex() { 174 ( od -An -vtx1 | tr -d ' \n' ) 175 } 176 177 hextorb() { 178 ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) 179 } 180 181 do_open_yubikey() { 182 # Make all of these local to this function 183 # to prevent their values being leaked 184 local salt 185 local iterations 186 local k_user 187 local challenge 188 local response 189 local k_luks 190 local opened 191 local new_salt 192 local new_iterations 193 local new_challenge 194 local new_response 195 local new_k_luks 196 197 mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \ 198 die "Failed to mount Yubikey salt storage device" 199 200 salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')" 201 iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')" 202 challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" 203 response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" 204 205 for try in $(seq 3); do 206 ${optionalString yubikey.twoFactor '' 207 echo -n "Enter two-factor passphrase: " 208 read -r k_user 209 echo 210 ''} 211 212 if [ ! -z "$k_user" ]; then 213 k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" 214 else 215 k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" 216 fi 217 218 echo -n "$k_luks" | hextorb | ${csopen} --key-file=- 219 220 if [ $? == 0 ]; then 221 opened=true 222 break 223 else 224 opened=false 225 echo "Authentication failed!" 226 fi 227 done 228 229 [ "$opened" == false ] && die "Maximum authentication errors reached" 230 231 echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." 232 for i in $(seq ${toString yubikey.saltLength}); do 233 byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; 234 new_salt="$new_salt$byte"; 235 echo -n . 236 done; 237 echo "ok" 238 239 new_iterations="$iterations" 240 ${optionalString (yubikey.iterationStep > 0) '' 241 new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))" 242 ''} 243 244 new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" 245 246 new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)" 247 248 if [ ! -z "$k_user" ]; then 249 new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" 250 else 251 new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" 252 fi 253 254 echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key 255 echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key 256 257 if [ $? == 0 ]; then 258 echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path} 259 else 260 echo "Warning: Could not update LUKS key, current challenge persists!" 261 fi 262 263 rm -f /crypt-ramfs/new_key 264 umount /crypt-storage 265 } 266 267 open_yubikey() { 268 if wait_yubikey ${toString yubikey.gracePeriod}; then 269 do_open_yubikey 270 else 271 echo "No yubikey found, falling back to non-yubikey open procedure" 272 open_normally 273 fi 274 } 275 276 open_yubikey 277 '' else '' 278 open_normally 279 ''} 280 ''; 281 282 askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' 283 #!/bin/sh 284 285 ${commonFunctions} 286 287 while true; do 288 wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" 289 device=$(cat /crypt-ramfs/device) 290 291 echo -n "Passphrase for $device: " 292 IFS= read -rs passphrase 293 echo 294 295 rm /crypt-ramfs/device 296 echo -n "$passphrase" > /crypt-ramfs/passphrase 297 done 298 ''; 299 300 preLVM = filterAttrs (n: v: v.preLVM) luks.devices; 301 postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; 302 303in 304{ 305 306 options = { 307 308 boot.initrd.luks.mitigateDMAAttacks = mkOption { 309 type = types.bool; 310 default = true; 311 description = '' 312 Unless enabled, encryption keys can be easily recovered by an attacker with physical 313 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port. 314 More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>. 315 316 This option blacklists FireWire drivers, but doesn't remove them. You can manually 317 load the drivers if you need to use a FireWire device, but don't forget to unload them! 318 ''; 319 }; 320 321 boot.initrd.luks.cryptoModules = mkOption { 322 type = types.listOf types.str; 323 default = 324 [ "aes" "aes_generic" "blowfish" "twofish" 325 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" 326 327 (if pkgs.stdenv.hostPlatform.system == "x86_64-linux" then "aes_x86_64" else "aes_i586") 328 ]; 329 description = '' 330 A list of cryptographic kernel modules needed to decrypt the root device(s). 331 The default includes all common modules. 332 ''; 333 }; 334 335 boot.initrd.luks.forceLuksSupportInInitrd = mkOption { 336 type = types.bool; 337 default = false; 338 internal = true; 339 description = '' 340 Whether to configure luks support in the initrd, when no luks 341 devices are configured. 342 ''; 343 }; 344 345 boot.initrd.luks.reusePassphrases = mkOption { 346 type = types.bool; 347 default = true; 348 description = '' 349 When opening a new LUKS device try reusing last successful 350 passphrase. 351 352 Useful for mounting a number of devices that use the same 353 passphrase without retyping it several times. 354 355 Such setup can be useful if you use <command>cryptsetup 356 luksSuspend</command>. Different LUKS devices will still have 357 different master keys even when using the same passphrase. 358 ''; 359 }; 360 361 boot.initrd.luks.devices = mkOption { 362 default = { }; 363 example = { "luksroot".device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; }; 364 description = '' 365 The encrypted disk that should be opened before the root 366 filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM 367 setups are supported. The unencrypted devices can be accessed as 368 <filename>/dev/mapper/<replaceable>name</replaceable></filename>. 369 ''; 370 371 type = with types; loaOf (submodule ( 372 { name, ... }: { options = { 373 374 name = mkOption { 375 visible = false; 376 default = name; 377 example = "luksroot"; 378 type = types.str; 379 description = "Name of the unencrypted device in <filename>/dev/mapper</filename>."; 380 }; 381 382 device = mkOption { 383 example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; 384 type = types.str; 385 description = "Path of the underlying encrypted block device."; 386 }; 387 388 header = mkOption { 389 default = null; 390 example = "/root/header.img"; 391 type = types.nullOr types.str; 392 description = '' 393 The name of the file or block device that 394 should be used as header for the encrypted device. 395 ''; 396 }; 397 398 keyFile = mkOption { 399 default = null; 400 example = "/dev/sdb1"; 401 type = types.nullOr types.str; 402 description = '' 403 The name of the file (can be a raw device or a partition) that 404 should be used as the decryption key for the encrypted device. If 405 not specified, you will be prompted for a passphrase instead. 406 ''; 407 }; 408 409 keyFileSize = mkOption { 410 default = null; 411 example = 4096; 412 type = types.nullOr types.int; 413 description = '' 414 The size of the key file. Use this if only the beginning of the 415 key file should be used as a key (often the case if a raw device 416 or partition is used as key file). If not specified, the whole 417 <literal>keyFile</literal> will be used decryption, instead of just 418 the first <literal>keyFileSize</literal> bytes. 419 ''; 420 }; 421 422 keyFileOffset = mkOption { 423 default = null; 424 example = 4096; 425 type = types.nullOr types.int; 426 description = '' 427 The offset of the key file. Use this in combination with 428 <literal>keyFileSize</literal> to use part of a file as key file 429 (often the case if a raw device or partition is used as a key file). 430 If not specified, the key begins at the first byte of 431 <literal>keyFile</literal>. 432 ''; 433 }; 434 435 # FIXME: get rid of this option. 436 preLVM = mkOption { 437 default = true; 438 type = types.bool; 439 description = "Whether the luksOpen will be attempted before LVM scan or after it."; 440 }; 441 442 allowDiscards = mkOption { 443 default = false; 444 type = types.bool; 445 description = '' 446 Whether to allow TRIM requests to the underlying device. This option 447 has security implications; please read the LUKS documentation before 448 activating it. 449 ''; 450 }; 451 452 fallbackToPassword = mkOption { 453 default = false; 454 type = types.bool; 455 description = '' 456 Whether to fallback to interactive passphrase prompt if the keyfile 457 cannot be found. This will prevent unattended boot should the keyfile 458 go missing. 459 ''; 460 }; 461 462 yubikey = mkOption { 463 default = null; 464 description = '' 465 The options to use for this LUKS device in Yubikey-PBA. 466 If null (the default), Yubikey-PBA will be disabled for this device. 467 ''; 468 469 type = with types; nullOr (submodule { 470 options = { 471 twoFactor = mkOption { 472 default = true; 473 type = types.bool; 474 description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false)."; 475 }; 476 477 slot = mkOption { 478 default = 2; 479 type = types.int; 480 description = "Which slot on the Yubikey to challenge."; 481 }; 482 483 saltLength = mkOption { 484 default = 16; 485 type = types.int; 486 description = "Length of the new salt in byte (64 is the effective maximum)."; 487 }; 488 489 keyLength = mkOption { 490 default = 64; 491 type = types.int; 492 description = "Length of the LUKS slot key derived with PBKDF2 in byte."; 493 }; 494 495 iterationStep = mkOption { 496 default = 0; 497 type = types.int; 498 description = "How much the iteration count for PBKDF2 is increased at each successful authentication."; 499 }; 500 501 gracePeriod = mkOption { 502 default = 10; 503 type = types.int; 504 description = "Time in seconds to wait for the Yubikey."; 505 }; 506 507 /* TODO: Add to the documentation of the current module: 508 509 Options related to the storing the salt. 510 */ 511 storage = { 512 device = mkOption { 513 default = "/dev/sda1"; 514 type = types.path; 515 description = '' 516 An unencrypted device that will temporarily be mounted in stage-1. 517 Must contain the current salt to create the challenge for this LUKS device. 518 ''; 519 }; 520 521 fsType = mkOption { 522 default = "vfat"; 523 type = types.str; 524 description = "The filesystem of the unencrypted device."; 525 }; 526 527 path = mkOption { 528 default = "/crypt-storage/default"; 529 type = types.str; 530 description = '' 531 Absolute path of the salt on the unencrypted device with 532 that device's root directory as "/". 533 ''; 534 }; 535 }; 536 }; 537 }); 538 }; 539 }; 540 })); 541 }; 542 543 boot.initrd.luks.yubikeySupport = mkOption { 544 default = false; 545 type = types.bool; 546 description = '' 547 Enables support for authenticating with a Yubikey on LUKS devices. 548 See the NixOS wiki for information on how to properly setup a LUKS device 549 and a Yubikey to work with this feature. 550 ''; 551 }; 552 }; 553 554 config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { 555 556 # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested 557 boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks 558 ["firewire_ohci" "firewire_core" "firewire_sbp2"]; 559 560 # Some modules that may be needed for mounting anything ciphered 561 boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ] 562 ++ luks.cryptoModules 563 # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged 564 # remove once 'modprobe --show-depends xts' shows ecb as a dependency 565 ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []); 566 567 # copy the cryptsetup binary and it's dependencies 568 boot.initrd.extraUtilsCommands = '' 569 copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup 570 copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass 571 sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass 572 573 ${optionalString luks.yubikeySupport '' 574 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp 575 copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo 576 copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl 577 578 cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto 579 strip -s pbkdf2-sha512 580 copy_bin_and_libs pbkdf2-sha512 581 582 mkdir -p $out/etc/ssl 583 cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl 584 585 cat > $out/bin/openssl-wrap <<EOF 586 #!$out/bin/sh 587 export OPENSSL_CONF=$out/etc/ssl/openssl.cnf 588 $out/bin/openssl "\$@" 589 EOF 590 chmod +x $out/bin/openssl-wrap 591 ''} 592 ''; 593 594 boot.initrd.extraUtilsCommandsTest = '' 595 $out/bin/cryptsetup --version 596 ${optionalString luks.yubikeySupport '' 597 $out/bin/ykchalresp -V 598 $out/bin/ykinfo -V 599 $out/bin/openssl-wrap version 600 ''} 601 ''; 602 603 boot.initrd.preFailCommands = postCommands; 604 boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands; 605 boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands; 606 607 environment.systemPackages = [ pkgs.cryptsetup ]; 608 }; 609}