Rewrite as a pre-boot authentication module (mostly) comforming to the design specification of

'YubiKey Integration for Full Disk Encryption Pre-Boot Authentication (Copyright) Yubico, 2011 Version: 1.1'.

Used binaries:
  * uuidgen - for generation of random sequence numbers
  * ykchalresp - for challenging a Yubikey
  * ykinfo - to check if a Yubikey is plugged in at boot (fallback to passphrase authentication otherwise)
  * openssl - for calculation of SHA-1, HMAC-SHA-1, as well as AES-256-CTR (de/en)cryption

Main differences to the specification mentioned above:
  * No user management (yet), only one password+yubikey per LUKS device
  * SHA-512 instead of CRC-16 for checksum

Main differences to the previous implementation:
  * Instead of changing the key slot of the LUKS device each boot,
    the actual key for the LUKS device will be encrypted itself
  * Since the response for the new challenge is now calculated
    locally with openssl, the MITM-USB-attack with which previously
    an attacker could obtain the new response (that was used as the new
    encryption key for the LUKS device) by listening to the
    Yubikey has ideally become useless (as long as uuidgen can
    successfuly generate new random sequence numbers).

Remarks:
  * This is not downwards compatible to the previous implementation
This commit is contained in:
Moritz Maxeiner 2014-01-28 04:02:51 +01:00
parent 333f5caaf9
commit 407a770161

View File

