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:
parent
333f5caaf9
commit
407a770161
@ -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
|
||||||
''}
|
''}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user