From e7e44cd61bbcead0e9e3adfcf704d7d4a24bd111 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 13 Oct 2020 14:17:22 +0200 Subject: [PATCH 1/6] fix test --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cli_args/config.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b91b404..6a93512 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "fido2luks" -version = "0.2.14" +version = "0.2.15" dependencies = [ "ctap_hmac", "failure", diff --git a/Cargo.toml b/Cargo.toml index ad486f0..c858081 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fido2luks" -version = "0.2.14" +version = "0.2.15" authors = ["shimunn "] edition = "2018" diff --git a/src/cli_args/config.rs b/src/cli_args/config.rs index d2ef46c..cde093f 100644 --- a/src/cli_args/config.rs +++ b/src/cli_args/config.rs @@ -198,7 +198,7 @@ mod test { fn input_salt_obtain() { assert_eq!( SecretInput::String("abc".into()) - .obtain(&PasswordHelper::Stdin) + .obtain_sha256(&PasswordHelper::Stdin) .unwrap(), [ 186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97, From 8a7b3addbbf8f1fb7455ffbaa221e9cc7aa4224f Mon Sep 17 00:00:00 2001 From: Alexandre ACEBEDO Date: Sun, 27 Sep 2020 16:30:44 +0200 Subject: [PATCH 2/6] Added an helper script to be used with pam_mount --- Cargo.toml | 1 + PKGBUILD | 1 + pam_mount/fido2luksmounthelper.sh | 220 ++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100755 pam_mount/fido2luksmounthelper.sh diff --git a/Cargo.toml b/Cargo.toml index ad486f0..d52f39c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ extended-description = "Decrypt your LUKS partition using a FIDO2 compatible aut assets = [ ["target/release/fido2luks", "usr/bin/", "755"], ["fido2luks.bash", "usr/share/bash-completion/completions/fido2luks", "644"], + ["pam_mount/fido2luksmounthelper.sh", "usr/bin/", "755"], ["initramfs-tools/keyscript.sh", "/lib/cryptsetup/scripts/fido2luks", "755" ], ["initramfs-tools/hook/fido2luks.sh", "etc/initramfs-tools/hooks/", "755" ], ["initramfs-tools/fido2luks.conf", "etc/", "644"], diff --git a/PKGBUILD b/PKGBUILD index 6828676..99dfb59 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -21,5 +21,6 @@ build() { package() { install -Dm 755 target/release/${pkgname} -t "${pkgdir}/usr/bin" + install -Dm 755 ../pam_mount/fido2luksmounthelper.sh -t "${pkgdir}/usr/bin" install -Dm 644 ../fido2luks.bash "${pkgdir}/usr/share/bash-completion/completions/fido2luks" } diff --git a/pam_mount/fido2luksmounthelper.sh b/pam_mount/fido2luksmounthelper.sh new file mode 100755 index 0000000..64c6908 --- /dev/null +++ b/pam_mount/fido2luksmounthelper.sh @@ -0,0 +1,220 @@ +#!/bin/bash +# +# This is a rather minimal example Argbash potential +# Example taken from http://argbash.readthedocs.io/en/stable/example.html +# +# ARG_POSITIONAL_SINGLE([operation],[Operation to perform (mount|umount)],[]) +# ARG_OPTIONAL_SINGLE([credentials-type],[c],[Type of the credentials to use (external|embedded)]) +# ARG_OPTIONAL_SINGLE([device],[d],[Name of the device to create]) +# ARG_OPTIONAL_SINGLE([mount-point],[m],[Path of the mount point to use]) +# ARG_OPTIONAL_BOOLEAN([ask-pin],[a],[Ask for a pin],[off]) +# ARG_OPTIONAL_SINGLE([salt],[s],[Salt to use],[""]) +# ARG_HELP([Unlocks/Locks a LUKS volume and mount/unmount it in the given mount point.]) +# ARGBASH_GO() +# needed because of Argbash --> m4_ignore([ +### START OF CODE GENERATED BY Argbash v2.9.0 one line above ### +# Argbash is a bash code generator used to get arguments parsing right. +# Argbash is FREE SOFTWARE, see https://argbash.io for more info +# Generated online by https://argbash.io/generate + + +die() +{ + local _ret="${2:-1}" + test "${_PRINT_HELP:-no}" = yes && print_help >&2 + echo "$1" >&2 + exit "${_ret}" +} + + +begins_with_short_option() +{ + local first_option all_short_options='cdmash' + first_option="${1:0:1}" + test "$all_short_options" = "${all_short_options/$first_option/}" && return 1 || return 0 +} + +# THE DEFAULTS INITIALIZATION - POSITIONALS +_positionals=() +# THE DEFAULTS INITIALIZATION - OPTIONALS +_arg_credentials_type= +_arg_device= +_arg_mount_point= +_arg_ask_pin="off" +_arg_salt="" + + +print_help() +{ + printf '%s\n' "Unlocks/Locks a LUKS volume and mount/unmount it in the given mount point." + printf 'Usage: %s [-c|--credentials-type ] [-d|--device ] [-m|--mount-point ] [-a|--(no-)ask-pin] [-s|--salt ] [-h|--help] \n' "$0" + printf '\t%s\n' ": Operation to perform (mount|umount)" + printf '\t%s\n' "-c, --credentials-type: Type of the credentials to use (external|embedded) (no default)" + printf '\t%s\n' "-d, --device: Name of the device to create (no default)" + printf '\t%s\n' "-m, --mount-point: Path of the mount point to use (no default)" + printf '\t%s\n' "-a, --ask-pin, --no-ask-pin: Ask for a pin (off by default)" + printf '\t%s\n' "-s, --salt: Salt to use (default: '""')" + printf '\t%s\n' "-h, --help: Prints help" +} + + +parse_commandline() +{ + _positionals_count=0 + while test $# -gt 0 + do + _key="$1" + case "$_key" in + -c|--credentials-type) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_credentials_type="$2" + shift + ;; + --credentials-type=*) + _arg_credentials_type="${_key##--credentials-type=}" + ;; + -c*) + _arg_credentials_type="${_key##-c}" + ;; + -d|--device) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_device="$2" + shift + ;; + --device=*) + _arg_device="${_key##--device=}" + ;; + -d*) + _arg_device="${_key##-d}" + ;; + -m|--mount-point) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_mount_point="$2" + shift + ;; + --mount-point=*) + _arg_mount_point="${_key##--mount-point=}" + ;; + -m*) + _arg_mount_point="${_key##-m}" + ;; + -a|--no-ask-pin|--ask-pin) + _arg_ask_pin="on" + test "${1:0:5}" = "--no-" && _arg_ask_pin="off" + ;; + -a*) + _arg_ask_pin="on" + _next="${_key##-a}" + if test -n "$_next" -a "$_next" != "$_key" + then + { begins_with_short_option "$_next" && shift && set -- "-a" "-${_next}" "$@"; } || die "The short option '$_key' can't be decomposed to ${_key:0:2} and -${_key:2}, because ${_key:0:2} doesn't accept value and '-${_key:2:1}' doesn't correspond to a short option." + fi + ;; + -s|--salt) + test $# -lt 2 && die "Missing value for the optional argument '$_key'." 1 + _arg_salt="$2" + shift + ;; + --salt=*) + _arg_salt="${_key##--salt=}" + ;; + -s*) + _arg_salt="${_key##-s}" + ;; + -h|--help) + print_help + exit 0 + ;; + -h*) + print_help + exit 0 + ;; + *) + _last_positional="$1" + _positionals+=("$_last_positional") + _positionals_count=$((_positionals_count + 1)) + ;; + esac + shift + done +} + + +handle_passed_args_count() +{ + local _required_args_string="'operation'" + test "${_positionals_count}" -ge 1 || _PRINT_HELP=yes die "FATAL ERROR: Not enough positional arguments - we require exactly 1 (namely: $_required_args_string), but got only ${_positionals_count}." 1 + test "${_positionals_count}" -le 1 || _PRINT_HELP=yes die "FATAL ERROR: There were spurious positional arguments --- we expect exactly 1 (namely: $_required_args_string), but got ${_positionals_count} (the last one was: '${_last_positional}')." 1 +} + + +assign_positional_args() +{ + local _positional_name _shift_for=$1 + _positional_names="_arg_operation " + + shift "$_shift_for" + for _positional_name in ${_positional_names} + do + test $# -gt 0 || break + eval "$_positional_name=\${1}" || die "Error during argument parsing, possibly an Argbash bug." 1 + shift + done +} + +parse_commandline "$@" +handle_passed_args_count +assign_positional_args 1 "${_positionals[@]}" + +# OTHER STUFF GENERATED BY Argbash + +### END OF CODE GENERATED BY Argbash (sortof) ### ]) +# [ <-- needed because of Argbash + +if [ -z ${_arg_mount_point} ]; then + die "Missing '--mount-point' argument" +fi + +if [ -z ${_arg_device} ]; then + die "Missing '--device' argument" +fi + +ASK_PIN=${_arg_ask_pin} +OPERATION=${_arg_operation} +DEVICE=${_arg_device} +DEVICE_NAME=$(sed "s|/|_|g" <<< ${DEVICE}) +MOUNT_POINT=${_arg_mount_point} +CREDENTIALS_TYPE=${_arg_credentials_type} +SALT=${_arg_salt} +CONF_FILE_PATH="/etc/fido2luksmounthelper.conf" + +if [ "${OPERATION}" == "mount" ]; then + if [ "${CREDENTIALS_TYPE}" == "external" ]; then + if [ -f ${CONF_FILE_PATH} ]; then + if [ "${ASK_PIN}" == "on" ]; then + read PASSWORD + fi + CREDENTIALS=$(<${CONF_FILE_PATH}) + else + die "The configuration file '${CONF_FILE_PATH}' is missing. Please create it or use embedded credentials." + fi + printf ${PASSWORD} | fido2luks open --salt string:${SALT} --pin --pin-source /dev/stdin ${DEVICE} ${DEVICE_NAME} ${CREDENTIALS} + elif [ "${CREDENTIALS_TYPE}" == "embedded" ]; then + if [ "${ASK_PIN}" == "on" ]; then + read PASSWORD + fi + printf ${PASSWORD} | fido2luks open-token --salt string:${SALT} --pin --pin-source /dev/stdin ${DEVICE} ${DEVICE_NAME} + else + die "Given credential-type '${CREDENTIALS_TYPE}' is invalid. It must be 'external' or 'embedded'" + fi + mount /dev/mapper/${DEVICE_NAME} ${MOUNT_POINT} +elif [ "${OPERATION}" == "umount" ]; then + umount ${MOUNT_POINT} + cryptsetup luksClose ${DEVICE_NAME} +else + die "Given operation '${OPERATION}' is invalid. It must be 'mount' or 'unmount'" +fi + +exit 0 + +# ] <-- needed because of Argbash \ No newline at end of file From b0404f2fc167b51a3d45264af0c3e7e733a40b67 Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 17 Nov 2020 13:05:45 +0100 Subject: [PATCH 3/6] minimum YubiKey firmware version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2691701..361bf41 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This will allow you to unlock your LUKS encrypted disk with an FIDO2 compatible key. -Note: This has only been tested under Fedora 31, [Ubuntu 20.04](initramfs-tools/), [NixOS](https://nixos.org/nixos/manual/#sec-luks-file-systems-fido2) using a Solo Key, Trezor Model T +Note: This has only been tested under Fedora 31, [Ubuntu 20.04](initramfs-tools/), [NixOS](https://nixos.org/nixos/manual/#sec-luks-file-systems-fido2) using a Solo Key, Trezor Model T, YubiKey(fw >= [5.2.3](https://support.yubico.com/hc/en-us/articles/360016649319-YubiKey-5-2-3-Enhancements-to-FIDO-2-Support)) ## Setup From 17ca487b8502524d2c5ff7849e96acce8f2a4395 Mon Sep 17 00:00:00 2001 From: shimunn <41011289+shimunn@users.noreply.github.com> Date: Mon, 8 Feb 2021 15:58:41 +0100 Subject: [PATCH 4/6] Obvious password promt (#29) * obvious password promt * prompt interaction with FIDO device --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cli.rs | 34 +++++++++++++++++++++++----------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a93512..0165240 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "fido2luks" -version = "0.2.15" +version = "0.2.16" dependencies = [ "ctap_hmac", "failure", diff --git a/Cargo.toml b/Cargo.toml index 842253d..4c5d21b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fido2luks" -version = "0.2.15" +version = "0.2.16" authors = ["shimunn "] edition = "2018" diff --git a/src/cli.rs b/src/cli.rs index 629b5b9..5b248f4 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -71,6 +71,12 @@ pub fn parse_cmdline() -> Args { Args::from_args() } +pub fn prompt_interaction(interactive: bool) { + if interactive { + println!("Authorize using your FIDO device"); + } +} + pub fn run_cli() -> Fido2LuksResult<()> { let mut stdout = io::stdout(); let args = parse_cmdline(); @@ -109,6 +115,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { } else { secret.salt.obtain_sha256(&secret.password_helper) }?; + prompt_interaction(interactive); let (secret, _cred) = derive_secret( credentials.ids.0.as_slice(), &salt, @@ -164,23 +171,27 @@ pub fn run_cli() -> Fido2LuksResult<()> { } => Ok((util::read_keyfile(file)?, None)), OtherSecret { fido_device: true, .. - } => Ok(derive_secret( - &credentials.ids.0, - &salt(salt_q, verify)?, - authenticator.await_time, - pin.as_deref(), - ) - .map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?), + } => { + prompt_interaction(interactive); + Ok(derive_secret( + &credentials.ids.0, + &salt(salt_q, verify)?, + authenticator.await_time, + pin.as_deref(), + ) + .map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?) + } _ => Ok(( util::read_password(salt_q, verify)?.as_bytes().to_vec(), None, )), } }; - let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> { + let secret = |q: &str, verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> { + prompt_interaction(interactive); derive_secret( &credentials.ids.0, - &salt("Password", verify)?, + &salt(q, verify)?, authenticator.await_time, pin.as_deref(), ) @@ -190,7 +201,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { match &args.command { Command::AddKey { exclusive, .. } => { let (existing_secret, _) = other_secret("Current password", false)?; - let (new_secret, cred) = secret(true)?; + let (new_secret, cred) = secret("Password to be added", true)?; let added_slot = luks_dev.add_key( &new_secret, &existing_secret[..], @@ -215,7 +226,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { Ok(()) } Command::ReplaceKey { add_password, .. } => { - let (existing_secret, _) = secret(false)?; + let (existing_secret, _) = secret("Current password", false)?; let (replacement_secret, cred) = other_secret("Replacement password", true)?; let slot = if *add_password { luks_dev.add_key( @@ -274,6 +285,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { // Cow shouldn't be necessary let secret = |credentials: Cow<'_, Vec>| { + prompt_interaction(interactive); derive_secret( credentials.as_ref(), &salt("Password", false)?, From b3495c45f3b9666d2e25dfbd620c81947a4249f4 Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 8 Feb 2021 16:05:39 +0100 Subject: [PATCH 5/6] add nix flake --- .gitignore | 4 +++- flake.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/.gitignore b/.gitignore index 057a1eb..52533a2 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ fido2luks.bash fido2luks.elv fido2luks.fish -fido2luks.zsh \ No newline at end of file +fido2luks.zsh +result +result-* diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..71f9fe9 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "naersk": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1612192764, + "narHash": "sha256-7EnLtZQWP6511G1ZPA7FmJlqAr3hWsAYb24tvTvJ/ec=", + "owner": "nmattia", + "repo": "naersk", + "rev": "6e149bfd726a8ebefa415f2d713ba6d942435abd", + "type": "github" + }, + "original": { + "owner": "nmattia", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1611910458, + "narHash": "sha256-//j54S14v9lp3YKizS1WZW3WKwLjGTzvwhHfUAaRBPQ=", + "path": "/nix/store/z5g10k571cc5q9yvr0bafzswp0ggawjw-source", + "rev": "6e7f25001fe6874f7ae271891f709bbf50a22c45", + "type": "path" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs", + "utils": "utils" + } + }, + "utils": { + "locked": { + "lastModified": 1610051610, + "narHash": "sha256-U9rPz/usA1/Aohhk7Cmc2gBrEEKRzcW4nwPWMPwja4Y=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3982c9903e93927c2164caa727cd3f6a0e6d14cc", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..cc6df56 --- /dev/null +++ b/flake.nix @@ -0,0 +1,61 @@ +{ + description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"; + + inputs = { + utils.url = "github:numtide/flake-utils"; + naersk = { + url = "github:nmattia/naersk"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, utils, naersk }: + let + root = ./.; + pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name; + forPkgs = pkgs: + let + naersk-lib = naersk.lib."${pkgs.system}"; + buildInputs = with pkgs; [ cryptsetup ]; + LIBCLANG_PATH = "${pkgs.clang.cc.lib}/lib"; + nativeBuildInputs = with pkgs; [ + pkgconfig + clang + ]; + in + rec { + # `nix build` + packages.${pname} = naersk-lib.buildPackage { + inherit pname root buildInputs nativeBuildInputs LIBCLANG_PATH; + }; + defaultPackage = packages.${pname}; + + # `nix run` + apps.${pname} = utils.lib.mkApp { + drv = packages.${pname}; + }; + defaultApp = apps.${pname}; + + # `nix flake check` + checks = { + fmt = with pkgs; runCommandLocal "${pname}-fmt" { buildInputs = [ cargo rustfmt nixpkgs-fmt ]; } '' + cd ${root} + cargo fmt -- --check + nixpkgs-fmt --check *.nix + touch $out + ''; + }; + + # `nix develop` + devShell = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ rustc cargo rustfmt nixpkgs-fmt ] ++ nativeBuildInputs; + inherit buildInputs LIBCLANG_PATH; + }; + }; + forSystem = system: forPkgs nixpkgs.legacyPackages."${system}"; + in + (utils.lib.eachSystem [ "aarch64-linux" "i686-linux" "x86_64-linux" ] forSystem) // { + overlay = final: prev: (forPkgs final).packages; + }; + +} From 7e6b33ae7f475cb637a627fd00a48ed19199847e Mon Sep 17 00:00:00 2001 From: shimunn <41011289+shimunn@users.noreply.github.com> Date: Sun, 28 Feb 2021 12:56:57 +0100 Subject: [PATCH 6/6] Theory of operation (#30) --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 361bf41..7cafda0 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,36 @@ sudo -E fido2luks -i replace-key /dev/disk/by-uuid/ sudo rm -rf /usr/lib/dracut/modules.d/96luks-2fa /etc/dracut.conf.d/luks-2fa.conf /etc/fido2luks.conf ``` +## Theory of operation + +fido2luks builds on two basic building blocks, LUKS as an abstraction over linux disk encryption and and the FIDO2 extension [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.0-rd-20180702/fido-client-to-authenticator-protocol-v2.0-rd-20180702.html#sctn-hmac-secret-extension). +The `hmac-secret` extension allows for an secret to be dervied on the FIDO2 device from two inputs, the user supplied salt/password/keyfile and another secret contained within the FID2 device. The output of the `hmac-secret` function will then be used to decrypt the LUKS header which in turn is used to decrypt the disk. +``` + + +-------------------------------------------------------------------------------+ + | | + | +-----------------------------------------+ | + | | FIDO2 device | | + | | | | + | | | | ++-------+--------+ +------+ | +---------------+ | | +------------------------+ +| Salt/Password +-> |sha256+------------------------> | | | v | LUKS header | ++----------------+ +------+ | | | | | | +---------------+ + | | | | +--------+ +------------------------+--------> |Disk master key| + | | sha256_hmac +---------> | sha256 +-------> | Keyslot 1 | +---------------+ ++----------------+ | +----------+ | | | +--------+ +------------------------+ +| FIDO credential+---------------> |Credential| +----> | | | | Keyslot 2 | ++----------------+ | |secret | | | | +------------------------+ + | +----------+ +---------------+ | + | | + | | + +-----------------------------------------+ + +``` +Since all these components build upon each other losing or damaging just one of them will render the disk undecryptable, it's threfore of paramount importance to backup the LUKS header and ideally set an backup password +or utilise more than one FIDO2 device. Each additional credential and password combination will require it's own LUKS keyslot since the credential secret is randomly generated for each new credential and will thus result +in a completly different secret. + ## License Licensed under