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:
Moritz Maxeiner 2014-02-03 22:50:17 +01:00
parent e96bc485db
commit 8e74e1fded
1 changed files with 42 additions and 92 deletions

View File

@ -39,27 +39,11 @@ let
${optionalString (luks.yubikeySupport && (yubikey != null)) '' ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
rbtohex() { rbtohex() {
od -An -vtx1 | tr -d ' \n' ( od -An -vtx1 | tr -d ' \n' )
} }
hextorb() { hextorb() {
tr '[:lower:]' '[:upper:]' | sed -e 's|\([0-9A-F]\{2\}\)|\\\\\\x\1|gI' | xargs printf ( 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
} }
open_yubikey() { open_yubikey() {
@ -70,83 +54,41 @@ let
local uuid_r local uuid_r
local k_user local k_user
local challenge local challenge
local k_blob local opened
local aes_blob_decrypted
local checksum_correct
local checksum
local uuid_luks
local user_record
uuid_luks="$(cryptsetup luksUUID ${device} | take 36 | tr -d '-')" sleep 1
${optionalString (!yubikey.multiUser) '' uuid_r="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path})"
user_record="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path})"
uuid_r="$(echo -n $user_record | take 32)"
''}
for try in $(seq 3); do 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 '' ${optionalString yubikey.twoFactor ''
echo -n "Enter two-factor passphrase: " echo -n "Enter two-factor passphrase: "
read -s k_user read -s k_user
echo echo
''} ''}
${optionalString yubikey.multiUser '' challenge="$(echo -n $k_user$uuid_r | openssl-wrap dgst -binary -sha512 | rbtohex)"
local user_id_hash
user_id_hash="$(echo -n $user_id | 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 echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
user_record="$(echo -n $user_record | drop 128)"
uuid_r="$(echo -n $user_record | take 32)"
''}
challenge="$(echo -n $k_user$uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | rbtohex)" if [ $? == "0" ]; then
opened=true
k_blob="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" break
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 ''
else else
checksum_correct=0 opened=false
echo "Authentication failed!" echo "Authentication failed!"
fi fi
''}
done done
if [ "$checksum_correct" != "1" ]; then if [ "$opened" == false ]; then
umount ${yubikey.storage.mountPoint} umount ${yubikey.storage.mountPoint}
echo "Maximum authentication errors reached" echo "Maximum authentication errors reached"
exit 1 exit 1
fi 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 update_failed=false
local new_uuid_r local new_uuid_r
@ -161,24 +103,32 @@ let
fi fi
if [ "$update_failed" == false ]; then 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 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 local new_k_luks
new_k_blob="$(echo -n $new_challenge | hextorb | openssl-wrap dgst -binary -sha1 -mac HMAC -macopt hexkey:$k_yubi | rbtohex)" new_k_luks="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
local new_aes_blob mkdir -p ${yubikey.ramfsMountPoint}
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) # 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 '' echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key
sed -i -e "s|^$user_id_hash$user_record|$user_id_hash$new_uuid_r$new_aes_blob|1" echo -n "$k_luks" | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key
''}
${optionalString (!yubikey.multiUser) '' if [ $? == "0" ]; then
echo -n "$new_uuid_r$new_aes_blob" > ${yubikey.storage.mountPoint}${yubikey.storage.path} 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 else
echo "Warning: Could not obtain new UUID, current challenge persists!" echo "Warning: Could not obtain new UUID, current challenge persists!"
fi fi
@ -336,21 +286,21 @@ in
description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false)"; 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 { slot = mkOption {
default = 2; default = 2;
type = types.int; type = types.int;
description = "Which slot on the Yubikey to challenge"; 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 { storage = mkOption {
type = types.optionSet; type = types.optionSet;
description = "Options related to the authentication record"; description = "Options related to the storing the random UUID";
options = { options = {
device = mkOption { device = mkOption {
@ -358,7 +308,7 @@ in
type = types.path; type = types.path;
description = '' description = ''
An unencrypted device that will temporarily be mounted in stage-1. 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"; default = "/crypt-storage/default";
type = types.string; type = types.string;
description = '' 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 "/". that device's root directory as "/".
''; '';
}; };