summary refs log tree commit diff
path: root/nixos/modules/system/boot/luksroot.nix
diff options
context:
space:
mode:
authorMoritz Maxeiner <moritz@ucworks.org>2014-01-28 04:02:51 +0100
committerMoritz Maxeiner <moritz@ucworks.org>2014-01-28 04:02:51 +0100
commit407a77016162958c743bbf354465e0396c32ecc0 (patch)
treef4c9fbf63370a563f7c24103bcb296f3f12ba61e /nixos/modules/system/boot/luksroot.nix
parent333f5caaf91ce1c28586483774a60bf385e19a32 (diff)
downloadnixpkgs-407a77016162958c743bbf354465e0396c32ecc0.tar
nixpkgs-407a77016162958c743bbf354465e0396c32ecc0.tar.gz
nixpkgs-407a77016162958c743bbf354465e0396c32ecc0.tar.bz2
nixpkgs-407a77016162958c743bbf354465e0396c32ecc0.tar.lz
nixpkgs-407a77016162958c743bbf354465e0396c32ecc0.tar.xz
nixpkgs-407a77016162958c743bbf354465e0396c32ecc0.tar.zst
nixpkgs-407a77016162958c743bbf354465e0396c32ecc0.zip
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
Diffstat (limited to 'nixos/modules/system/boot/luksroot.nix')
-rw-r--r--nixos/modules/system/boot/luksroot.nix266
1 files changed, 145 insertions, 121 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
     ''}
 
+    open_normally() {
+        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)) ''
-    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
-    ''}
+    rbtohex() {
+        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
+    }
 
-    ${optionalString (!yubikey.twoFactor) ''
-    if [ ! -z "$response" ]; then
-        current_key="$response"
-    fi
-    ''}
-    ''}
+    open_yubikey() {
 
-    # 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}"}"}
-    ''}
+        mkdir -p ${yubikey.storage.mountPoint}
+        mount -t ${yubikey.storage.fsType} ${toString yubikey.storage.device} ${yubikey.storage.mountPoint}
 
-    ${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=-
+        local uuid_r
+        uuid_r="$(take 16 ${yubikey.storage.mountPoint}${yubikey.storage.path} | rbtohex)"
 
-        if [ $? != "0" ]; then
-            for try in $(seq 3); do
+        local uuid_luks
+        uuid_luks="$(cryptsetup luksUUID ${device} | take 36 | tr -d '-')"
 
-                ${optionalString (!yubikey.twoFactor) ''
-                sleep 1
-                ''}
+        local k_user
+        local challenge
+        local k_blob
+        local aes_blob_decrypted
+        local checksum_correct
+        local checksum
 
-                ${optionalString yubikey.twoFactor ''
-                echo -n "Enter two-factor passphrase: "
-                read -s passphrase
-                current_key="$passphrase$response"
-                ''}
+        for try in $(seq 3); do
 
-                echo $current_key | cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} --key-file=-
-                if [ $? == "0" ]; then break; fi
-                echo -n .
-            done
+            ${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
       ''}
     '';