@ -31,140 +31,148 @@ let
fi fi
''} ''}
${optionalString (luks.yubikeySupport && (yubikey != null)) '' open_normally() {
mkdir -p ${yubikey.challenge.mountPoint}
mount -t ${yubikey.challenge.fsType} ${toString yubikey.challenge.device} ${yubikey.challenge.mountPoint}
response="$(ykchalresp -${toString yubikey.yubikeySlot} "`cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file}`" 2>/dev/null || true)"
if [ -z "$response" ]; then
echo -n "waiting 10 seconds for yubikey to appear..."
for try in $(seq 10); do
sleep 1
response="$(ykchalresp -${toString yubikey.yubikeySlot} "`cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file}`" 2>/dev/null || true)"
if [ ! -z "$response" ]; then break; fi
echo -n .
done
echo "ok"
fi
${optionalString yubikey.twoFactor ''
if [ ! -z "$response" ]; then
echo -n "Enter two-factor passphrase: "
read -s passphrase
current_key="$passphrase$response"
fi
''}
${optionalString (!yubikey.twoFactor) ''
if [ ! -z "$response" ]; then
current_key="$response"
fi
''}
''}
# open luksRoot and scan for logical volumes
${optionalString ((!luks.yubikeySupport) || (yubikey == null)) ''
cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"}
''}
${optionalString (luks.yubikeySupport && (yubikey != null)) ''
if [ -z "$response" ]; then
cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \ cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"} ${optionalString (keyFile != null) "--key-file=${keyFile} ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"}"}
else }
echo $current_key | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
if [ $? != "0" ]; then ${optionalString (luks.yubikeySupport && (yubikey != null)) ''
for try in $(seq 3); do
${optionalString (!yubikey.twoFactor) '' rbtohex() {
sleep 1 od -An -vtx1 | tr -d ' \n'
''} }
${optionalString yubikey.twoFactor '' hextorb() {
echo -n "Enter two-factor passphrase: " tr '[:lower:]' '[:upper:]' | sed -e 's|\([0-9A-F]\{2\}\)|\\\\\\x\1|gI' | xargs printf
read -s passphrase }
current_key="$passphrase$response"
''}
echo $current_key | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=- take() {
if [ $? == "0" ]; then break; fi local c="$1"
echo -n . shift
done 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() {
mkdir -p ${yubikey.storage.mountPoint}
mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint}
local uuid_r
uuid_r="$(take 16 ${yubikey.storage.mountPoint}${yubikey.storage.path} | rbtohex)"
local uuid_luks
uuid_luks="$(cryptsetup luksUUID ${device} | take 36 | tr -d '-')"
local k_user
local challenge
local k_blob
local aes_blob_decrypted
local checksum_correct
local checksum
for try in $(seq 3); do
${optionalString yubikey.twoFactor ''
echo -n "Enter two-factor passphrase: "
read -s k_user
''}
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="$(drop 16 ${yubikey.storage.mountPoint}${yubikey.storage.path} | openssl-wrap enc -d -aes-256-ctr -K $k_blob -iv $uuid_r | rbtohex)"
checksum="$(echo -n $aes_blob_decrypted | hextorb | drop 84 | rbtohex)"
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
done
if [ "$checksum_correct" != "1" ]; then
umount ${yubikey.storage.mountPoint}
echo "Maximum authentication errors reached"
exit 1
fi fi
mkdir -p ${yubikey.ramfsMountPoint} local k_yubi
# A ramfs is used here to ensure that the file used to update k_yubi="$(echo -n $aes_blob_decrypted | hextorb | take 20 | rbtohex)"
# the key slot with cryptsetup will never get swapped out.
# Warning: Do NOT replace with tmpfs! local k_luks
mount -t ramfs none ${yubikey.ramfsMountPoint} k_luks="$(echo -n $aes_blob_decrypted | hextorb | drop 20 | take 64 | rbtohex)"
echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
update_failed=false update_failed=false
old_challenge=$(cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file})
new_challenge=$(uuidgen) local new_uuid_r
new_uuid_r="$(uuidgen)"
if [ $? != "0" ]; then if [ $? != "0" ]; then
for try in $(seq 10); do for try in $(seq 10); do
sleep 1 sleep 1
new_challenge=$(uuidgen) new_uuid_r="$(uuidgen)"
if [ $? == "0" ]; then break; fi if [ $? == "0" ]; then break; fi
if [ $try -eq 10 ]; then update_failed=true; fi if [ $try -eq 10 ]; then update_failed=true; fi
done done
fi fi
if [ "$update_failed" == false ]; then if [ "$update_failed" == false ]; then
echo $new_challenge > ${yubikey.challenge.mountPoint}${yubikey.challenge.file} new_uuid_r="$(echo -n $new_uuid_r | take 36 | tr -d '-')"
response="$(ykchalresp -${toString yubikey.yubikeySlot} "`cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file}`" 2>/dev/null || true)"
if [ -z "$response" ]; then
echo -n "waiting 10 seconds for yubikey to appear..."
for try in $(seq 10); do
sleep 1
response="$(ykchalresp -${toString yubikey.yubikeySlot} "`cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file}`" 2>/dev/null || true)"
if [ ! -z "$response" ]; then break; fi
echo -n .
done
echo "ok";
fi
if [ ! -z "$response" ]; then local new_challenge
${optionalString yubikey.twoFactor '' new_challenge="$(echo -n $k_user$new_uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | rbtohex)"
new_key="$passphrase$response"
''}
${optionalString (!yubikey.twoFactor) '' local new_k_blob
new_key="$response" new_k_blob="$(echo -n $new_challenge | hextorb | openssl-wrap dgst -binary -sha1 -mac HMAC -macopt hexkey:$k_yubi | rbtohex)"
''}
echo $new_key > ${yubikey.ramfsMountPoint}/new_key echo -n "$new_uuid_r" | hextorb > ${yubikey.storage.mountPoint}${yubikey.storage.path}
echo -n "$k_yubi$k_luks$checksum" | hextorb | openssl-wrap enc -e -aes-256-ctr -K "$new_k_blob" -iv "$new_uuid_r" >> ${yubikey.storage.mountPoint}${yubikey.storage.path}
echo $current_key | cryptsetup luksChangeKey ${device} --key-file=- --key-slot ${toString yubikey.luksKeySlot} ${yubikey.ramfsMountPoint}/new_key
if [ $? != "0" ]; then
for try in $(seq 10); do
sleep 1
echo $current_key | cryptsetup luksChangeKey ${device} --key-file=- --key-slot ${toString yubikey.luksKeySlot} ${yubikey.ramfsMountPoint}/new_key
if [ $? == "0" ]; then break; fi
if [ $try -eq 10 ]; then update_failed=true; fi
done
fi
rm -f ${yubikey.ramfsMountPoint}/new_key
if [ "$update_failed" == true ]; then
echo $old_challenge > ${yubikey.challenge.mountPoint}${yubikey.challenge.file}
echo "Warning: Could not update luks header with new key for ${device}, old challenge restored!"
fi
else
echo $old_challenge > ${yubikey.challenge.mountPoint}${yubikey.challenge.file}
echo "Warning: No yubikey present to challenge for ${device}, old challenge restored!"
fi
else else
echo "Warning: New challenge could not be obtained for ${device}, old challenge persists!" echo "Warning: Could not obtain new UUID, current challenge persists!"
fi fi
umount ${yubikey.ramfsMountPoint} umount ${yubikey.storage.mountPoint}
umount ${yubikey.challenge.mountPoint} }
ykinfo -v
yubikey_missing="$(ykinfo -v 1>/dev/null 2>&1)$?"
if [ "$yubikey_missing" != "0" ]; then
echo -n "waiting 10 seconds for yubikey to appear..."
for try in $(seq 10); do
sleep 1
ykinfo -v
yubikey_missing="$(ykinfo -v 1>/dev/null 2>&1)$?"
if [ "$yubikey_missing" == "0" ]; then break; fi
echo -n .
done
echo "ok"
fi fi
if [ "$yubikey_missing" != "0" ]; then
echo "no yubikey found, falling back to non-yubikey open procedure"
open_normally
else
open_yubikey
fi
''}
# open luksRoot and scan for logical volumes
${optionalString ((!luks.yubikeySupport) || (yubikey == null)) ''
open_normally
''} ''}
''; '';
@ -283,19 +291,13 @@ in
description = "TODO"; description = "TODO";
}; };
yubikeySlot = mkOption { slot = mkOption {
default = 2; default = 2;
type = types.int; type = types.int;
description = "TODO"; description = "TODO";
}; };
luksKeySlot = mkOption { storage = mkOption {
default = 1;
type = types.int;
description = "TODO";
};
challenge = mkOption {
type = types.optionSet; type = types.optionSet;
description = "TODO"; description = "TODO";
@ -313,24 +315,18 @@ in
}; };
mountPoint = mkOption { mountPoint = mkOption {
default = "/crypt-challenge"; default = "/crypt-storage";
type = types.string; type = types.string;
description = "TODO"; description = "TODO";
}; };
file = mkOption { path = mkOption {
default = "/crypt-challenge"; default = "/crypt-storage/default";
type = types.string; type = types.string;
description = "TODO"; description = "TODO";
}; };
}; };
}; };
ramfsMountPoint = mkOption {
default = "/crypt-update";
type = types.string;
description = "TODO";
};
}; };
}; };
@ -361,6 +357,7 @@ in
cp -pdvn $lib $out/lib cp -pdvn $lib $out/lib
cp -pvn $(readlink -f $lib) $out/lib cp -pvn $(readlink -f $lib) $out/lib
done done
${optionalString luks.yubikeySupport '' ${optionalString luks.yubikeySupport ''
cp -pdv ${pkgs.utillinux}/bin/uuidgen $out/bin cp -pdv ${pkgs.utillinux}/bin/uuidgen $out/bin
for lib in $(ldd $out/bin/uuidgen |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do for lib in $(ldd $out/bin/uuidgen |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do
@ -373,6 +370,26 @@ in
cp -pdvn $lib $out/lib cp -pdvn $lib $out/lib
cp -pvn $(readlink -f $lib) $out/lib cp -pvn $(readlink -f $lib) $out/lib
done done
cp -pdv ${pkgs.ykpers}/bin/ykinfo $out/bin
for lib in $(ldd $out/bin/ykinfo |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do
cp -pdvn $lib $out/lib
cp -pvn $(readlink -f $lib) $out/lib
done
cp -pdv ${pkgs.openssl}/bin/openssl $out/bin
for lib in $(ldd $out/bin/openssl |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do
cp -pdvn $lib $out/lib
cp -pvn $(readlink -f $lib) $out/lib
done
mkdir -p $out/etc/ssl
cp -pdv ${pkgs.openssl}/etc/ssl/openssl.cnf $out/etc/ssl
cat > $out/bin/openssl-wrap <<EOF
#!$out/bin/sh
EOF
chmod +x $out/bin/openssl-wrap
''} ''}
''; '';
@ -381,6 +398,13 @@ in
${optionalString luks.yubikeySupport '' ${optionalString luks.yubikeySupport ''
$out/bin/uuidgen --version $out/bin/uuidgen --version
$out/bin/ykchalresp -V $out/bin/ykchalresp -V
$out/bin/ykinfo -V
cat > $out/bin/openssl-wrap <<EOF
#!$out/bin/sh
export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
$out/bin/openssl "\$@"
EOF
$out/bin/openssl-wrap version
''} ''}
''; '';