Merge branch 'yubikey' of git://github.com/Calrama/nixpkgs

This commit is contained in:
Petr Rockai 2014-02-08 16:01:22 +01:00
commit 12315a278c
2 changed files with 152 additions and 119 deletions

View File

@ -39,153 +39,123 @@ 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() {
# Make all of these local to this function
# to prevent their values being leaked
local salt
local iterations
local k_user
local challenge
local response
local k_luks
local opened
local new_salt
local new_iterations
local new_challenge
local new_response
local new_k_luks
mkdir -p ${yubikey.storage.mountPoint}
mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint}
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
uuid_luks="$(cryptsetup luksUUID ${device} | take 36 | tr -d '-')"
${optionalString (!yubikey.multiUser) ''
user_record="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path})"
uuid_r="$(echo -n $user_record | take 32)"
''}
salt="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 1p | tr -d '\n')"
iterations="$(cat ${yubikey.storage.mountPoint}${yubikey.storage.path} | sed -n 2p | tr -d '\n')"
challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)"
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)"
user_record="$(sed -n -e /^$user_id_hash[^$]*$/p ${yubikey.storage.mountPoint}${yubikey.storage.path} | tr -d '\n')"
if [ ! -z "$user_record" ]; then
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)"
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 [ ! -z "$k_user" ]; then
k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
else
checksum_correct=0
k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)"
fi
echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
if [ $? == "0" ]; then
opened=true
break
else
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)"
echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..."
for i in $(seq ${toString yubikey.saltLength}); do
byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)";
new_salt="$new_salt$byte";
echo -n .
done;
echo "ok"
local k_luks
k_luks="$(echo -n $aes_blob_decrypted | drop 40 | take 128)"
new_iterations="$iterations"
${optionalString (yubikey.iterationStep > 0) ''
new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))"
''}
echo -n "$k_luks" | hextorb | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)"
update_failed=false
new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)"
local new_uuid_r
new_uuid_r="$(uuidgen)"
if [ $? != "0" ]; then
for try in $(seq 10); do
sleep 1
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
new_uuid_r="$(echo -n $new_uuid_r | take 36 | tr -d '-')"
local new_challenge
new_challenge="$(echo -n $k_user$new_uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | 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_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)
${optionalString yubikey.multiUser ''
sed -i -e "s|^$user_id_hash$user_record|$user_id_hash$new_uuid_r$new_aes_blob|1"
''}
${optionalString (!yubikey.multiUser) ''
echo -n "$new_uuid_r$new_aes_blob" > ${yubikey.storage.mountPoint}${yubikey.storage.path}
''}
if [ ! -z "$k_user" ]; then
new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
else
echo "Warning: Could not obtain new UUID, current challenge persists!"
new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)"
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}
echo -n "$new_k_luks" | hextorb > ${yubikey.ramfsMountPoint}/new_key
echo -n "$k_luks" | hextorb | cryptsetup luksChangeKey ${device} --key-file=- ${yubikey.ramfsMountPoint}/new_key
if [ $? == "0" ]; then
echo -ne "$new_salt\n$new_iterations" > ${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}
umount ${yubikey.storage.mountPoint}
}
${optionalString (yubikey.gracePeriod > 0) ''
echo -n "Waiting ${toString yubikey.gracePeriod} seconds as grace..."
for i in $(seq ${toString yubikey.gracePeriod}); do
sleep 1
echo -n .
done
echo "ok"
''}
yubikey_missing=true
ykinfo -v 1>/dev/null 2>&1
if [ $? != "0" ]; then
@ -336,21 +306,45 @@ 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";
};
saltLength = mkOption {
default = 16;
type = types.int;
description = "Length of the new salt in byte (64 is the effective maximum)";
};
keyLength = mkOption {
default = 64;
type = types.int;
description = "Length of the LUKS slot key derived with PBKDF2 in byte";
};
iterationStep = mkOption {
default = 0;
type = types.int;
description = "How much the iteration count for PBKDF2 is increased at each successful authentication";
};
gracePeriod = mkOption {
default = 2;
type = types.int;
description = "Time in seconds to wait before attempting to find the Yubikey";
};
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 salt";
options = {
device = mkOption {
@ -358,7 +352,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 salt to create the challenge for this LUKS device.
'';
};
@ -378,7 +372,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 salt on the unencrypted device with
that device's root directory as "/".
'';
};
@ -420,11 +414,13 @@ in
cp -pdv ${pkgs.popt}/lib/libpopt*.so.* $out/lib
${optionalString luks.yubikeySupport ''
cp -pdv ${pkgs.utillinux}/bin/uuidgen $out/bin
cp -pdv ${pkgs.ykpers}/bin/ykchalresp $out/bin
cp -pdv ${pkgs.ykpers}/bin/ykinfo $out/bin
cp -pdv ${pkgs.openssl}/bin/openssl $out/bin
cc -O3 -I${pkgs.openssl}/include -L${pkgs.openssl}/lib ${./pbkdf2-sha512.c} -o $out/bin/pbkdf2-sha512 -lcrypto
strip -s $out/bin/pbkdf2-sha512
cp -pdv ${pkgs.libusb1}/lib/libusb*.so.* $out/lib
cp -pdv ${pkgs.ykpers}/lib/libykpers*.so.* $out/lib
cp -pdv ${pkgs.libyubikey}/lib/libyubikey*.so.* $out/lib
@ -444,7 +440,6 @@ EOF
boot.initrd.extraUtilsCommandsTest = ''
$out/bin/cryptsetup --version
${optionalString luks.yubikeySupport ''
$out/bin/uuidgen --version
$out/bin/ykchalresp -V
$out/bin/ykinfo -V
cat > $out/bin/openssl-wrap <<EOF

View File

@ -0,0 +1,38 @@
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <openssl/evp.h>
void hextorb(uint8_t* hex, uint8_t* rb)
{
while(sscanf(hex, "%2x", rb) == 1)
{
hex += 2;
rb += 1;
}
*rb = '\0';
}
int main(int argc, char** argv)
{
uint8_t k_user[2048];
uint8_t salt[2048];
uint8_t key[4096];
uint32_t key_length = atoi(argv[1]);
uint32_t iteration_count = atoi(argv[2]);
hextorb(argv[3], salt);
uint32_t salt_length = strlen(argv[3]) / 2;
fgets(k_user, 2048, stdin);
uint32_t k_user_length = strlen(k_user);
if(k_user[k_user_length - 1] == '\n') {
k_user[k_user_length - 1] = '\0';
}
PKCS5_PBKDF2_HMAC(k_user, k_user_length, salt, salt_length, iteration_count, EVP_sha512(), key_length, key);
fwrite(key, 1, key_length, stdout);
return 0;
}