about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorPetr Rockai <me@mornfall.net>2014-01-29 18:54:07 +0100
committerPetr Rockai <me@mornfall.net>2014-01-29 18:54:07 +0100
commit2062abfd4f0d9c6d80439db9e3140fc24ae2477a (patch)
tree641f8f09457a99930ef01cb85208e4276e0f5312 /nixos
parent7c24880b4c6f2f91e50f613694bb2ab32b6bfb40 (diff)
parent7bf94cadad5c416375c3e5dfd46f31a81b76ea05 (diff)
downloadnixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.tar
nixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.tar.gz
nixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.tar.bz2
nixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.tar.lz
nixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.tar.xz
nixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.tar.zst
nixlib-2062abfd4f0d9c6d80439db9e3140fc24ae2477a.zip
Merge branch 'yubikey' of git://github.com/Calrama/nixpkgs
Diffstat (limited to 'nixos')
-rw-r--r--nixos/modules/system/boot/luksroot.nix306
1 files changed, 297 insertions, 9 deletions
diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix
index ba357f5d2de3..8547682284f7 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,191 @@ 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)) ''
+
+    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
+    }
+
+    open_yubikey() {
+
+        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)"
+        ''}
+
+        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 ''
+            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
+
+        local k_yubi
+        k_yubi="$(echo -n $aes_blob_decrypted | take 40)"
+
+        local k_luks
+        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=-
+
+        update_failed=false
+
+        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}
+            ''}
+        else
+            echo "Warning: Could not obtain new UUID, current challenge persists!"
+        fi
+
+        umount ${yubikey.storage.mountPoint}
+    }
+
+    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 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" == true ]; 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
-    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)) ''
+    open_normally
+    ''}
   '';
 
   isPreLVM = f: f.preLVM;
@@ -139,10 +321,84 @@ in
           '';
         };
 
-      };
+        yubikey = mkOption {
+          default = null;
+          type = types.nullOr types.optionSet;
+          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 = true;
+              type = types.bool;
+              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";
+            };
+
+            storage = mkOption {
+              type = types.optionSet;
+              description = "Options related to the authentication record";
+
+              options = {
+                device = mkOption {
+                  default = /dev/sda1;
+                  type = types.path;
+                  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 = "The filesystem of the unencrypted device";
+                };
+
+                mountPoint = mkOption {
+                  default = "/crypt-storage";
+                  type = types.string;
+                  description = "Path where the unencrypted device will be mounted in stage-1";
+                };
+
+                path = mkOption {
+                  default = "/crypt-storage/default";
+                  type = types.string;
+                  description = ''
+                    Absolute path of the authentication record on the unencrypted device with
+                    that device's root directory as "/".
+                  '';
+                };
+              };
+            };
+          };
+        };
 
+      };
     };
 
+    boot.initrd.luks.yubikeySupport = mkOption {
+      default = false;
+      type = types.bool;
+      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.
+          '';
+    };
   };
 
   config = mkIf (luks.devices != []) {
@@ -157,15 +413,47 @@ 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
+      cp -pdv ${pkgs.ykpers}/bin/ykchalresp $out/bin
+      cp -pdv ${pkgs.ykpers}/bin/ykinfo $out/bin
+      cp -pdv ${pkgs.openssl}/bin/openssl $out/bin
+
+      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
+
+      cat > $out/bin/openssl-wrap <<EOF
+#!$out/bin/sh
+EOF
+      chmod +x $out/bin/openssl-wrap
+      ''}
     '';
 
     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
+#!$out/bin/sh
+export OPENSSL_CONF=$out/etc/ssl/openssl.cnf
+$out/bin/openssl "\$@"
+EOF
+        $out/bin/openssl-wrap version
+      ''}
     '';
 
     boot.initrd.preLVMCommands = concatMapStrings openCommand preLVM;