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 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 isPreLVM = f: f.preLVM; 196 preLVM = filter isPreLVM luks.devices; 197 postLVM = filter (f: !(isPreLVM f)) luks.devices; 198 199in 200{ 201 202 options = { 203 204 boot.initrd.luks.mitigateDMAAttacks = mkOption { 205 type = types.bool; 206 default = true; 207 description = '' 208 Unless enabled, encryption keys can be easily recovered by an attacker with physical 209 access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port. 210 More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>. 211 212 This option blacklists FireWire drivers, but doesn't remove them. You can manually 213 load the drivers if you need to use a FireWire device, but don't forget to unload them! 214 ''; 215 }; 216 217 boot.initrd.luks.cryptoModules = mkOption { 218 type = types.listOf types.str; 219 default = 220 [ "aes" "aes_generic" "blowfish" "twofish" 221 "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" 222 (if pkgs.stdenv.system == "x86_64-linux" then "aes_x86_64" else "aes_i586") 223 ]; 224 description = '' 225 A list of cryptographic kernel modules needed to decrypt the root device(s). 226 The default includes all common modules. 227 ''; 228 }; 229 230 boot.initrd.luks.devices = mkOption { 231 default = [ ]; 232 example = literalExample ''[ { name = "luksroot"; device = "/dev/sda3"; preLVM = true; } ]''; 233 description = '' 234 The list of devices that should be decrypted using LUKS before trying to mount the 235 root partition. This works for both LVM-over-LUKS and LUKS-over-LVM setups. 236 237 The devices are decrypted to the device mapper names defined. 238 239 Make sure that initrd has the crypto modules needed for decryption. 240 ''; 241 242 type = types.listOf types.optionSet; 243 244 options = { 245 246 name = mkOption { 247 example = "luksroot"; 248 type = types.str; 249 description = "Named to be used for the generated device in /dev/mapper."; 250 }; 251 252 device = mkOption { 253 example = "/dev/sda2"; 254 type = types.str; 255 description = "Path of the underlying block device."; 256 }; 257 258 header = mkOption { 259 default = null; 260 example = "/root/header.img"; 261 type = types.nullOr types.str; 262 description = '' 263 The name of the file or block device that 264 should be used as header for the encrypted device. 265 ''; 266 }; 267 268 keyFile = mkOption { 269 default = null; 270 example = "/dev/sdb1"; 271 type = types.nullOr types.str; 272 description = '' 273 The name of the file (can be a raw device or a partition) that 274 should be used as the decryption key for the encrypted device. If 275 not specified, you will be prompted for a passphrase instead. 276 ''; 277 }; 278 279 keyFileSize = mkOption { 280 default = null; 281 example = 4096; 282 type = types.nullOr types.int; 283 description = '' 284 The size of the key file. Use this if only the beginning of the 285 key file should be used as a key (often the case if a raw device 286 or partition is used as key file). If not specified, the whole 287 <literal>keyFile</literal> will be used decryption, instead of just 288 the first <literal>keyFileSize</literal> bytes. 289 ''; 290 }; 291 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/openssl 440 441 cc -O3 -I${pkgs.openssl}/include -L${pkgs.openssl}/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}/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 = concatMapStrings openCommand preLVM; 467 boot.initrd.postDeviceCommands = concatMapStrings openCommand postLVM; 468 469 environment.systemPackages = [ pkgs.cryptsetup ]; 470 }; 471}