From 433774c6088501f53b36ddac5811ff63784ae75c Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Sat, 25 Jan 2014 03:13:34 +0100 Subject: [PATCH 01/10] Add libyubikey (aka yubico-c) package --- .../libraries/libyubikey/default.nix | 20 +++++++++++++++++++ pkgs/top-level/all-packages.nix | 2 ++ 2 files changed, 22 insertions(+) create mode 100644 pkgs/development/libraries/libyubikey/default.nix diff --git a/pkgs/development/libraries/libyubikey/default.nix b/pkgs/development/libraries/libyubikey/default.nix new file mode 100644 index 00000000000..77bf60c4255 --- /dev/null +++ b/pkgs/development/libraries/libyubikey/default.nix @@ -0,0 +1,20 @@ +{stdenv, fetchurl}: + +stdenv.mkDerivation rec +{ + version = "1.11"; + name = "libyubikey-${version}"; + + src = fetchurl + { + url = "http://opensource.yubico.com/yubico-c/releases/${name}.tar.gz"; + sha256 = "19pm4rqsnm9r0n5j26bqkxa1jpimdavzcvg5g7p416vkjhxc6lw9"; + }; + + meta = + { + homepage = "http://opensource.yubico.com/yubico-c/"; + description = "C library for manipulating Yubico YubiKey One-Time Passwords (OTPs)"; + license = "bsd"; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 6ee74ee90e1..f16c5b7cd9e 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -5036,6 +5036,8 @@ let libyamlcpp = callPackage ../development/libraries/libyaml-cpp { }; libyamlcpp03 = callPackage ../development/libraries/libyaml-cpp/0.3.x.nix { }; + libyubikey = callPackage ../development/libraries/libyubikey {}; + libzip = callPackage ../development/libraries/libzip { }; libzrtpcpp = callPackage ../development/libraries/libzrtpcpp { }; From 8f9300fb0ee63c2e0ebc0080346c15dbb26a72ee Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Sat, 25 Jan 2014 03:17:06 +0100 Subject: [PATCH 02/10] Add ykpers (aka yubikey-personalization) package --- pkgs/applications/misc/ykpers/default.nix | 22 ++++++++++++++++++++++ pkgs/top-level/all-packages.nix | 2 ++ 2 files changed, 24 insertions(+) create mode 100644 pkgs/applications/misc/ykpers/default.nix diff --git a/pkgs/applications/misc/ykpers/default.nix b/pkgs/applications/misc/ykpers/default.nix new file mode 100644 index 00000000000..70ec351f71b --- /dev/null +++ b/pkgs/applications/misc/ykpers/default.nix @@ -0,0 +1,22 @@ +{stdenv, fetchurl, pkgconfig, libusb1, libyubikey}: + +stdenv.mkDerivation rec +{ + version = "1.15.0"; + name = "ykpers-${version}"; + + src = fetchurl + { + url = "http://opensource.yubico.com/yubikey-personalization/releases/${name}.tar.gz"; + sha256 = "1n4s8kk31q5zh2rm7sj9qmv86yl8ibimdnpvk9ny391a88qlypyd"; + }; + + buildInputs = [pkgconfig libusb1 libyubikey]; + + meta = + { + homepage = "http://opensource.yubico.com/yubikey-personalization/"; + description = "YubiKey Personalization cross-platform library and tool"; + license = "bsd"; + }; +} diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index f16c5b7cd9e..a967115b5ee 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -9119,6 +9119,8 @@ let qgis = callPackage ../applications/misc/qgis {}; + ykpers = callPackage ../applications/misc/ykpers {}; + yoshimi = callPackage ../applications/audio/yoshimi { fltk = fltk13; }; From 333f5caaf91ce1c28586483774a60bf385e19a32 Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Sat, 25 Jan 2014 03:27:12 +0100 Subject: [PATCH 03/10] Implement authentication for a LUKS device with a yubikey (HMAC-SHA1); supports simple challenge-response and two-factor authentication --- nixos/modules/system/boot/luksroot.nix | 220 ++++++++++++++++++++++++- 1 file changed, 218 insertions(+), 2 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index ba357f5d2de..ca4a8c480f8 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -5,7 +5,7 @@ with pkgs.lib; let luks = config.boot.initrd.luks; - openCommand = { name, device, keyFile, keyFileSize, allowDiscards, ... }: '' + openCommand = { name, device, keyFile, keyFileSize, allowDiscards, yubikey, ... }: '' # Wait for luksRoot to appear, e.g. if on a usb drive. # XXX: copied and adapted from stage-1-init.sh - should be # available as a function. @@ -31,9 +31,141 @@ 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 + 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 (!yubikey.twoFactor) '' + sleep 1 + ''} + + ${optionalString yubikey.twoFactor '' + echo -n "Enter two-factor passphrase: " + read -s passphrase + current_key="$passphrase$response" + ''} + + echo $current_key | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=- + if [ $? == "0" ]; then break; fi + echo -n . + done + 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} + + update_failed=false + old_challenge=$(cat ${yubikey.challenge.mountPoint}${yubikey.challenge.file}) + + new_challenge=$(uuidgen) + if [ $? != "0" ]; then + for try in $(seq 10); do + sleep 1 + new_challenge=$(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 + + if [ ! -z "$response" ]; then + ${optionalString yubikey.twoFactor '' + new_key="$passphrase$response" + ''} + + ${optionalString (!yubikey.twoFactor) '' + new_key="$response" + ''} + + 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 + else + echo "Warning: New challenge could not be obtained for ${device}, old challenge persists!" + fi + + umount ${yubikey.ramfsMountPoint} + umount ${yubikey.challenge.mountPoint} + fi + ''} ''; isPreLVM = f: f.preLVM; @@ -139,10 +271,77 @@ in ''; }; - }; + yubikey = mkOption { + default = null; + type = types.nullOr types.optionSet; + description = "TODO"; + options = { + twoFactor = mkOption { + default = false; + type = types.bool; + description = "TODO"; + }; + + yubikeySlot = mkOption { + default = 2; + type = types.int; + description = "TODO"; + }; + + luksKeySlot = mkOption { + default = 1; + type = types.int; + description = "TODO"; + }; + + challenge = mkOption { + type = types.optionSet; + description = "TODO"; + + options = { + device = mkOption { + default = /dev/sda1; + type = types.path; + description = "TODO"; + }; + + fsType = mkOption { + default = "vfat"; + type = types.string; + description = "TODO"; + }; + + mountPoint = mkOption { + default = "/crypt-challenge"; + type = types.string; + description = "TODO"; + }; + + file = mkOption { + default = "/crypt-challenge"; + type = types.string; + description = "TODO"; + }; + }; + }; + + ramfsMountPoint = mkOption { + default = "/crypt-update"; + type = types.string; + description = "TODO"; + }; + }; + }; + + }; }; + boot.initrd.luks.yubikeySupport = mkOption { + default = false; + type = types.bool; + description = "TODO"; + }; }; config = mkIf (luks.devices != []) { @@ -162,10 +361,27 @@ 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 + cp -pdvn $lib $out/lib + cp -pvn $(readlink -f $lib) $out/lib + done + + cp -pdv ${pkgs.ykpers}/bin/ykchalresp $out/bin + for lib in $(ldd $out/bin/ykchalresp |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do + cp -pdvn $lib $out/lib + cp -pvn $(readlink -f $lib) $out/lib + done + ''} ''; boot.initrd.extraUtilsCommandsTest = '' $out/bin/cryptsetup --version + ${optionalString luks.yubikeySupport '' + $out/bin/uuidgen --version + $out/bin/ykchalresp -V + ''} ''; boot.initrd.preLVMCommands = concatMapStrings openCommand preLVM; From 407a77016162958c743bbf354465e0396c32ecc0 Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Tue, 28 Jan 2014 04:02:51 +0100 Subject: [PATCH 04/10] 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 --- nixos/modules/system/boot/luksroot.nix | 272 ++++++++++++++----------- 1 file changed, 148 insertions(+), 124 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index ca4a8c480f8..52c91a0e20c 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -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 < $out/bin/openssl-wrap < Date: Tue, 28 Jan 2014 20:39:25 +0100 Subject: [PATCH 05/10] Cosmetic change to yubikey detection --- nixos/modules/system/boot/luksroot.nix | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 52c91a0e20c..46cd96e0167 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -148,21 +148,25 @@ let umount ${yubikey.storage.mountPoint} } - ykinfo -v - yubikey_missing="$(ykinfo -v 1>/dev/null 2>&1)$?" - if [ "$yubikey_missing" != "0" ]; then + yubikey_missing=true + ykinfo -v 1>/dev/null 2>&1 + if [ $? != "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 + ykinfo -v 1>/dev/null 2>&1 + if [ $? == "0" ]; then + yubikey_missing=false + break + fi echo -n . done echo "ok" + else + yubikey_missing=false fi - if [ "$yubikey_missing" != "0" ]; then + if [ "$yubikey_missing" == true ]; then echo "no yubikey found, falling back to non-yubikey open procedure" open_normally else From cce9712331e7470aa891dc66d0aa28255d582e97 Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Tue, 28 Jan 2014 23:45:16 +0100 Subject: [PATCH 06/10] Enable two-factor authentication by default. Add proper descriptions to attributes. --- nixos/modules/system/boot/luksroot.nix | 35 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 46cd96e0167..ae684dd8977 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -53,7 +53,7 @@ let } drop() { - local c=$1 + 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 ) @@ -286,48 +286,57 @@ in yubikey = mkOption { default = null; type = types.nullOr types.optionSet; - description = "TODO"; + description = '' + The options to use for this LUKS device in Yubikey-PBA. + If null (the default), Yubikey-PBA will be disabled for this device. + ''; options = { twoFactor = mkOption { - default = false; + default = true; type = types.bool; - description = "TODO"; + description = "Whether to use a passphrase and a Yubikey (true), or only a Yubikey (false)"; }; slot = mkOption { default = 2; type = types.int; - description = "TODO"; + description = "Which slot on the Yubikey to challenge"; }; storage = mkOption { type = types.optionSet; - description = "TODO"; + description = "Options related to the authentication record"; options = { device = mkOption { default = /dev/sda1; type = types.path; - description = "TODO"; + description = '' + An unencrypted device that will temporarily be mounted in stage-1. + Must contain the authentication record for this LUKS device. + ''; }; fsType = mkOption { default = "vfat"; type = types.string; - description = "TODO"; + description = "The filesystem of the unencrypted device"; }; mountPoint = mkOption { default = "/crypt-storage"; type = types.string; - description = "TODO"; + description = "Path where the unencrypted device will be mounted in stage-1"; }; path = mkOption { default = "/crypt-storage/default"; type = types.string; - description = "TODO"; + description = '' + Absolute path of the authentication record on the unencrypted device with + that device's root directory as "/". + ''; }; }; }; @@ -340,7 +349,11 @@ in boot.initrd.luks.yubikeySupport = mkOption { default = false; type = types.bool; - description = "TODO"; + description = '' + Enables support for authenticating with a Yubikey on LUKS devices. + See the NixOS wiki for information on how to properly setup a LUKS device + and a Yubikey to work with this feature. + ''; }; }; From 20cfaf0faaef3fe3115275aa64b26b634f0108f0 Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Wed, 29 Jan 2014 13:57:12 +0100 Subject: [PATCH 07/10] Change the crypt-storage file to be hex encoded instead of raw binary. To update from the previous configuration, convert your crypt-storage file from raw binary to hex. --- nixos/modules/system/boot/luksroot.nix | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index ae684dd8977..b9dc6d7823e 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -68,7 +68,7 @@ let 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)" + uuid_r="$(take 32 ${yubikey.storage.mountPoint}${yubikey.storage.path})" local uuid_luks uuid_luks="$(cryptsetup luksUUID ${device} | take 36 | tr -d '-')" @@ -91,9 +91,9 @@ let 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)" + aes_blob_decrypted="$(drop 32 ${yubikey.storage.mountPoint}${yubikey.storage.path} | hextorb | openssl-wrap enc -d -aes-256-ctr -K $k_blob -iv $uuid_r | rbtohex)" - checksum="$(echo -n $aes_blob_decrypted | hextorb | drop 84 | 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 @@ -110,10 +110,10 @@ let fi local k_yubi - k_yubi="$(echo -n $aes_blob_decrypted | hextorb | take 20 | rbtohex)" + k_yubi="$(echo -n $aes_blob_decrypted | take 40)" local k_luks - k_luks="$(echo -n $aes_blob_decrypted | hextorb | drop 20 | take 64 | rbtohex)" + 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=- @@ -139,8 +139,8 @@ let 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 -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 -n "$new_uuid_r" > ${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" | rbtohex >> ${yubikey.storage.mountPoint}${yubikey.storage.path} else echo "Warning: Could not obtain new UUID, current challenge persists!" fi From e96f58ef5cdbb28cd4efe1b5f7bc532b9ae55b58 Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Wed, 29 Jan 2014 17:20:05 +0100 Subject: [PATCH 08/10] Implement muli-user authentication for yubikey pba, i.e. multiple users can now share a single luks keyslot. This is achieved by having multiple lines per storage file, one for each user (if the feature is enabled); each of these lines has the same format as would be the case for the userless authentication, except that they are prepended with a SHA-512 of the user's id. --- nixos/modules/system/boot/luksroot.nix | 76 ++++++++++++++++++++------ 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index b9dc6d7823e..3f4b703fa9c 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -68,39 +68,69 @@ let mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint} local uuid_r - uuid_r="$(take 32 ${yubikey.storage.mountPoint}${yubikey.storage.path})" - - 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 + 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)" + ''} 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 ''} - challenge="$(echo -n $k_user$uuid_r$uuid_luks | openssl-wrap dgst -binary -sha1 | rbtohex)" + ${optionalString yubikey.multiUser '' + local user_id_hash + user_id_hash="$(echo -n $user_id | openssl-wrap dgst -binary -sha512 | rbtohex)" - k_blob="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" + user_record="$(sed -n -e /^$user_id_hash[^$]*$/p ${yubikey.storage.mountPoint}${yubikey.storage.path} | tr -d '\n')" - aes_blob_decrypted="$(drop 32 ${yubikey.storage.mountPoint}${yubikey.storage.path} | hextorb | openssl-wrap enc -d -aes-256-ctr -K $k_blob -iv $uuid_r | rbtohex)" + if [ ! -z "$user_record" ]; then + user_record="$(echo -n $user_record | drop 128)" + uuid_r="$(echo -n $user_record | take 32)" + ''} - 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 + 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 '' else - checksum_correct=0 - echo "Authentication failed!" + checksum_correct=0 + echo "Authentication failed!" fi + ''} done if [ "$checksum_correct" != "1" ]; then @@ -139,8 +169,16 @@ let 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 -n "$new_uuid_r" > ${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" | rbtohex >> ${yubikey.storage.mountPoint}${yubikey.storage.path} + 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} + ''} else echo "Warning: Could not obtain new UUID, current challenge persists!" fi @@ -298,6 +336,12 @@ 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; From b0d2f0e3f04a74e847dee898bf4c73767c986dfc Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Wed, 29 Jan 2014 18:05:05 +0100 Subject: [PATCH 09/10] Add myself as a maintainer for ykpers and libyubikey --- lib/maintainers.nix | 1 + pkgs/applications/misc/ykpers/default.nix | 7 ++++--- pkgs/development/libraries/libyubikey/default.nix | 7 ++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/maintainers.nix b/lib/maintainers.nix index 3c7a399623d..5e55b64ffa9 100644 --- a/lib/maintainers.nix +++ b/lib/maintainers.nix @@ -18,6 +18,7 @@ bjornfor = "Bjørn Forsman "; bluescreen303 = "Mathijs Kwik "; bodil = "Bodil Stokke "; + calrama = "Moritz Maxeiner "; chaoflow = "Florian Friesdorf "; coconnor = "Corey O'Connor "; coroa = "Jonas Hörsch "; diff --git a/pkgs/applications/misc/ykpers/default.nix b/pkgs/applications/misc/ykpers/default.nix index 70ec351f71b..e7bfa8ded50 100644 --- a/pkgs/applications/misc/ykpers/default.nix +++ b/pkgs/applications/misc/ykpers/default.nix @@ -8,7 +8,7 @@ stdenv.mkDerivation rec src = fetchurl { url = "http://opensource.yubico.com/yubikey-personalization/releases/${name}.tar.gz"; - sha256 = "1n4s8kk31q5zh2rm7sj9qmv86yl8ibimdnpvk9ny391a88qlypyd"; + sha256 = "1n4s8kk31q5zh2rm7sj9qmv86yl8ibimdnpvk9ny391a88qlypyd"; }; buildInputs = [pkgconfig libusb1 libyubikey]; @@ -16,7 +16,8 @@ stdenv.mkDerivation rec meta = { homepage = "http://opensource.yubico.com/yubikey-personalization/"; - description = "YubiKey Personalization cross-platform library and tool"; - license = "bsd"; + description = "YubiKey Personalization cross-platform library and tool"; + license = "bsd"; + maintainers = [ stdenv.lib.maintainers.calrama ]; }; } diff --git a/pkgs/development/libraries/libyubikey/default.nix b/pkgs/development/libraries/libyubikey/default.nix index 77bf60c4255..25c2117b059 100644 --- a/pkgs/development/libraries/libyubikey/default.nix +++ b/pkgs/development/libraries/libyubikey/default.nix @@ -8,13 +8,14 @@ stdenv.mkDerivation rec src = fetchurl { url = "http://opensource.yubico.com/yubico-c/releases/${name}.tar.gz"; - sha256 = "19pm4rqsnm9r0n5j26bqkxa1jpimdavzcvg5g7p416vkjhxc6lw9"; + sha256 = "19pm4rqsnm9r0n5j26bqkxa1jpimdavzcvg5g7p416vkjhxc6lw9"; }; meta = { homepage = "http://opensource.yubico.com/yubico-c/"; - description = "C library for manipulating Yubico YubiKey One-Time Passwords (OTPs)"; - license = "bsd"; + description = "C library for manipulating Yubico YubiKey One-Time Passwords (OTPs)"; + license = "bsd"; + maintainers = [ stdenv.lib.maintainers.calrama ]; }; } From 7bf94cadad5c416375c3e5dfd46f31a81b76ea05 Mon Sep 17 00:00:00 2001 From: Moritz Maxeiner Date: Wed, 29 Jan 2014 18:49:26 +0100 Subject: [PATCH 10/10] Add library dependencies explicitly --- nixos/modules/system/boot/luksroot.nix | 35 ++++++++------------------ 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index 3f4b703fa9c..8547682284f 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -413,36 +413,23 @@ in # copy the cryptsetup binary and it's dependencies boot.initrd.extraUtilsCommands = '' cp -pdv ${pkgs.cryptsetup}/sbin/cryptsetup $out/bin - # XXX: do we have a function that does this? - for lib in $(ldd $out/bin/cryptsetup |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do - cp -pdvn $lib $out/lib - cp -pvn $(readlink -f $lib) $out/lib - done + + cp -pdv ${pkgs.libgcrypt}/lib/libgcrypt*.so.* $out/lib + cp -pdv ${pkgs.libgpgerror}/lib/libgpg-error*.so.* $out/lib + cp -pdv ${pkgs.cryptsetup}/lib/libcryptsetup*.so.* $out/lib + cp -pdv ${pkgs.popt}/lib/libpopt*.so.* $out/lib ${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 - cp -pdvn $lib $out/lib - cp -pvn $(readlink -f $lib) $out/lib - done - cp -pdv ${pkgs.ykpers}/bin/ykchalresp $out/bin - for lib in $(ldd $out/bin/ykchalresp |grep '=>' |grep /nix/store/ |cut -d' ' -f3); do - 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 + + 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 + cp -pdv ${pkgs.openssl}/lib/libssl*.so.* $out/lib + cp -pdv ${pkgs.openssl}/lib/libcrypto*.so.* $out/lib mkdir -p $out/etc/ssl cp -pdv ${pkgs.openssl}/etc/ssl/openssl.cnf $out/etc/ssl