mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-23 15:33:13 +00:00
Replace the current Yubikey PBA implementation with the previous one.
Rationale: * The main reason for choosing to implement the PBA in accordance with the Yubico documentation was to prevent a MITM-USB-attack successfully recovering the new LUKS key. * However, a MITM-USB-attacker can read user id and password when they were entered for PBA, which allows him to recover the new challenge after the PBA is complete, with which he can challenge the Yubikey, decrypt the new AES blob and recover the LUKS key. * Additionally, since the Yubikey shared secret is stored in the same AES blob, after such an attack not only is the LUKS device compromised, the Yubikey is as well, since the shared secret has also been recovered by the attacker. * Furthermore, with this method an attacker could also bruteforce the AES blob, if he has access to the unencrypted device, which would again compromise the Yubikey, should he be successful. * Finally, with this method, once the LUKS key has been recovered once, the encryption is permanently broken, while with the previous system, the LUKS key itself it changed at every successful boot, so recovering it once will not necessarily result in a permanent breakage and will also not compromise the Yubikey itself (since its secret is never stored anywhere but on the Yubikey itself). Summary: The current implementation opens up up vulnerability to brute-forcing the AES blob, while retaining the current MITM-USB attack, additionally making the consequences of this attack permanent and extending it to the Yubikey itself.
This commit is contained in:
parent
e96bc485db
commit
8e74e1fded
@ -39,27 +39,11 @@ let
|
||||
${optionalString (luks.yubikeySupport && (yubikey != null)) ''
|
||||
|
||||
rbtohex() {
|
||||
od -An -vtx1 | tr -d ' \n'
|
||||
( od -An -vtx1 | tr -d ' \n' )
|
||||
}
|
||||
|
||||
hextorb() {
|
||||
tr '[:lower:]' '[:upper:]' | sed -e 's|\([0-9A-F]\{2\}\)|\\\\\\x\1|gI' | xargs printf
|
||||
}
|
||||
|
||||
take() {
|
||||
local c="$1"
|
||||
shift
|
||||
head -c $c "$@"
|
||||
}
|
||||
|
||||
drop() {
|
||||
local c="$1"
|
||||
shift
|
||||
if [ -e "$1" ]; then
|
||||
cat "$1" | ( dd of=/dev/null bs="$c" count=1 2>/dev/null ; dd 2>/dev/null )
|
||||
else
|
||||
( dd of=/dev/null bs="$c" count=1 2>/dev/null ; dd 2>/dev/null )
|
||||
fi
|
||||
( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI'| xargs printf )
|
||||
}
|
||||
|
||||
open_yubikey() {
|
||||
@ -70,83 +54,41 @@ let
|
||||
local uuid_r
|
||||
local k_user
|
||||
local challenge
|
||||
local k_blob
|
||||
local aes_blob_decrypted
|
||||
local checksum_correct
|
||||
local checksum
|
||||
local uuid_luks
|
||||
local user_record
|
||||
local opened
|
||||
|
||||
uuid_luks="$(cryptsetup luksUUID ${device} | take 36 | tr -d '-')"
|
||||
sleep 1
|
||||
|
||||
${optionalString (!yubikey.multiUser) ''
|
||||
user_record="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path})"
|
||||
uuid_r="$(echo -n $user_record | take 32)"
|
||||
''}
|
||||
uuid_r="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path})"
|
||||
|
||||
for try in $(seq 3); do
|
||||
|
||||
${optionalString yubikey.multiUser ''
|
||||
local user_id
|
||||
echo -n "Enter user id: "
|
||||
read -s user_id
|
||||
echo
|
||||
''}
|
||||
|
||||
${optionalString yubikey.twoFactor ''
|
||||
echo -n "Enter two-factor passphrase: "
|
||||
read -s k_user
|
||||
echo
|
||||
''}
|
||||
|
||||
${optionalString yubikey.multiUser ''
|
||||
local user_id_hash
|
||||
user_id_hash="$(echo -n $user_id | openssl-wrap dgst -binary -sha512 | rbtohex)"
|
||||
challenge="$(echo -n $k_user$uuid_r | openssl-wrap dgst -binary -sha512 | rbtohex)"
|
||||
|
||||
user_record="$(sed -n -e /^$user_id_hash[^$]*$/p ${yubikey.storage.mountPoint}${yubikey.storage.path} | tr -d '\n')"
|
||||
k_luks="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
|
||||
|
||||
if [ ! -z "$user_record" ]; then
|
||||
user_record="$(echo -n $user_record | drop 128)"
|
||||
uuid_r="$(echo -n $user_record | take 32)"
|
||||
''}
|
||||
echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
|
||||
|
||||
challenge="$(echo -n $k_user$uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | rbtohex)"
|
||||
|
||||
k_blob="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
|
||||
|
||||
aes_blob_decrypted="$(echo -n $user_record | drop 32 | hextorb | openssl-wrap enc -d -aes-256-ctr -K $k_blob -iv $uuid_r | rbtohex)"
|
||||
|
||||
checksum="$(echo -n $aes_blob_decrypted | drop 168)"
|
||||
if [ "$(echo -n $aes_blob_decrypted | hextorb | take 84 | openssl-wrap dgst -binary -sha512 | rbtohex)" == "$checksum" ]; then
|
||||
checksum_correct=1
|
||||
break
|
||||
else
|
||||
checksum_correct=0
|
||||
echo "Authentication failed!"
|
||||
fi
|
||||
|
||||
${optionalString yubikey.multiUser ''
|
||||
if [ $? == "0" ]; then
|
||||
opened=true
|
||||
break
|
||||
else
|
||||
checksum_correct=0
|
||||
opened=false
|
||||
echo "Authentication failed!"
|
||||
fi
|
||||
''}
|
||||
done
|
||||
|
||||
if [ "$checksum_correct" != "1" ]; then
|
||||
if [ "$opened" == false ]; then
|
||||
umount ${yubikey.storage.mountPoint}
|
||||
echo "Maximum authentication errors reached"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local k_yubi
|
||||
k_yubi="$(echo -n $aes_blob_decrypted | take 40)"
|
||||
|
||||
local k_luks
|
||||
k_luks="$(echo -n $aes_blob_decrypted | drop 40 | take 128)"
|
||||
|
||||
echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
|
||||
|
||||
update_failed=false
|
||||
|
||||
local new_uuid_r
|
||||
@ -161,24 +103,32 @@ let
|
||||
fi
|
||||
|
||||
if [ "$update_failed" == false ]; then
|
||||
new_uuid_r="$(echo -n $new_uuid_r | take 36 | tr -d '-')"
|
||||
new_uuid_r="$(echo -n $new_uuid_r | head -c 36 | tr -d '-')"
|
||||
|
||||
local new_challenge
|
||||
new_challenge="$(echo -n $k_user$new_uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | rbtohex)"
|
||||
new_challenge="$(echo -n $k_user$new_uuid_r | openssl-wrap dgst -binary -sha512 | rbtohex)"
|
||||
|
||||
local new_k_blob
|
||||
new_k_blob="$(echo -n $new_challenge | hextorb | openssl-wrap dgst -binary -sha1 -mac HMAC -macopt hexkey:$k_yubi | rbtohex)"
|
||||
local new_k_luks
|
||||
new_k_luks="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
|
||||
|
||||
local new_aes_blob
|
||||
new_aes_blob=$(echo -n "$k_yubi$k_luks$checksum" | hextorb | openssl-wrap enc -e -aes-256-ctr -K "$new_k_blob" -iv "$new_uuid_r" | rbtohex)
|
||||
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}
|
||||
|
||||
${optionalString yubikey.multiUser ''
|
||||
sed -i -e "s|^$user_id_hash$user_record|$user_id_hash$new_uuid_r$new_aes_blob|1"
|
||||
''}
|
||||
echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key
|
||||
echo -n "$k_luks" | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key
|
||||
|
||||
${optionalString (!yubikey.multiUser) ''
|
||||
echo -n "$new_uuid_r$new_aes_blob" > ${yubikey.storage.mountPoint}${yubikey.storage.path}
|
||||
''}
|
||||
if [ $? == "0" ]; then
|
||||
echo -n "$new_uuid_r" > ${yubikey.storage.mountPoint}${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}
|
||||
else
|
||||
echo "Warning: Could not obtain new UUID, current challenge persists!"
|
||||
fi
|
||||
@ -336,21 +286,21 @@ in
|
||||
description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false)";
|
||||
};
|
||||
|
||||
multiUser = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = "Whether to allow multiple users to authenticate with a Yubikey";
|
||||
};
|
||||
|
||||
slot = mkOption {
|
||||
default = 2;
|
||||
type = types.int;
|
||||
description = "Which slot on the Yubikey to challenge";
|
||||
};
|
||||
|
||||
ramfsMountPoint = mkOption {
|
||||
default = "/crypt-ramfs";
|
||||
type = types.string;
|
||||
description = "Path where the ramfs used to update the LUKS key will be mounted in stage-1";
|
||||
};
|
||||
|
||||
storage = mkOption {
|
||||
type = types.optionSet;
|
||||
description = "Options related to the authentication record";
|
||||
description = "Options related to the storing the random UUID";
|
||||
|
||||
options = {
|
||||
device = mkOption {
|
||||
@ -358,7 +308,7 @@ in
|
||||
type = types.path;
|
||||
description = ''
|
||||
An unencrypted device that will temporarily be mounted in stage-1.
|
||||
Must contain the authentication record for this LUKS device.
|
||||
Must contain the current random UUID to create the challenge for this LUKS device.
|
||||
'';
|
||||
};
|
||||
|
||||
@ -378,7 +328,7 @@ in
|
||||
default = "/crypt-storage/default";
|
||||
type = types.string;
|
||||
description = ''
|
||||
Absolute path of the authentication record on the unencrypted device with
|
||||
Absolute path of the random UUID on the unencrypted device with
|
||||
that device's root directory as "/".
|
||||
'';
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user