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