nixos: initrd/luks: change passphrases handling

Also reuse common cryptsetup invocation subexpressions.

- Passphrase reading is done via the shell now, not by cryptsetup.
  This way the same passphrase can be reused between cryptsetup
  invocations, which this module now tries to do by default (can be
  disabled).
- Number of retries is now infinity, it makes no sense to make users
  reboot when they fail to type in their passphrase.
This commit is contained in:
Jan Malakhovski 2017-09-15 21:48:51 +00:00
parent 12e6907f33
commit a9d69a74d6

View File

@ -67,7 +67,25 @@ let
}
'';
openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name; ''
preCommands = ''
# A place to store crypto things
# A ramfs is used here to ensure that the file used to update
# the key slot with cryptsetup will never get swapped out.
# Warning: Do NOT replace with tmpfs!
mkdir -p /crypt-ramfs
mount -t ramfs none /crypt-ramfs
'';
postCommands = ''
umount /crypt-ramfs 2>/dev/null
'';
openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name;
let
csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}";
cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}";
in ''
# Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
# if on a USB drive.
wait_target "device" ${device} || die "${device} is unavailable"
@ -76,31 +94,72 @@ let
wait_target "header" ${header} || die "${header} is unavailable"
''}
${optionalString (keyFile != null) ''
wait_target "key file" ${keyFile} || die "${keyFile} is unavailable"
''}
do_open_passphrase() {
local passphrase
open_normally() {
echo luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
${optionalString (header != null) "--header=${header}"} \
> /.luksopen_args
${optionalString (keyFile != null) ''
${optionalString fallbackToPassword "if [ -e ${keyFile} ]; then"}
echo " --key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}" \
"${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}" \
>> /.luksopen_args
${optionalString fallbackToPassword ''
else
echo "keyfile ${keyFile} not found -- fallback to interactive unlocking"
fi
''}
''}
cryptsetup-askpass
rm /.luksopen_args
while true; do
echo -n "Passphrase for ${device}: "
passphrase=
while true; do
if [ -e /crypt-ramfs/passphrase ]; then
echo "reused"
passphrase=$(cat /crypt-ramfs/passphrase)
break
else
# ask cryptsetup-askpass
echo -n "${device}" > /crypt-ramfs/device
# and try reading it from /dev/console
IFS= read -t 1 -rs passphrase
if [ -n "$passphrase" ]; then
${if luks.reusePassphrases then ''
# remember it for the next device
echo -n "$passphrase" > /crypt-ramfs/passphrase
'' else ''
# Don't save it to ramfs. We are very paranoid
''}
echo
break
fi
fi
done
echo -n "Verifiying passphrase for ${device}..."
echo -n "$passphrase" | ${csopen} --key-file=-
if [ $? == 0 ]; then
echo " - success"
${if luks.reusePassphrases then ''
# we don't rm here because we might reuse it for the next device
'' else ''
rm -f /crypt-ramfs/passphrase
''}
break
else
echo " - failure"
# ask for a different one
rm -f /crypt-ramfs/passphrase
fi
done
}
${optionalString (luks.yubikeySupport && (yubikey != null)) ''
# LUKS
open_normally() {
${if (keyFile != null) then ''
if wait_target "key file" ${keyFile}; then
${csopen} --key-file=${keyFile} \
${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \
${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"}
else
${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable"
echo " - failing back to interactive password prompt"
do_open_passphrase
fi
'' else ''
do_open_passphrase
''}
}
${if luks.yubikeySupport && (yubikey != null) then ''
# Yubikey
rbtohex() {
( od -An -vtx1 | tr -d ' \n' )
}
@ -109,7 +168,7 @@ let
( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf )
}
open_yubikey() {
do_open_yubikey() {
# Make all of these local to this function
# to prevent their values being leaked
local salt
@ -146,7 +205,7 @@ let
k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
fi
echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
if [ $? == 0 ]; then
opened=true
@ -192,7 +251,7 @@ let
mount -t ramfs none ${yubikey.ramfsMountPoint}
echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key
echo -n "$k_luks" | hextorb | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key
echo -n "$k_luks" | hextorb | ${cschange} --key-file=- ${yubikey.ramfsMountPoint}/new_key
if [ $? == 0 ]; then
echo -ne "$new_salt\n$new_iterations" > ${yubikey.storage.mountPoint}${yubikey.storage.path}
@ -207,20 +266,39 @@ let
umount ${yubikey.storage.mountPoint}
}
if wait_yubikey ${toString yubikey.gracePeriod}; then
open_yubikey
else
echo "no yubikey found, falling back to non-yubikey open procedure"
open_normally
fi
''}
open_yubikey() {
if wait_yubikey ${toString yubikey.gracePeriod}; then
do_open_yubikey
else
echo "No yubikey found, falling back to non-yubikey open procedure"
open_normally
fi
}
# open luksRoot and scan for logical volumes
${optionalString ((!luks.yubikeySupport) || (yubikey == null)) ''
open_yubikey
'' else ''
open_normally
''}
'';
askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
#!/bin/sh
${commonFunctions}
while true; do
wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now"
device=$(cat /crypt-ramfs/device)
echo -n "Passphrase for $device: "
IFS= read -rs passphrase
echo
rm /crypt-ramfs/device
echo -n "$passphrase" > /crypt-ramfs/passphrase
done
'';
preLVM = filterAttrs (n: v: v.preLVM) luks.devices;
postLVM = filterAttrs (n: v: !v.preLVM) luks.devices;
@ -266,6 +344,22 @@ in
'';
};
boot.initrd.luks.reusePassphrases = mkOption {
type = types.bool;
default = true;
description = ''
When opening a new LUKS device try reusing last successful
passphrase.
Useful for mounting a number of devices that use the same
passphrase without retyping it several times.
Such setup can be useful if you use <command>cryptsetup
luksSuspend</command>. Different LUKS devices will still have
different master keys even when using the same passphrase.
'';
};
boot.initrd.luks.devices = mkOption {
default = { };
example = { "luksroot".device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; };
@ -487,18 +581,8 @@ in
# copy the cryptsetup binary and it's dependencies
boot.initrd.extraUtilsCommands = ''
copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup
cat > $out/bin/cryptsetup-askpass <<EOF
#!$out/bin/sh -e
if [ -e /.luksopen_args ]; then
cryptsetup \$(cat /.luksopen_args)
killall -q cryptsetup
else
echo "Passphrase is not requested now"
exit 1
fi
EOF
chmod +x $out/bin/cryptsetup-askpass
copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass
sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass
${optionalString luks.yubikeySupport ''
copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp
@ -530,8 +614,9 @@ in
''}
'';
boot.initrd.preLVMCommands = commonFunctions + concatStrings (mapAttrsToList openCommand preLVM);
boot.initrd.postDeviceCommands = commonFunctions + concatStrings (mapAttrsToList openCommand postLVM);
boot.initrd.preFailCommands = postCommands;
boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands;
boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands;
environment.systemPackages = [ pkgs.cryptsetup ];
};