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}