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