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