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
''}
${optionalString (luks.yubikeySupport && (yubikey != null)) ''
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
open_normally() {
cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} \
${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
for try in $(seq 3); do
${optionalString (luks.yubikeySupport && (yubikey != null)) ''
${optionalString (!yubikey.twoFactor) ''
sleep 1
''}
rbtohex() {
od -An -vtx1 | tr -d ' \n'
}
${optionalString yubikey.twoFactor ''
echo -n "Enter two-factor passphrase: "
read -s passphrase
current_key="$passphrase$response"
''}
hextorb() {
tr '[:lower:]' '[:upper:]' | sed -e 's|\([0-9A-F]\{2\}\)|\\\\\\x\1|gI' | xargs printf
}
echo $current_key | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
if [ $? == "0" ]; then break; fi
echo -n .
done
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() {
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
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}
local k_yubi
k_yubi="$(echo -n $aes_blob_decrypted | hextorb | take 20 | rbtohex)"
local k_luks
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
old_challenge=$(cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file})
new_challenge=$(uuidgen)
local new_uuid_r
new_uuid_r="$(uuidgen)"
if [ $? != "0" ]; then
for try in $(seq 10); do
sleep 1
new_challenge=$(uuidgen)
new_uuid_r="$(uuidgen)"
if [ $? == "0" ]; then break; fi
if [ $try -eq 10 ]; then update_failed=true; fi
done
fi
if [ "$update_failed" == false ]; then
echo $new_challenge > ${yubikey.challenge.mountPoint}${yubikey.challenge.file}
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
new_uuid_r="$(echo -n $new_uuid_r | take 36 | tr -d '-')"
if [ ! -z "$response" ]; then
${optionalString yubikey.twoFactor ''
new_key="$passphrase$response"
''}
local new_challenge
new_challenge="$(echo -n $k_user$new_uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | rbtohex)"
${optionalString (!yubikey.twoFactor) ''
new_key="$response"
''}
local new_k_blob
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 $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
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}
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
umount ${yubikey.ramfsMountPoint}
umount ${yubikey.challenge.mountPoint}
umount ${yubikey.storage.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
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";
};
yubikeySlot = mkOption {
slot = mkOption {
default = 2;
type = types.int;
description = "TODO";
};
luksKeySlot = mkOption {
default = 1;
type = types.int;
description = "TODO";
};
challenge = mkOption {
storage = mkOption {
type = types.optionSet;
description = "TODO";
@ -313,24 +315,18 @@ in
};
mountPoint = mkOption {
default = "/crypt-challenge";
default = "/crypt-storage";
type = types.string;
description = "TODO";
};
file = mkOption {
default = "/crypt-challenge";
path = mkOption {
default = "/crypt-storage/default";
type = types.string;
description = "TODO";
};
};
};
ramfsMountPoint = mkOption {
default = "/crypt-update";
type = types.string;
description = "TODO";
};
};
};
@ -361,6 +357,7 @@ in
cp -pdvn $lib $out/lib
cp -pvn $(readlink -f $lib) $out/lib
done
${optionalString luks.yubikeySupport ''
cp -pdv ${pkgs.utillinux}/bin/uuidgen $out/bin
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 -pvn $(readlink -f $lib) $out/lib
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 ''
$out/bin/uuidgen --version
$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
''}
'';