Naïm Favier 2021-05-26 15:43:38 +02:00 committed by GitHub
parent 11e02bd01d
commit a6788be01a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 63 additions and 44 deletions

View File

@ -140,24 +140,27 @@ let
umount /crypt-ramfs 2>/dev/null umount /crypt-ramfs 2>/dev/null
''; '';
openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, fallbackToPassword, preOpenCommands, postOpenCommands,... }: assert name' == name; openCommand = name: dev: assert name == dev.name;
let let
csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; csopen = "cryptsetup luksOpen ${dev.device} ${dev.name}"
cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; + optionalString dev.allowDiscards " --allow-discards"
+ optionalString dev.bypassWorkqueues " --perf-no_read_workqueue --perf-no_write_workqueue"
+ optionalString (dev.header != null) " --header=${dev.header}";
cschange = "cryptsetup luksChangeKey ${dev.device} ${optionalString (dev.header != null) "--header=${dev.header}"}";
in '' in ''
# Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g.
# if on a USB drive. # if on a USB drive.
wait_target "device" ${device} || die "${device} is unavailable" wait_target "device" ${dev.device} || die "${dev.device} is unavailable"
${optionalString (header != null) '' ${optionalString (dev.header != null) ''
wait_target "header" ${header} || die "${header} is unavailable" wait_target "header" ${dev.header} || die "${dev.header} is unavailable"
''} ''}
do_open_passphrase() { do_open_passphrase() {
local passphrase local passphrase
while true; do while true; do
echo -n "Passphrase for ${device}: " echo -n "Passphrase for ${dev.device}: "
passphrase= passphrase=
while true; do while true; do
if [ -e /crypt-ramfs/passphrase ]; then if [ -e /crypt-ramfs/passphrase ]; then
@ -166,7 +169,7 @@ let
break break
else else
# ask cryptsetup-askpass # ask cryptsetup-askpass
echo -n "${device}" > /crypt-ramfs/device echo -n "${dev.device}" > /crypt-ramfs/device
# and try reading it from /dev/console with a timeout # and try reading it from /dev/console with a timeout
IFS= read -t 1 -r passphrase IFS= read -t 1 -r passphrase
@ -182,7 +185,7 @@ let
fi fi
fi fi
done done
echo -n "Verifying passphrase for ${device}..." echo -n "Verifying passphrase for ${dev.device}..."
echo -n "$passphrase" | ${csopen} --key-file=- echo -n "$passphrase" | ${csopen} --key-file=-
if [ $? == 0 ]; then if [ $? == 0 ]; then
echo " - success" echo " - success"
@ -202,13 +205,13 @@ let
# LUKS # LUKS
open_normally() { open_normally() {
${if (keyFile != null) then '' ${if (dev.keyFile != null) then ''
if wait_target "key file" ${keyFile}; then if wait_target "key file" ${dev.keyFile}; then
${csopen} --key-file=${keyFile} \ ${csopen} --key-file=${dev.keyFile} \
${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \ ${optionalString (dev.keyFileSize != null) "--keyfile-size=${toString dev.keyFileSize}"} \
${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"} ${optionalString (dev.keyFileOffset != null) "--keyfile-offset=${toString dev.keyFileOffset}"}
else else
${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable" ${if dev.fallbackToPassword then "echo" else "die"} "${dev.keyFile} is unavailable"
echo " - failing back to interactive password prompt" echo " - failing back to interactive password prompt"
do_open_passphrase do_open_passphrase
fi fi
@ -217,7 +220,7 @@ let
''} ''}
} }
${optionalString (luks.yubikeySupport && (yubikey != null)) '' ${optionalString (luks.yubikeySupport && (dev.yubikey != null)) ''
# YubiKey # YubiKey
rbtohex() { rbtohex() {
( od -An -vtx1 | tr -d ' \n' ) ( od -An -vtx1 | tr -d ' \n' )
@ -243,16 +246,16 @@ let
local new_response local new_response
local new_k_luks local new_k_luks
mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \ mount -t ${dev.yubikey.storage.fsType} ${dev.yubikey.storage.device} /crypt-storage || \
die "Failed to mount YubiKey salt storage device" die "Failed to mount YubiKey salt storage device"
salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')" salt="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 1p | tr -d '\n')"
iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')" iterations="$(cat /crypt-storage${dev.yubikey.storage.path} | sed -n 2p | tr -d '\n')"
challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" response="$(ykchalresp -${toString dev.yubikey.slot} -x $challenge 2>/dev/null)"
for try in $(seq 3); do for try in $(seq 3); do
${optionalString yubikey.twoFactor '' ${optionalString dev.yubikey.twoFactor ''
echo -n "Enter two-factor passphrase: " echo -n "Enter two-factor passphrase: "
k_user= k_user=
while true; do while true; do
@ -278,9 +281,9 @@ let
''} ''}
if [ ! -z "$k_user" ]; then if [ ! -z "$k_user" ]; then
k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)"
else else
k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $iterations $response | rbtohex)"
fi fi
echo -n "$k_luks" | hextorb | ${csopen} --key-file=- echo -n "$k_luks" | hextorb | ${csopen} --key-file=-
@ -302,7 +305,7 @@ let
[ "$opened" == false ] && die "Maximum authentication errors reached" [ "$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)..." 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 for i in $(seq ${toString dev.yubikey.saltLength}); do
byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
new_salt="$new_salt$byte"; new_salt="$new_salt$byte";
echo -n . echo -n .
@ -310,25 +313,25 @@ let
echo "ok" echo "ok"
new_iterations="$iterations" new_iterations="$iterations"
${optionalString (yubikey.iterationStep > 0) '' ${optionalString (dev.yubikey.iterationStep > 0) ''
new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))" new_iterations="$(($new_iterations + ${toString dev.yubikey.iterationStep}))"
''} ''}
new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)" new_response="$(ykchalresp -${toString dev.yubikey.slot} -x $new_challenge 2>/dev/null)"
if [ ! -z "$k_user" ]; then if [ ! -z "$k_user" ]; then
new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
else else
new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" new_k_luks="$(echo | pbkdf2-sha512 ${toString dev.yubikey.keyLength} $new_iterations $new_response | rbtohex)"
fi fi
echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key
echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key
if [ $? == 0 ]; then if [ $? == 0 ]; then
echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path} echo -ne "$new_salt\n$new_iterations" > /crypt-storage${dev.yubikey.storage.path}
else else
echo "Warning: Could not update LUKS key, current challenge persists!" echo "Warning: Could not update LUKS key, current challenge persists!"
fi fi
@ -338,7 +341,7 @@ let
} }
open_with_hardware() { open_with_hardware() {
if wait_yubikey ${toString yubikey.gracePeriod}; then if wait_yubikey ${toString dev.yubikey.gracePeriod}; then
do_open_yubikey do_open_yubikey
else else
echo "No YubiKey found, falling back to non-YubiKey open procedure" echo "No YubiKey found, falling back to non-YubiKey open procedure"
@ -347,7 +350,7 @@ let
} }
''} ''}
${optionalString (luks.gpgSupport && (gpgCard != null)) '' ${optionalString (luks.gpgSupport && (dev.gpgCard != null)) ''
do_open_gpg_card() { do_open_gpg_card() {
# Make all of these local to this function # Make all of these local to this function
@ -355,12 +358,12 @@ let
local pin local pin
local opened local opened
gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null gpg --import /gpg-keys/${dev.device}/pubkey.asc > /dev/null 2> /dev/null
gpg --card-status > /dev/null 2> /dev/null gpg --card-status > /dev/null 2> /dev/null
for try in $(seq 3); do for try in $(seq 3); do
echo -n "PIN for GPG Card associated with device ${device}: " echo -n "PIN for GPG Card associated with device ${dev.device}: "
pin= pin=
while true; do while true; do
if [ -e /crypt-ramfs/passphrase ]; then if [ -e /crypt-ramfs/passphrase ]; then
@ -382,8 +385,8 @@ let
fi fi
fi fi
done done
echo -n "Verifying passphrase for ${device}..." echo -n "Verifying passphrase for ${dev.device}..."
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 echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${dev.device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null
if [ $? == 0 ]; then if [ $? == 0 ]; then
echo " - success" echo " - success"
${if luks.reusePassphrases then '' ${if luks.reusePassphrases then ''
@ -403,7 +406,7 @@ let
} }
open_with_hardware() { open_with_hardware() {
if wait_gpgcard ${toString gpgCard.gracePeriod}; then if wait_gpgcard ${toString dev.gpgCard.gracePeriod}; then
do_open_gpg_card do_open_gpg_card
else else
echo "No GPG Card found, falling back to normal open procedure" echo "No GPG Card found, falling back to normal open procedure"
@ -412,15 +415,15 @@ let
} }
''} ''}
${optionalString (luks.fido2Support && (fido2.credential != null)) '' ${optionalString (luks.fido2Support && (dev.fido2.credential != null)) ''
open_with_hardware() { open_with_hardware() {
local passsphrase local passsphrase
${if fido2.passwordLess then '' ${if dev.fido2.passwordLess then ''
export passphrase="" export passphrase=""
'' else '' '' else ''
read -rsp "FIDO2 salt for ${device}: " passphrase read -rsp "FIDO2 salt for ${dev.device}: " passphrase
echo echo
''} ''}
${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") ''
@ -428,7 +431,7 @@ let
echo "Please move your mouse to create needed randomness." echo "Please move your mouse to create needed randomness."
''} ''}
echo "Waiting for your FIDO2 device..." echo "Waiting for your FIDO2 device..."
fido2luks open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase fido2luks open ${dev.device} ${dev.name} ${dev.fido2.credential} --await-dev ${toString dev.fido2.gracePeriod} --salt string:$passphrase
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "No FIDO2 key found, falling back to normal open procedure" echo "No FIDO2 key found, falling back to normal open procedure"
open_normally open_normally
@ -437,16 +440,16 @@ let
''} ''}
# commands to run right before we mount our device # commands to run right before we mount our device
${preOpenCommands} ${dev.preOpenCommands}
${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) then '' ${if (luks.yubikeySupport && (dev.yubikey != null)) || (luks.gpgSupport && (dev.gpgCard != null)) || (luks.fido2Support && (dev.fido2.credential != null)) then ''
open_with_hardware open_with_hardware
'' else '' '' else ''
open_normally open_normally
''} ''}
# commands to run right after we mounted our device # commands to run right after we mounted our device
${postOpenCommands} ${dev.postOpenCommands}
''; '';
askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' askPass = pkgs.writeScriptBin "cryptsetup-askpass" ''
@ -621,6 +624,17 @@ in
''; '';
}; };
bypassWorkqueues = mkOption {
default = false;
type = types.bool;
description = ''
Whether to bypass dm-crypt's internal read and write workqueues.
Enabling this should improve performance on SSDs; see
<link xlink:href="https://wiki.archlinux.org/index.php/Dm-crypt/Specialties#Disable_workqueue_for_increased_solid_state_drive_(SSD)_performance">here</link>
for more information. Needs Linux 5.9 or later.
'';
};
fallbackToPassword = mkOption { fallbackToPassword = mkOption {
default = false; default = false;
type = types.bool; type = types.bool;
@ -833,6 +847,11 @@ in
{ assertion = !(luks.fido2Support && luks.yubikeySupport); { assertion = !(luks.fido2Support && luks.yubikeySupport);
message = "FIDO2 and YubiKey may not be used at the same time."; message = "FIDO2 and YubiKey may not be used at the same time.";
} }
{ assertion = any (dev: dev.bypassWorkqueues) (attrValues luks.devices)
-> versionAtLeast kernelPackages.kernel.version "5.9";
message = "boot.initrd.luks.devices.<name>.bypassWorkqueues is not supported for kernels older than 5.9";
}
]; ];
# actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested