diff --git a/nixos/doc/manual/release-notes/rl-1809.xml b/nixos/doc/manual/release-notes/rl-1809.xml
index a70fb1aedbb86b..d527984f5ef142 100644
--- a/nixos/doc/manual/release-notes/rl-1809.xml
+++ b/nixos/doc/manual/release-notes/rl-1809.xml
@@ -190,6 +190,16 @@ $ nix-instantiate -E '(import <nixpkgsunstable> {}).gitFull'
which indicates that the nix output hash will be used as tag.
+
+
+ Options
+ boot.initrd.luks.devices.name.yubikey.ramfsMountPoint
+ boot.initrd.luks.devices.name.yubikey.storage.mountPoint
+ were removed. luksroot.nix module never supported more than one YubiKey at
+ a time anyway, hence those options never had any effect. You should be able to remove them
+ from your config without any issues.
+
+
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index 401c3cc69919f9..27c1f891f485a2 100644
--- a/nixos/modules/system/boot/luksroot.nix
+++ b/nixos/modules/system/boot/luksroot.nix
@@ -5,62 +5,171 @@ with lib;
let
luks = config.boot.initrd.luks;
- openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, fallbackToPassword, ... }: assert name' == name; ''
+ commonFunctions = ''
+ die() {
+ echo "$@" >&2
+ exit 1
+ }
- # Wait for a target (e.g. device, keyFile, header, ...) to appear.
wait_target() {
local name="$1"
local target="$2"
+ local secs="''${3:-10}"
+ local desc="''${4:-$name $target to appear}"
if [ ! -e $target ]; then
- echo -n "Waiting 10 seconds for $name $target to appear"
+ echo -n "Waiting $secs seconds for $desc..."
local success=false;
- for try in $(seq 10); do
+ for try in $(seq $secs); do
echo -n "."
sleep 1
- if [ -e $target ]; then success=true break; fi
+ if [ -e $target ]; then
+ success=true
+ break
+ fi
done
- if [ $success = true ]; then
+ if [ $success == true ]; then
echo " - success";
+ return 0
else
echo " - failure";
+ return 1
fi
fi
+ return 0
+ }
+
+ wait_yubikey() {
+ local secs="''${1:-10}"
+
+ ykinfo -v 1>/dev/null 2>&1
+ if [ $? != 0 ]; then
+ echo -n "Waiting $secs seconds for Yubikey to appear..."
+ local success=false
+ for try in $(seq $secs); do
+ echo -n .
+ sleep 1
+ ykinfo -v 1>/dev/null 2>&1
+ if [ $? == 0 ]; then
+ success=true
+ break
+ fi
+ done
+ if [ $success == true ]; then
+ echo " - success";
+ return 0
+ else
+ echo " - failure";
+ return 1
+ fi
+ fi
+ return 0
}
+ '';
+
+ 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
+
+ # For Yubikey salt storage
+ mkdir -p /crypt-storage
+ # Disable all input echo for the whole stage. We could use read -s
+ # instead but that would ocasionally leak characters between read
+ # invocations.
+ stty -echo
+ '';
+
+ postCommands = ''
+ stty echo
+ umount /crypt-storage 2>/dev/null
+ 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}
-
- ${optionalString (keyFile != null) ''
- wait_target "key file" ${keyFile}
- ''}
+ wait_target "device" ${device} || die "${device} is unavailable"
${optionalString (header != null) ''
- wait_target "header" ${header}
+ wait_target "header" ${header} || die "${header} is unavailable"
''}
+ do_open_passphrase() {
+ local passphrase
+
+ 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 with a timeout
+ IFS= read -t 1 -r 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
+ }
+
+ # LUKS
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 ''
+ ${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
- echo "keyfile ${keyFile} not found -- fallback to interactive unlocking"
+ ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable"
+ echo " - failing back to interactive password prompt"
+ do_open_passphrase
fi
+ '' else ''
+ do_open_passphrase
''}
- ''}
- cryptsetup-askpass
- rm /.luksopen_args
}
- ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
-
+ ${if luks.yubikeySupport && (yubikey != null) then ''
+ # Yubikey
rbtohex() {
( od -An -vtx1 | tr -d ' \n' )
}
@@ -69,8 +178,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
@@ -86,19 +194,18 @@ let
local new_response
local new_k_luks
- mkdir -p ${yubikey.storage.mountPoint}
- mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint}
+ mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \
+ die "Failed to mount Yubikey salt storage device"
- salt="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
- iterations="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
+ salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
+ iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
for try in $(seq 3); do
-
${optionalString yubikey.twoFactor ''
echo -n "Enter two-factor passphrase: "
- read -s k_user
+ read -r k_user
echo
''}
@@ -108,9 +215,9 @@ 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
+ if [ $? == 0 ]; then
opened=true
break
else
@@ -119,11 +226,7 @@ let
fi
done
- if [ "$opened" == false ]; then
- umount ${yubikey.storage.mountPoint}
- echo "Maximum authentication errors reached"
- exit 1
- fi
+ [ "$opened" == false ] && die "Maximum authentication errors reached"
echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
for i in $(seq ${toString yubikey.saltLength}); do
@@ -148,67 +251,50 @@ let
new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
fi
- mkdir -p ${yubikey.ramfsMountPoint}
- # 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!
- 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 "$new_k_luks" | hextorb > /crypt-ramfs/new_key
+ echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
- if [ $? == "0" ]; then
- echo -ne "$new_salt\n$new_iterations" > ${yubikey.storage.mountPoint}${yubikey.storage.path}
+ if [ $? == 0 ]; then
+ echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path}
else
echo "Warning: Could not update LUKS key, current challenge persists!"
fi
- rm -f ${yubikey.ramfsMountPoint}/new_key
- umount ${yubikey.ramfsMountPoint}
- rm -rf ${yubikey.ramfsMountPoint}
+ rm -f /crypt-ramfs/new_key
+ umount /crypt-storage
+ }
- umount ${yubikey.storage.mountPoint}
+ 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
}
- ${optionalString (yubikey.gracePeriod > 0) ''
- echo -n "Waiting ${toString yubikey.gracePeriod} seconds as grace..."
- for i in $(seq ${toString yubikey.gracePeriod}); do
- sleep 1
- echo -n .
- done
- echo "ok"
+ open_yubikey
+ '' else ''
+ open_normally
''}
+ '';
- yubikey_missing=true
- ykinfo -v 1>/dev/null 2>&1
- if [ $? != "0" ]; then
- echo -n "waiting 10 seconds for yubikey to appear..."
- for try in $(seq 10); do
- sleep 1
- ykinfo -v 1>/dev/null 2>&1
- if [ $? == "0" ]; then
- yubikey_missing=false
- break
- fi
- echo -n .
- done
- echo "ok"
- else
- yubikey_missing=false
- fi
-
- if [ "$yubikey_missing" == true ]; then
- echo "no yubikey found, falling back to non-yubikey open procedure"
- open_normally
- else
- open_yubikey
- fi
- ''}
+ askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
+ #!/bin/sh
- # open luksRoot and scan for logical volumes
- ${optionalString ((!luks.yubikeySupport) || (yubikey == null)) ''
- open_normally
- ''}
+ ${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;
@@ -256,6 +342,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 cryptsetup
+ luksSuspend. 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"; };
@@ -397,15 +499,9 @@ in
};
gracePeriod = mkOption {
- default = 2;
+ default = 10;
type = types.int;
- description = "Time in seconds to wait before attempting to find the Yubikey.";
- };
-
- ramfsMountPoint = mkOption {
- default = "/crypt-ramfs";
- type = types.str;
- description = "Path where the ramfs used to update the LUKS key will be mounted during early boot.";
+ description = "Time in seconds to wait for the Yubikey.";
};
/* TODO: Add to the documentation of the current module:
@@ -428,12 +524,6 @@ in
description = "The filesystem of the unencrypted device.";
};
- mountPoint = mkOption {
- default = "/crypt-storage";
- type = types.str;
- description = "Path where the unencrypted device will be mounted during early boot.";
- };
-
path = mkOption {
default = "/crypt-storage/default";
type = types.str;
@@ -446,8 +536,8 @@ in
};
});
};
-
- }; }));
+ };
+ }));
};
boot.initrd.luks.yubikeySupport = mkOption {
@@ -477,18 +567,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 <