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