diff options
Diffstat (limited to 'modules/luksroot.nix')
-rw-r--r-- | modules/luksroot.nix | 1075 |
1 files changed, 1075 insertions, 0 deletions
diff --git a/modules/luksroot.nix b/modules/luksroot.nix new file mode 100644 index 00000000..abaee692 --- /dev/null +++ b/modules/luksroot.nix | |||
@@ -0,0 +1,1075 @@ | |||
1 | { config, lib, pkgs, ... }: | ||
2 | |||
3 | with lib; | ||
4 | |||
5 | let | ||
6 | luks = config.boot.initrd.luks; | ||
7 | kernelPackages = config.boot.kernelPackages; | ||
8 | |||
9 | commonFunctions = '' | ||
10 | die() { | ||
11 | echo "$@" >&2 | ||
12 | exit 1 | ||
13 | } | ||
14 | |||
15 | dev_exist() { | ||
16 | local target="$1" | ||
17 | if [ -e $target ]; then | ||
18 | return 0 | ||
19 | else | ||
20 | local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g') | ||
21 | blkid --uuid $uuid >/dev/null | ||
22 | return $? | ||
23 | fi | ||
24 | } | ||
25 | |||
26 | wait_target() { | ||
27 | local name="$1" | ||
28 | local target="$2" | ||
29 | local secs="''${3:-10}" | ||
30 | local desc="''${4:-$name $target to appear}" | ||
31 | |||
32 | if ! dev_exist $target; then | ||
33 | echo -n "Waiting $secs seconds for $desc..." | ||
34 | local success=false; | ||
35 | for try in $(seq $secs); do | ||
36 | echo -n "." | ||
37 | sleep 1 | ||
38 | if dev_exist $target; then | ||
39 | success=true | ||
40 | break | ||
41 | fi | ||
42 | done | ||
43 | if [ $success == true ]; then | ||
44 | echo " - success"; | ||
45 | return 0 | ||
46 | else | ||
47 | echo " - failure"; | ||
48 | return 1 | ||
49 | fi | ||
50 | fi | ||
51 | return 0 | ||
52 | } | ||
53 | |||
54 | wait_yubikey() { | ||
55 | local secs="''${1:-10}" | ||
56 | |||
57 | ykinfo -v 1>/dev/null 2>&1 | ||
58 | if [ $? != 0 ]; then | ||
59 | echo -n "Waiting $secs seconds for YubiKey to appear..." | ||
60 | local success=false | ||
61 | for try in $(seq $secs); do | ||
62 | echo -n . | ||
63 | sleep 1 | ||
64 | ykinfo -v 1>/dev/null 2>&1 | ||
65 | if [ $? == 0 ]; then | ||
66 | success=true | ||
67 | break | ||
68 | fi | ||
69 | done | ||
70 | if [ $success == true ]; then | ||
71 | echo " - success"; | ||
72 | return 0 | ||
73 | else | ||
74 | echo " - failure"; | ||
75 | return 1 | ||
76 | fi | ||
77 | fi | ||
78 | return 0 | ||
79 | } | ||
80 | |||
81 | wait_gpgcard() { | ||
82 | local secs="''${1:-10}" | ||
83 | |||
84 | gpg --card-status > /dev/null 2> /dev/null | ||
85 | if [ $? != 0 ]; then | ||
86 | echo -n "Waiting $secs seconds for GPG Card to appear" | ||
87 | local success=false | ||
88 | for try in $(seq $secs); do | ||
89 | echo -n . | ||
90 | sleep 1 | ||
91 | gpg --card-status > /dev/null 2> /dev/null | ||
92 | if [ $? == 0 ]; then | ||
93 | success=true | ||
94 | break | ||
95 | fi | ||
96 | done | ||
97 | if [ $success == true ]; then | ||
98 | echo " - success"; | ||
99 | return 0 | ||
100 | else | ||
101 | echo " - failure"; | ||
102 | return 1 | ||
103 | fi | ||
104 | fi | ||
105 | return 0 | ||
106 | } | ||
107 | ''; | ||
108 | |||
109 | preCommands = '' | ||
110 | # A place to store crypto things | ||
111 | |||
112 | # A ramfs is used here to ensure that the file used to update | ||
113 | # the key slot with cryptsetup will never get swapped out. | ||
114 | # Warning: Do NOT replace with tmpfs! | ||
115 | mkdir -p /crypt-ramfs | ||
116 | mount -t ramfs none /crypt-ramfs | ||
117 | |||
118 | # Cryptsetup locking directory | ||
119 | mkdir -p /run/cryptsetup | ||
120 | |||
121 | # For YubiKey salt storage | ||
122 | mkdir -p /crypt-storage | ||
123 | |||
124 | ${optionalString luks.gpgSupport '' | ||
125 | export GPG_TTY=$(tty) | ||
126 | export GNUPGHOME=/crypt-ramfs/.gnupg | ||
127 | |||
128 | gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null | ||
129 | ''} | ||
130 | |||
131 | # Disable all input echo for the whole stage. We could use read -s | ||
132 | # instead but that would ocasionally leak characters between read | ||
133 | # invocations. | ||
134 | stty -echo | ||
135 | ''; | ||
136 | |||
137 | postCommands = '' | ||
138 | stty echo | ||
139 | umount /crypt-storage 2>/dev/null | ||
140 | umount /crypt-ramfs 2>/dev/null | ||
141 | ''; | ||
142 | |||
143 | openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, clevis, dmi, fallbackToPassword, preOpenCommands, postOpenCommands, ... }: assert name' == name; | ||
144 | let | ||
145 | csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; | ||
146 | cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; | ||
147 | in '' | ||
148 | # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. | ||
149 | # if on a USB drive. | ||
150 | wait_target "device" ${device} || die "${device} is unavailable" | ||
151 | |||
152 | ${optionalString (header != null) '' | ||
153 | wait_target "header" ${header} || die "${header} is unavailable" | ||
154 | ''} | ||
155 | |||
156 | do_open_passphrase() { | ||
157 | local passphrase | ||
158 | |||
159 | while true; do | ||
160 | echo -n "Passphrase for ${device}: " | ||
161 | passphrase= | ||
162 | while true; do | ||
163 | if [ -e /crypt-ramfs/passphrase ]; then | ||
164 | echo "reused" | ||
165 | passphrase=$(cat /crypt-ramfs/passphrase) | ||
166 | break | ||
167 | else | ||
168 | # ask cryptsetup-askpass | ||
169 | echo -n "${device}" > /crypt-ramfs/device | ||
170 | |||
171 | # and try reading it from /dev/console with a timeout | ||
172 | IFS= read -t 1 -r passphrase | ||
173 | if [ -n "$passphrase" ]; then | ||
174 | ${if luks.reusePassphrases then '' | ||
175 | # remember it for the next device | ||
176 | echo -n "$passphrase" > /crypt-ramfs/passphrase | ||
177 | '' else '' | ||
178 | # Don't save it to ramfs. We are very paranoid | ||
179 | ''} | ||
180 | echo | ||
181 | break | ||
182 | fi | ||
183 | fi | ||
184 | done | ||
185 | echo -n "Verifying passphrase for ${device}..." | ||
186 | echo -n "$passphrase" | ${csopen} --key-file=- | ||
187 | if [ $? == 0 ]; then | ||
188 | echo " - success" | ||
189 | ${if luks.reusePassphrases then '' | ||
190 | # we don't rm here because we might reuse it for the next device | ||
191 | '' else '' | ||
192 | rm -f /crypt-ramfs/passphrase | ||
193 | ''} | ||
194 | break | ||
195 | else | ||
196 | echo " - failure" | ||
197 | # ask for a different one | ||
198 | rm -f /crypt-ramfs/passphrase | ||
199 | fi | ||
200 | done | ||
201 | } | ||
202 | |||
203 | # LUKS | ||
204 | open_normally() { | ||
205 | ${if (keyFile != null) then '' | ||
206 | if wait_target "key file" ${keyFile}; then | ||
207 | ${csopen} --key-file=${keyFile} \ | ||
208 | ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \ | ||
209 | ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"} | ||
210 | else | ||
211 | ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable" | ||
212 | echo " - failing back to interactive password prompt" | ||
213 | do_open_passphrase | ||
214 | fi | ||
215 | '' else '' | ||
216 | do_open_passphrase | ||
217 | ''} | ||
218 | } | ||
219 | |||
220 | ${optionalString (luks.yubikeySupport && (yubikey != null)) '' | ||
221 | # YubiKey | ||
222 | rbtohex() { | ||
223 | ( od -An -vtx1 | tr -d ' \n' ) | ||
224 | } | ||
225 | |||
226 | hextorb() { | ||
227 | ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) | ||
228 | } | ||
229 | |||
230 | do_open_yubikey() { | ||
231 | # Make all of these local to this function | ||
232 | # to prevent their values being leaked | ||
233 | local salt | ||
234 | local iterations | ||
235 | local k_user | ||
236 | local challenge | ||
237 | local response | ||
238 | local k_luks | ||
239 | local opened | ||
240 | local new_salt | ||
241 | local new_iterations | ||
242 | local new_challenge | ||
243 | local new_response | ||
244 | local new_k_luks | ||
245 | |||
246 | mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \ | ||
247 | die "Failed to mount YubiKey salt storage device" | ||
248 | |||
249 | salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')" | ||
250 | iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')" | ||
251 | challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" | ||
252 | response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" | ||
253 | |||
254 | for try in $(seq 3); do | ||
255 | ${optionalString yubikey.twoFactor '' | ||
256 | echo -n "Enter two-factor passphrase: " | ||
257 | k_user= | ||
258 | while true; do | ||
259 | if [ -e /crypt-ramfs/passphrase ]; then | ||
260 | echo "reused" | ||
261 | k_user=$(cat /crypt-ramfs/passphrase) | ||
262 | break | ||
263 | else | ||
264 | # Try reading it from /dev/console with a timeout | ||
265 | IFS= read -t 1 -r k_user | ||
266 | if [ -n "$k_user" ]; then | ||
267 | ${if luks.reusePassphrases then '' | ||
268 | # Remember it for the next device | ||
269 | echo -n "$k_user" > /crypt-ramfs/passphrase | ||
270 | '' else '' | ||
271 | # Don't save it to ramfs. We are very paranoid | ||
272 | ''} | ||
273 | echo | ||
274 | break | ||
275 | fi | ||
276 | fi | ||
277 | done | ||
278 | ''} | ||
279 | |||
280 | if [ ! -z "$k_user" ]; then | ||
281 | k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" | ||
282 | else | ||
283 | k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" | ||
284 | fi | ||
285 | |||
286 | echo -n "$k_luks" | hextorb | ${csopen} --key-file=- | ||
287 | |||
288 | if [ $? == 0 ]; then | ||
289 | opened=true | ||
290 | ${if luks.reusePassphrases then '' | ||
291 | # We don't rm here because we might reuse it for the next device | ||
292 | '' else '' | ||
293 | rm -f /crypt-ramfs/passphrase | ||
294 | ''} | ||
295 | break | ||
296 | else | ||
297 | opened=false | ||
298 | echo "Authentication failed!" | ||
299 | fi | ||
300 | done | ||
301 | |||
302 | [ "$opened" == false ] && die "Maximum authentication errors reached" | ||
303 | |||
304 | echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." | ||
305 | for i in $(seq ${toString yubikey.saltLength}); do | ||
306 | byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; | ||
307 | new_salt="$new_salt$byte"; | ||
308 | echo -n . | ||
309 | done; | ||
310 | echo "ok" | ||
311 | |||
312 | new_iterations="$iterations" | ||
313 | ${optionalString (yubikey.iterationStep > 0) '' | ||
314 | new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))" | ||
315 | ''} | ||
316 | |||
317 | new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" | ||
318 | |||
319 | new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)" | ||
320 | |||
321 | if [ ! -z "$k_user" ]; then | ||
322 | new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" | ||
323 | else | ||
324 | new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" | ||
325 | fi | ||
326 | |||
327 | echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key | ||
328 | echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key | ||
329 | |||
330 | if [ $? == 0 ]; then | ||
331 | echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path} | ||
332 | else | ||
333 | echo "Warning: Could not update LUKS key, current challenge persists!" | ||
334 | fi | ||
335 | |||
336 | rm -f /crypt-ramfs/new_key | ||
337 | umount /crypt-storage | ||
338 | } | ||
339 | |||
340 | open_with_hardware() { | ||
341 | if wait_yubikey ${toString yubikey.gracePeriod}; then | ||
342 | do_open_yubikey | ||
343 | else | ||
344 | echo "No YubiKey found, falling back to non-YubiKey open procedure" | ||
345 | open_normally | ||
346 | fi | ||
347 | } | ||
348 | ''} | ||
349 | |||
350 | ${optionalString (luks.gpgSupport && (gpgCard != null)) '' | ||
351 | |||
352 | do_open_gpg_card() { | ||
353 | # Make all of these local to this function | ||
354 | # to prevent their values being leaked | ||
355 | local pin | ||
356 | local opened | ||
357 | |||
358 | gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null | ||
359 | |||
360 | gpg --card-status > /dev/null 2> /dev/null | ||
361 | |||
362 | for try in $(seq 3); do | ||
363 | echo -n "PIN for GPG Card associated with device ${device}: " | ||
364 | pin= | ||
365 | while true; do | ||
366 | if [ -e /crypt-ramfs/passphrase ]; then | ||
367 | echo "reused" | ||
368 | pin=$(cat /crypt-ramfs/passphrase) | ||
369 | break | ||
370 | else | ||
371 | # and try reading it from /dev/console with a timeout | ||
372 | IFS= read -t 1 -r pin | ||
373 | if [ -n "$pin" ]; then | ||
374 | ${if luks.reusePassphrases then '' | ||
375 | # remember it for the next device | ||
376 | echo -n "$pin" > /crypt-ramfs/passphrase | ||
377 | '' else '' | ||
378 | # Don't save it to ramfs. We are very paranoid | ||
379 | ''} | ||
380 | echo | ||
381 | break | ||
382 | fi | ||
383 | fi | ||
384 | done | ||
385 | echo -n "Verifying passphrase for ${device}..." | ||
386 | echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null | ||
387 | if [ $? == 0 ]; then | ||
388 | echo " - success" | ||
389 | ${if luks.reusePassphrases then '' | ||
390 | # we don't rm here because we might reuse it for the next device | ||
391 | '' else '' | ||
392 | rm -f /crypt-ramfs/passphrase | ||
393 | ''} | ||
394 | break | ||
395 | else | ||
396 | echo " - failure" | ||
397 | # ask for a different one | ||
398 | rm -f /crypt-ramfs/passphrase | ||
399 | fi | ||
400 | done | ||
401 | |||
402 | [ "$opened" == false ] && die "Maximum authentication errors reached" | ||
403 | } | ||
404 | |||
405 | open_with_hardware() { | ||
406 | if wait_gpgcard ${toString gpgCard.gracePeriod}; then | ||
407 | do_open_gpg_card | ||
408 | else | ||
409 | echo "No GPG Card found, falling back to normal open procedure" | ||
410 | open_normally | ||
411 | fi | ||
412 | } | ||
413 | ''} | ||
414 | |||
415 | ${optionalString (luks.fido2Support && (fido2.credential != null)) '' | ||
416 | |||
417 | open_with_hardware() { | ||
418 | local passsphrase | ||
419 | |||
420 | ${if fido2.passwordLess then '' | ||
421 | export passphrase="" | ||
422 | '' else '' | ||
423 | read -rsp "FIDO2 salt for ${device}: " passphrase | ||
424 | echo | ||
425 | ''} | ||
426 | ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' | ||
427 | echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest." | ||
428 | echo "Please move your mouse to create needed randomness." | ||
429 | ''} | ||
430 | echo "Waiting for your FIDO2 device..." | ||
431 | fido2luks open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase | ||
432 | if [ $? -ne 0 ]; then | ||
433 | echo "No FIDO2 key found, falling back to normal open procedure" | ||
434 | open_normally | ||
435 | fi | ||
436 | } | ||
437 | ''} | ||
438 | |||
439 | ${optionalString (luks.clevisSupport && clevis) '' | ||
440 | |||
441 | open_with_hardware() { | ||
442 | mkdir -p /crypt-ramfs/clevis | ||
443 | |||
444 | TMPDIR=/crypt-ramfs/clevis clevis luks unlock -d ${device} -n ${name} | ||
445 | |||
446 | if [ $? -ne 0 ]; then | ||
447 | echo "Unlocking with clevis failed, falling back to normal open procedure" | ||
448 | open_normally | ||
449 | fi | ||
450 | } | ||
451 | |||
452 | ''} | ||
453 | |||
454 | ${optionalString (luks.dmiSupport && dmi) '' | ||
455 | |||
456 | open_with_hardware() { | ||
457 | dmidecode -s system-uuid > /crypt-ramfs/passphrase | ||
458 | |||
459 | ${csopen} --key-file=- < /crypt-ramfs/passphrase > /dev/null 2> /dev/null | ||
460 | |||
461 | if [ $? -ne 0 ]; then | ||
462 | echo "Unlocking with system-uuid failed, falling back to normal open procedure" | ||
463 | rm -f /crypt-ramfs/passphrase | ||
464 | open_normally | ||
465 | ${optionalString (!luks.reusePassphrases) '' | ||
466 | else | ||
467 | rm -f /crypt-ramfs/passphrase | ||
468 | ''} | ||
469 | fi | ||
470 | } | ||
471 | |||
472 | ''} | ||
473 | |||
474 | # commands to run right before we mount our device | ||
475 | ${preOpenCommands} | ||
476 | |||
477 | ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) || (luks.clevisSupport && clevis) || (luks.dmiSupport && dmi) then '' | ||
478 | open_with_hardware | ||
479 | '' else '' | ||
480 | open_normally | ||
481 | ''} | ||
482 | |||
483 | # commands to run right after we mounted our device | ||
484 | ${postOpenCommands} | ||
485 | ''; | ||
486 | |||
487 | askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' | ||
488 | #!/bin/sh | ||
489 | |||
490 | ${commonFunctions} | ||
491 | |||
492 | while true; do | ||
493 | wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" | ||
494 | device=$(cat /crypt-ramfs/device) | ||
495 | |||
496 | echo -n "Passphrase for $device: " | ||
497 | IFS= read -rs passphrase | ||
498 | echo | ||
499 | |||
500 | rm /crypt-ramfs/device | ||
501 | echo -n "$passphrase" > /crypt-ramfs/passphrase | ||
502 | done | ||
503 | ''; | ||
504 | |||
505 | preLVM = filterAttrs (n: v: v.preLVM) luks.devices; | ||
506 | postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; | ||
507 | |||
508 | in | ||
509 | { | ||
510 | disabledModules = [ "system/boot/luksroot.nix" ]; | ||
511 | |||
512 | imports = [ | ||
513 | (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") | ||
514 | ]; | ||
515 | |||
516 | options = { | ||
517 | |||
518 | boot.initrd.luks.mitigateDMAAttacks = mkOption { | ||
519 | type = types.bool; | ||
520 | default = true; | ||
521 | description = '' | ||
522 | Unless enabled, encryption keys can be easily recovered by an attacker with physical | ||
523 | access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port. | ||
524 | More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>. | ||
525 | |||
526 | This option blacklists FireWire drivers, but doesn't remove them. You can manually | ||
527 | load the drivers if you need to use a FireWire device, but don't forget to unload them! | ||
528 | ''; | ||
529 | }; | ||
530 | |||
531 | boot.initrd.luks.cryptoModules = mkOption { | ||
532 | type = types.listOf types.str; | ||
533 | default = | ||
534 | [ "aes" "aes_generic" "blowfish" "twofish" | ||
535 | "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" | ||
536 | "af_alg" "algif_skcipher" | ||
537 | ]; | ||
538 | description = '' | ||
539 | A list of cryptographic kernel modules needed to decrypt the root device(s). | ||
540 | The default includes all common modules. | ||
541 | ''; | ||
542 | }; | ||
543 | |||
544 | boot.initrd.luks.forceLuksSupportInInitrd = mkOption { | ||
545 | type = types.bool; | ||
546 | default = false; | ||
547 | internal = true; | ||
548 | description = '' | ||
549 | Whether to configure luks support in the initrd, when no luks | ||
550 | devices are configured. | ||
551 | ''; | ||
552 | }; | ||
553 | |||
554 | boot.initrd.luks.reusePassphrases = mkOption { | ||
555 | type = types.bool; | ||
556 | default = true; | ||
557 | description = '' | ||
558 | When opening a new LUKS device try reusing last successful | ||
559 | passphrase. | ||
560 | |||
561 | Useful for mounting a number of devices that use the same | ||
562 | passphrase without retyping it several times. | ||
563 | |||
564 | Such setup can be useful if you use <command>cryptsetup | ||
565 | luksSuspend</command>. Different LUKS devices will still have | ||
566 | different master keys even when using the same passphrase. | ||
567 | ''; | ||
568 | }; | ||
569 | |||
570 | boot.initrd.luks.devices = mkOption { | ||
571 | default = { }; | ||
572 | example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; }; | ||
573 | description = '' | ||
574 | The encrypted disk that should be opened before the root | ||
575 | filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM | ||
576 | setups are supported. The unencrypted devices can be accessed as | ||
577 | <filename>/dev/mapper/<replaceable>name</replaceable></filename>. | ||
578 | ''; | ||
579 | |||
580 | type = with types; attrsOf (submodule ( | ||
581 | { name, ... }: { options = { | ||
582 | |||
583 | name = mkOption { | ||
584 | visible = false; | ||
585 | default = name; | ||
586 | example = "luksroot"; | ||
587 | type = types.str; | ||
588 | description = "Name of the unencrypted device in <filename>/dev/mapper</filename>."; | ||
589 | }; | ||
590 | |||
591 | device = mkOption { | ||
592 | example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; | ||
593 | type = types.str; | ||
594 | description = "Path of the underlying encrypted block device."; | ||
595 | }; | ||
596 | |||
597 | header = mkOption { | ||
598 | default = null; | ||
599 | example = "/root/header.img"; | ||
600 | type = types.nullOr types.str; | ||
601 | description = '' | ||
602 | The name of the file or block device that | ||
603 | should be used as header for the encrypted device. | ||
604 | ''; | ||
605 | }; | ||
606 | |||
607 | keyFile = mkOption { | ||
608 | default = null; | ||
609 | example = "/dev/sdb1"; | ||
610 | type = types.nullOr types.str; | ||
611 | description = '' | ||
612 | The name of the file (can be a raw device or a partition) that | ||
613 | should be used as the decryption key for the encrypted device. If | ||
614 | not specified, you will be prompted for a passphrase instead. | ||
615 | ''; | ||
616 | }; | ||
617 | |||
618 | keyFileSize = mkOption { | ||
619 | default = null; | ||
620 | example = 4096; | ||
621 | type = types.nullOr types.int; | ||
622 | description = '' | ||
623 | The size of the key file. Use this if only the beginning of the | ||
624 | key file should be used as a key (often the case if a raw device | ||
625 | or partition is used as key file). If not specified, the whole | ||
626 | <literal>keyFile</literal> will be used decryption, instead of just | ||
627 | the first <literal>keyFileSize</literal> bytes. | ||
628 | ''; | ||
629 | }; | ||
630 | |||
631 | keyFileOffset = mkOption { | ||
632 | default = null; | ||
633 | example = 4096; | ||
634 | type = types.nullOr types.int; | ||
635 | description = '' | ||
636 | The offset of the key file. Use this in combination with | ||
637 | <literal>keyFileSize</literal> to use part of a file as key file | ||
638 | (often the case if a raw device or partition is used as a key file). | ||
639 | If not specified, the key begins at the first byte of | ||
640 | <literal>keyFile</literal>. | ||
641 | ''; | ||
642 | }; | ||
643 | |||
644 | # FIXME: get rid of this option. | ||
645 | preLVM = mkOption { | ||
646 | default = true; | ||
647 | type = types.bool; | ||
648 | description = "Whether the luksOpen will be attempted before LVM scan or after it."; | ||
649 | }; | ||
650 | |||
651 | allowDiscards = mkOption { | ||
652 | default = false; | ||
653 | type = types.bool; | ||
654 | description = '' | ||
655 | Whether to allow TRIM requests to the underlying device. This option | ||
656 | has security implications; please read the LUKS documentation before | ||
657 | activating it. | ||
658 | ''; | ||
659 | }; | ||
660 | |||
661 | fallbackToPassword = mkOption { | ||
662 | default = false; | ||
663 | type = types.bool; | ||
664 | description = '' | ||
665 | Whether to fallback to interactive passphrase prompt if the keyfile | ||
666 | cannot be found. This will prevent unattended boot should the keyfile | ||
667 | go missing. | ||
668 | ''; | ||
669 | }; | ||
670 | |||
671 | gpgCard = mkOption { | ||
672 | default = null; | ||
673 | description = '' | ||
674 | The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard. | ||
675 | If null (the default), GPG-Smartcard will be disabled for this device. | ||
676 | ''; | ||
677 | |||
678 | type = with types; nullOr (submodule { | ||
679 | options = { | ||
680 | gracePeriod = mkOption { | ||
681 | default = 10; | ||
682 | type = types.int; | ||
683 | description = "Time in seconds to wait for the GPG Smartcard."; | ||
684 | }; | ||
685 | |||
686 | encryptedPass = mkOption { | ||
687 | default = ""; | ||
688 | type = types.path; | ||
689 | description = "Path to the GPG encrypted passphrase."; | ||
690 | }; | ||
691 | |||
692 | publicKey = mkOption { | ||
693 | default = ""; | ||
694 | type = types.path; | ||
695 | description = "Path to the Public Key."; | ||
696 | }; | ||
697 | }; | ||
698 | }); | ||
699 | }; | ||
700 | |||
701 | fido2 = { | ||
702 | credential = mkOption { | ||
703 | default = null; | ||
704 | example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; | ||
705 | type = types.nullOr types.str; | ||
706 | description = "The FIDO2 credential ID."; | ||
707 | }; | ||
708 | |||
709 | gracePeriod = mkOption { | ||
710 | default = 10; | ||
711 | type = types.int; | ||
712 | description = "Time in seconds to wait for the FIDO2 key."; | ||
713 | }; | ||
714 | |||
715 | passwordLess = mkOption { | ||
716 | default = false; | ||
717 | type = types.bool; | ||
718 | description = '' | ||
719 | Defines whatever to use an empty string as a default salt. | ||
720 | |||
721 | Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>. | ||
722 | ''; | ||
723 | }; | ||
724 | }; | ||
725 | |||
726 | yubikey = mkOption { | ||
727 | default = null; | ||
728 | description = '' | ||
729 | The options to use for this LUKS device in YubiKey-PBA. | ||
730 | If null (the default), YubiKey-PBA will be disabled for this device. | ||
731 | ''; | ||
732 | |||
733 | type = with types; nullOr (submodule { | ||
734 | options = { | ||
735 | twoFactor = mkOption { | ||
736 | default = true; | ||
737 | type = types.bool; | ||
738 | description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false)."; | ||
739 | }; | ||
740 | |||
741 | slot = mkOption { | ||
742 | default = 2; | ||
743 | type = types.int; | ||
744 | description = "Which slot on the YubiKey to challenge."; | ||
745 | }; | ||
746 | |||
747 | saltLength = mkOption { | ||
748 | default = 16; | ||
749 | type = types.int; | ||
750 | description = "Length of the new salt in byte (64 is the effective maximum)."; | ||
751 | }; | ||
752 | |||
753 | keyLength = mkOption { | ||
754 | default = 64; | ||
755 | type = types.int; | ||
756 | description = "Length of the LUKS slot key derived with PBKDF2 in byte."; | ||
757 | }; | ||
758 | |||
759 | iterationStep = mkOption { | ||
760 | default = 0; | ||
761 | type = types.int; | ||
762 | description = "How much the iteration count for PBKDF2 is increased at each successful authentication."; | ||
763 | }; | ||
764 | |||
765 | gracePeriod = mkOption { | ||
766 | default = 10; | ||
767 | type = types.int; | ||
768 | description = "Time in seconds to wait for the YubiKey."; | ||
769 | }; | ||
770 | |||
771 | /* TODO: Add to the documentation of the current module: | ||
772 | |||
773 | Options related to the storing the salt. | ||
774 | */ | ||
775 | storage = { | ||
776 | device = mkOption { | ||
777 | default = "/dev/sda1"; | ||
778 | type = types.path; | ||
779 | description = '' | ||
780 | An unencrypted device that will temporarily be mounted in stage-1. | ||
781 | Must contain the current salt to create the challenge for this LUKS device. | ||
782 | ''; | ||
783 | }; | ||
784 | |||
785 | fsType = mkOption { | ||
786 | default = "vfat"; | ||
787 | type = types.str; | ||
788 | description = "The filesystem of the unencrypted device."; | ||
789 | }; | ||
790 | |||
791 | path = mkOption { | ||
792 | default = "/crypt-storage/default"; | ||
793 | type = types.str; | ||
794 | description = '' | ||
795 | Absolute path of the salt on the unencrypted device with | ||
796 | that device's root directory as "/". | ||
797 | ''; | ||
798 | }; | ||
799 | }; | ||
800 | }; | ||
801 | }); | ||
802 | }; | ||
803 | |||
804 | clevis = mkOption { | ||
805 | type = types.bool; | ||
806 | default = false; | ||
807 | description = '' | ||
808 | Unlock device via clevis (e.g. with a tpm) | ||
809 | ''; | ||
810 | }; | ||
811 | |||
812 | dmi = mkOption { | ||
813 | type = types.bool; | ||
814 | default = false; | ||
815 | description = '' | ||
816 | Unlock device via system-uuid (via dmidecode) | ||
817 | ''; | ||
818 | }; | ||
819 | |||
820 | preOpenCommands = mkOption { | ||
821 | type = types.lines; | ||
822 | default = ""; | ||
823 | example = '' | ||
824 | mkdir -p /tmp/persistent | ||
825 | mount -t zfs rpool/safe/persistent /tmp/persistent | ||
826 | ''; | ||
827 | description = '' | ||
828 | Commands that should be run right before we try to mount our LUKS device. | ||
829 | This can be useful, if the keys needed to open the drive is on another partion. | ||
830 | ''; | ||
831 | }; | ||
832 | |||
833 | postOpenCommands = mkOption { | ||
834 | type = types.lines; | ||
835 | default = ""; | ||
836 | example = '' | ||
837 | umount /tmp/persistent | ||
838 | ''; | ||
839 | description = '' | ||
840 | Commands that should be run right after we have mounted our LUKS device. | ||
841 | ''; | ||
842 | }; | ||
843 | }; | ||
844 | })); | ||
845 | }; | ||
846 | |||
847 | boot.initrd.luks.gpgSupport = mkOption { | ||
848 | default = false; | ||
849 | type = types.bool; | ||
850 | description = '' | ||
851 | Enables support for authenticating with a GPG encrypted password. | ||
852 | ''; | ||
853 | }; | ||
854 | |||
855 | boot.initrd.luks.yubikeySupport = mkOption { | ||
856 | default = false; | ||
857 | type = types.bool; | ||
858 | description = '' | ||
859 | Enables support for authenticating with a YubiKey on LUKS devices. | ||
860 | See the NixOS wiki for information on how to properly setup a LUKS device | ||
861 | and a YubiKey to work with this feature. | ||
862 | ''; | ||
863 | }; | ||
864 | |||
865 | boot.initrd.luks.fido2Support = mkOption { | ||
866 | default = false; | ||
867 | type = types.bool; | ||
868 | description = '' | ||
869 | Enables support for authenticating with FIDO2 devices. | ||
870 | ''; | ||
871 | }; | ||
872 | |||
873 | boot.initrd.luks.clevisSupport = mkOption { | ||
874 | default = false; | ||
875 | type = types.bool; | ||
876 | description = '' | ||
877 | Enables support for unlocking luks volumes via clevis (e.g. with a tpm) | ||
878 | ''; | ||
879 | }; | ||
880 | |||
881 | boot.initrd.luks.dmiSupport = mkOption { | ||
882 | default = false; | ||
883 | type = types.bool; | ||
884 | description = '' | ||
885 | Enables support for unlocking luks volumes via system-uuid (via dmidecode) | ||
886 | ''; | ||
887 | }; | ||
888 | |||
889 | }; | ||
890 | |||
891 | config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { | ||
892 | |||
893 | assertions = | ||
894 | [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); | ||
895 | message = "YubiKey and GPG Card may not be used at the same time."; | ||
896 | } | ||
897 | |||
898 | { assertion = !(luks.gpgSupport && luks.fido2Support); | ||
899 | message = "FIDO2 and GPG Card may not be used at the same time."; | ||
900 | } | ||
901 | |||
902 | { assertion = !(luks.gpgSupport && luks.clevisSupport); | ||
903 | message = "Clevis and GPG Card may not be used at the same time."; | ||
904 | } | ||
905 | |||
906 | { assertion = !(luks.gpgSupport && luks.dmiSupport); | ||
907 | message = "DMI and GPG Card may not be used at the same time."; | ||
908 | } | ||
909 | |||
910 | { assertion = !(luks.fido2Support && luks.yubikeySupport); | ||
911 | message = "FIDO2 and YubiKey may not be used at the same time."; | ||
912 | } | ||
913 | |||
914 | { assertion = !(luks.fido2Support && luks.clevisSupport); | ||
915 | message = "FIDO2 and Clevis may not be used at the same time."; | ||
916 | } | ||
917 | |||
918 | { assertion = !(luks.fido2Support && luks.dmiSupport); | ||
919 | message = "FIDO2 and DMI may not be used at the same time."; | ||
920 | } | ||
921 | |||
922 | { assertion = !(luks.yubikeySupport && luks.clevisSupport); | ||
923 | message = "Clevis and YubiKey may not be used at the same time."; | ||
924 | } | ||
925 | |||
926 | { assertion = !(luks.yubikeySupport && luks.dmiSupport); | ||
927 | message = "DMI and YubiKey may not be used at the same time."; | ||
928 | } | ||
929 | |||
930 | ]; | ||
931 | |||
932 | # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested | ||
933 | boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks | ||
934 | ["firewire_ohci" "firewire_core" "firewire_sbp2"]; | ||
935 | |||
936 | # Some modules that may be needed for mounting anything ciphered | ||
937 | boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ] | ||
938 | ++ luks.cryptoModules | ||
939 | # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged | ||
940 | # remove once 'modprobe --show-depends xts' shows ecb as a dependency | ||
941 | ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []); | ||
942 | |||
943 | # copy the cryptsetup binary and it's dependencies | ||
944 | boot.initrd.extraUtilsCommands = | ||
945 | let | ||
946 | extraUtils = config.system.build.extraUtils; | ||
947 | |||
948 | ipkgs = pkgs.appendOverlays [ | ||
949 | (final: prev: { | ||
950 | tpm2-tss = prev.tpm2-tss.overrideAttrs (oldAttrs: { | ||
951 | doCheck = false; | ||
952 | patches = []; | ||
953 | postPatch = '' | ||
954 | patchShebangs script | ||
955 | ''; | ||
956 | configureFlags = []; | ||
957 | }); | ||
958 | }) | ||
959 | ]; | ||
960 | in '' | ||
961 | copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup | ||
962 | copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass | ||
963 | sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass | ||
964 | |||
965 | ${optionalString luks.yubikeySupport '' | ||
966 | copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp | ||
967 | copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo | ||
968 | copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl | ||
969 | |||
970 | cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto | ||
971 | strip -s pbkdf2-sha512 | ||
972 | copy_bin_and_libs pbkdf2-sha512 | ||
973 | |||
974 | mkdir -p $out/etc/ssl | ||
975 | cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl | ||
976 | |||
977 | cat > $out/bin/openssl-wrap <<EOF | ||
978 | #!$out/bin/sh | ||
979 | export OPENSSL_CONF=$out/etc/ssl/openssl.cnf | ||
980 | $out/bin/openssl "\$@" | ||
981 | EOF | ||
982 | chmod +x $out/bin/openssl-wrap | ||
983 | ''} | ||
984 | |||
985 | ${optionalString luks.fido2Support '' | ||
986 | copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks | ||
987 | ''} | ||
988 | |||
989 | |||
990 | ${optionalString luks.gpgSupport '' | ||
991 | copy_bin_and_libs ${pkgs.gnupg}/bin/gpg | ||
992 | copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent | ||
993 | copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon | ||
994 | |||
995 | ${concatMapStringsSep "\n" (x: | ||
996 | if x.gpgCard != null then | ||
997 | '' | ||
998 | mkdir -p $out/secrets/gpg-keys/${x.device} | ||
999 | cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg | ||
1000 | cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc | ||
1001 | '' | ||
1002 | else "" | ||
1003 | ) (attrValues luks.devices) | ||
1004 | } | ||
1005 | ''} | ||
1006 | |||
1007 | ${optionalString luks.clevisSupport '' | ||
1008 | for bin in ${ipkgs.tpm2-tools}/bin/* ${ipkgs.jose}/bin/* ${ipkgs.libpwquality}/bin/*; do | ||
1009 | if [ -L $bin ]; then | ||
1010 | cp -v $bin $out/bin | ||
1011 | else | ||
1012 | copy_bin_and_libs $bin | ||
1013 | fi | ||
1014 | done | ||
1015 | |||
1016 | copy_bin_and_libs ${ipkgs.bash}/bin/bash | ||
1017 | for bin in ${ipkgs.clevis}/bin/* ${ipkgs.clevis}/bin/.*; do | ||
1018 | [ -f $bin -o -L $bin ] || continue | ||
1019 | |||
1020 | substitute $bin $out/bin/$(basename $bin) \ | ||
1021 | --replace ${ipkgs.bash}/bin $out/bin \ | ||
1022 | --replace ${ipkgs.clevis}/bin $out/bin \ | ||
1023 | --replace ${ipkgs.tpm2-tools}/bin $out/bin \ | ||
1024 | --replace ${ipkgs.jose}/bin $out/bin \ | ||
1025 | --replace ${ipkgs.libpwquality}/bin $out/bin \ | ||
1026 | --replace ${ipkgs.coreutils}/bin $out/bin | ||
1027 | |||
1028 | [ -x $bin ] && chmod +x $out/bin/$(basename $bin) | ||
1029 | done | ||
1030 | |||
1031 | for lib in ${ipkgs.tpm2-tss}/lib/*.so*; do | ||
1032 | if [ -f $lib ]; then | ||
1033 | patchelf --output $out/lib/$(basename $lib) $lib \ | ||
1034 | --set-rpath $out/lib | ||
1035 | else | ||
1036 | cp -pdv $lib $out/lib | ||
1037 | fi | ||
1038 | done | ||
1039 | ''} | ||
1040 | |||
1041 | ${optionalString luks.dmiSupport '' | ||
1042 | copy_bin_and_libs ${pkgs.dmidecode}/bin/dmidecode | ||
1043 | ''} | ||
1044 | ''; | ||
1045 | |||
1046 | boot.initrd.extraUtilsCommandsTest = '' | ||
1047 | $out/bin/cryptsetup --version | ||
1048 | ${optionalString luks.yubikeySupport '' | ||
1049 | $out/bin/ykchalresp -V | ||
1050 | $out/bin/ykinfo -V | ||
1051 | $out/bin/openssl-wrap version | ||
1052 | ''} | ||
1053 | ${optionalString luks.gpgSupport '' | ||
1054 | $out/bin/gpg --version | ||
1055 | $out/bin/gpg-agent --version | ||
1056 | $out/bin/scdaemon --version | ||
1057 | ''} | ||
1058 | ${optionalString luks.fido2Support '' | ||
1059 | $out/bin/fido2luks --version | ||
1060 | ''} | ||
1061 | ${optionalString luks.clevisSupport '' | ||
1062 | $out/bin/jose alg | ||
1063 | ''} | ||
1064 | ${optionalString luks.dmiSupport '' | ||
1065 | $out/bin/dmidecode --version | ||
1066 | ''} | ||
1067 | ''; | ||
1068 | |||
1069 | boot.initrd.preFailCommands = postCommands; | ||
1070 | boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands; | ||
1071 | boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands; | ||
1072 | |||
1073 | environment.systemPackages = [ pkgs.cryptsetup ]; | ||
1074 | }; | ||
1075 | } | ||