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