Compare commits

..

16 Commits

Author SHA1 Message Date
ddaf3f9264 err
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-09-29 20:32:00 +02:00
349807a6c4 hint pin input
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2020-09-29 19:41:20 +02:00
4e3d799179 fix test
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-29 19:36:24 +02:00
197d9f511c wrap all error cases
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-29 19:31:48 +02:00
11ac32d3f1 print error msg
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-29 19:24:33 +02:00
f6627d887b proper error messages
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-29 19:21:44 +02:00
fbbf606631 sudo
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-29 18:28:22 +02:00
7879d64e3a Merge branch 'pam_mod' of git.shimun.net:shimun/fido2luks into pam_mod
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-28 18:06:10 +02:00
31ee2dcbe7 libpam-dev
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2020-09-28 17:27:13 +02:00
79849df284 add pam dependency 2020-09-25 01:38:17 +02:00
f2a8e412ac salt
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-25 01:21:34 +02:00
985f6f664b include password
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-25 01:19:47 +02:00
d4094b8a6a dbg
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2020-09-25 01:00:59 +02:00
e524996693 add
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-25 00:52:31 +02:00
d6f6c7c218 ext
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2020-09-25 00:24:46 +02:00
63f29249d3 pam module
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-25 00:23:36 +02:00
11 changed files with 258 additions and 320 deletions

View File

@@ -12,7 +12,7 @@ steps:
environment: environment:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
commands: commands:
- apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev - apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev libpam-dev
- cargo test --locked - cargo test --locked
- name: publish - name: publish
image: ubuntu:focal image: ubuntu:focal
@@ -22,7 +22,7 @@ steps:
from_secret: cargo_tkn from_secret: cargo_tkn
commands: commands:
- grep -E 'version ?= ?"${DRONE_TAG}"' -i Cargo.toml || (printf "incorrect crate/tag version" && exit 1) - grep -E 'version ?= ?"${DRONE_TAG}"' -i Cargo.toml || (printf "incorrect crate/tag version" && exit 1)
- apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev - apt update && apt install -y cargo libkeyutils-dev libclang-dev clang pkg-config libcryptsetup-dev libpam-dev
- cargo package --all-features - cargo package --all-features
- cargo publish --all-features - cargo publish --all-features
when: when:

18
Cargo.lock generated
View File

@@ -383,12 +383,14 @@ dependencies = [
"failure", "failure",
"hex", "hex",
"libcryptsetup-rs", "libcryptsetup-rs",
"pamsm",
"ring", "ring",
"rpassword", "rpassword",
"serde", "serde",
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"structopt", "structopt",
"sudo",
] ]
[[package]] [[package]]
@@ -596,6 +598,12 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
[[package]]
name = "pamsm"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3580ed2ebe075c74db583233318abf4b07bc8d9a40c7691d0ae9c186e19e43dd"
[[package]] [[package]]
name = "peeking_take_while" name = "peeking_take_while"
version = "0.1.2" version = "0.1.2"
@@ -980,6 +988,16 @@ dependencies = [
"syn 1.0.40", "syn 1.0.40",
] ]
[[package]]
name = "sudo"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a88e74edf206f281aff2820aa2066c781331044c770626dcafe19491f214e05"
dependencies = [
"libc",
"log",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "0.15.44" version = "0.15.44"

View File

@@ -24,6 +24,8 @@ libcryptsetup-rs = "0.4.1"
serde_json = "1.0.51" serde_json = "1.0.51"
serde_derive = "1.0.106" serde_derive = "1.0.106"
serde = "1.0.106" serde = "1.0.106"
pamsm = { version = "0.4.1", features = ["libpam"] }
sudo = "0.5.0"
[build-dependencies] [build-dependencies]
ctap_hmac = { version="0.4.2", features = ["request_multiple"] } ctap_hmac = { version="0.4.2", features = ["request_multiple"] }
@@ -32,6 +34,7 @@ ring = "0.13.5"
failure = "0.1.5" failure = "0.1.5"
rpassword = "4.0.1" rpassword = "4.0.1"
libcryptsetup-rs = "0.4.1" libcryptsetup-rs = "0.4.1"
pamsm = { version = "0.4.1", features = ["libpam"] }
structopt = "0.3.2" structopt = "0.3.2"
[profile.release] [profile.release]
@@ -41,14 +44,23 @@ panic = 'abort'
incremental = false incremental = false
overflow-checks = false overflow-checks = false
[[bin]]
name = "fido2luks"
path = "src/main.rs"
[lib]
name = "fido2luks_pam"
path = "src/lib.rs"
crate-type = ["cdylib"]
[package.metadata.deb] [package.metadata.deb]
depends = "$auto, cryptsetup" depends = "$auto, cryptsetup"
build-depends = "libclang-dev, libcryptsetup-dev" build-depends = "libclang-dev, libcryptsetup-dev, libpam-dev"
extended-description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator" extended-description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
assets = [ assets = [
["target/release/fido2luks", "usr/bin/", "755"], ["target/release/fido2luks", "usr/bin/", "755"],
["target/release/libfido2luks_pam.so", "usr/lib/x86_64-linux-gnu/security/pam_fido2luks.so", "755"],
["fido2luks.bash", "usr/share/bash-completion/completions/fido2luks", "644"], ["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/keyscript.sh", "/lib/cryptsetup/scripts/fido2luks", "755" ],
["initramfs-tools/hook/fido2luks.sh", "etc/initramfs-tools/hooks/", "755" ], ["initramfs-tools/hook/fido2luks.sh", "etc/initramfs-tools/hooks/", "755" ],
["initramfs-tools/fido2luks.conf", "etc/", "644"], ["initramfs-tools/fido2luks.conf", "etc/", "644"],

View File

@@ -17,13 +17,9 @@ pkgver() {
build() { build() {
cargo build --release --locked --all-features --target-dir=target cargo build --release --locked --all-features --target-dir=target
target/release/${pkgname} completions target
} }
package() { package() {
install -Dm 755 target/release/${pkgname} -t "${pkgdir}/usr/bin" 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"
install -Dm 644 target/fido2luks.bash "${pkgdir}/usr/share/bash-completion/completions/fido2luks"
install -Dm 755 ../initcpio/hook.sh "${pkgdir}/usr/lib/initcpio/install/fido2luks"
install -Dm 755 ../initcpio/hook.sh "${pkgdir}/usr/lib/initcpio/hooks/fido2luks"
} }

View File

@@ -2,7 +2,7 @@
This will allow you to unlock your LUKS encrypted disk with an FIDO2 compatible key. 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, YubiKey(fw >= [5.2.3](https://support.yubico.com/hc/en-us/articles/360016649319-YubiKey-5-2-3-Enhancements-to-FIDO-2-Support)) 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
## Setup ## Setup

View File

@@ -1,74 +0,0 @@
#!/bin/bash
set -ax
exit_with() {
echo "$1" >&2
exit 1
}
validate() {
[ ! -e /etc/fido2luks.conf ] && exit_with "/etc/fido2luks.conf does not exist! Please configure first"
. /etc/fido2luks.conf
[ ! -e "$FIDO2LUKS_DEVICE" ] && exit_with "FIDO2LUKS_DEVICE='$FIDO2LUKS_DEVICE' does not exist!"
[ -z "$FIDO2LUKS_CREDENTIAL_ID" ] && exit_with "FIDO2LUKS_CREDENTIAL_ID must be set!"
[ -z "$FIDO2LUKS_MAPPER_NAME" ] && exit_with "FIDO2LUKS_MAPPER_NAME must be set!"
}
build() {
local mod
add_binary "cryptsetup"
add_module "dm-crypt"
add_module "dm-integrity"
if [[ $CRYPTO_MODULES ]]; then
for mod in $CRYPTO_MODULES; do
add_module "$mod"
done
else
add_all_modules "/crypto/"
fi
add_binary "dmsetup"
add_file "/usr/lib/udev/rules.d/10-dm.rules"
add_file "/usr/lib/udev/rules.d/13-dm-disk.rules"
add_file "/usr/lib/udev/rules.d/95-dm-notify.rules"
add_file "/usr/lib/initcpio/udev/11-dm-initramfs.rules" "/usr/lib/udev/rules.d/11-dm-initramfs.rules"
add_systemd_unit "systemd-ask-password-console.path"
add_systemd_unit "systemd-ask-password-console.service"
# cryptsetup calls pthread_create(), which dlopen()s libgcc_s.so.1
add_binary "/usr/lib/libgcc_s.so.1"
# add mkswap for creating swap space on the fly (see 'swap' in crypttab(5))
add_binary "mkswap"
[[ -f /etc/crypttab.initramfs ]] && add_file "/etc/crypttab.initramfs" "/etc/crypttab"
validate
add_file "/etc/fido2luks.conf" "/etc/fido2luks.conf"
add_binary "fido2luks"
add_runscipt
}
run_hook() {
modprobe -a -q dm-crypt
. /etc/fido2luks.conf
if [ -z "$FIDO2LUKS_PASSWORD_HELPER" ]; then
export FIDO2LUKS_PASSWORD_HELPER="systemd-ask-password 'FIDO2 password salt for $FIDO2LUKS_DEVICE'"
fi
fido2luks open
}
help() {
cat <<HELPEOF
This hook allows for an encrypted root device with systemd initramfs.
See the manpage of systemd-cryptsetup-generator(8) for available kernel
command line options. Alternatively, if the file /etc/crypttab.initramfs
exists, it will be added to the initramfs as /etc/crypttab. See the
crypttab(5) manpage for more information on crypttab syntax.
HELPEOF
}
# vim: set ft=sh ts=4 sw=4 et:

View File

@@ -1,14 +0,0 @@
#!/bin/sh
set -a
. /etc/fido2luks.conf
if [ -z "$FIDO2LUKS_PASSWORD_HELPER" ]; then
MSG="FIDO2 password salt for $CRYPTTAB_NAME"
export FIDO2LUKS_PASSWORD_HELPER="systemd-ask-password '$MSG'"
fi
if [ "$FIDO2LUKS_USE_TOKEN" -eq 1 ]; then
export FIDO2LUKS_CREDENTIAL_ID="$FIDO2LUKS_CREDENTIAL_ID,$(fido2luks token list --csv $CRYPTTAB_SOURCE)"
fi
fido2luks print-secret --bin

View File

@@ -1,220 +0,0 @@
#!/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 <arg>] [-d|--device <arg>] [-m|--mount-point <arg>] [-a|--(no-)ask-pin] [-s|--salt <arg>] [-h|--help] <operation>\n' "$0"
printf '\t%s\n' "<operation>: 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

View File

@@ -25,7 +25,7 @@ fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult<String> {
if let Some(src) = ap.pin_source.as_ref() { if let Some(src) = ap.pin_source.as_ref() {
let mut pin = String::new(); let mut pin = String::new();
File::open(src)?.read_to_string(&mut pin)?; File::open(src)?.read_to_string(&mut pin)?;
Ok(pin.trim_end_matches("\n").to_string()) //remove trailing newline Ok(pin)
} else { } else {
util::read_password("Authenticator PIN", false) util::read_password("Authenticator PIN", false)
} }

View File

@@ -1,10 +1,10 @@
use ctap::FidoError; use ctap::FidoError;
use libcryptsetup_rs::LibcryptErr; use libcryptsetup_rs::LibcryptErr;
use pamsm::PamError;
use std::io; use std::io;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use Fido2LuksError::*; use Fido2LuksError::*;
pub type Fido2LuksResult<T> = Result<T, Fido2LuksError>; pub type Fido2LuksResult<T> = Result<T, Fido2LuksError>;
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
@@ -29,6 +29,10 @@ pub enum Fido2LuksError {
WrongSecret, WrongSecret,
#[fail(display = "not an utf8 string")] #[fail(display = "not an utf8 string")]
StringEncodingError { cause: FromUtf8Error }, StringEncodingError { cause: FromUtf8Error },
#[fail(display = "elevated privileges required")]
MissingPrivileges,
#[fail(display = "{}", cause)]
Configuration { cause: ConfigurationError },
} }
impl Fido2LuksError { impl Fido2LuksError {
@@ -50,6 +54,26 @@ pub enum AskPassError {
IO(io::Error), IO(io::Error),
#[fail(display = "provided passwords don't match")] #[fail(display = "provided passwords don't match")]
Mismatch, Mismatch,
#[fail(display = "unable to retrieve password: {}", _0)]
Pam(PamError),
}
impl From<PamError> for AskPassError {
fn from(e: PamError) -> Self {
AskPassError::Pam(e)
}
}
impl From<io::Error> for AskPassError {
fn from(e: io::Error) -> Self {
AskPassError::IO(e)
}
}
impl From<AskPassError> for Fido2LuksError {
fn from(cause: AskPassError) -> Self {
Fido2LuksError::AskPassError { cause }
}
} }
#[derive(Debug, Fail)] #[derive(Debug, Fail)]
@@ -112,3 +136,16 @@ impl From<FromUtf8Error> for Fido2LuksError {
StringEncodingError { cause: e } StringEncodingError { cause: e }
} }
} }
#[derive(Debug, Fail)]
pub enum ConfigurationError {
#[fail(display = "config is missing some values: {:?}", _0)]
Missing(Vec<String>),
#[fail(display = "config attribute {} contains an invalid value: {}", _1, _0)]
InvalidValue(String, String),
}
impl From<ConfigurationError> for Fido2LuksError {
fn from(cause: ConfigurationError) -> Fido2LuksError {
Fido2LuksError::Configuration { cause }
}
}

183
src/lib.rs Normal file
View File

@@ -0,0 +1,183 @@
#[macro_use]
extern crate failure;
extern crate ctap_hmac as ctap;
#[macro_use]
extern crate serde_derive;
use crate::cli_args::{CommaSeparated, HexEncoded};
use crate::device::*;
use crate::error::*;
use crate::luks::*;
use ctap::FidoCredential;
use failure::_core::time::Duration;
use pamsm::PamLibExt;
use pamsm::*;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::str::FromStr;
use sudo::{self, RunningAs};
pub mod cli_args;
pub mod device;
pub mod error;
pub mod luks;
pub mod util;
struct PamFido2Luks;
impl PamFido2Luks {
fn open(
&self,
user: String,
mut password: impl FnMut(&str) -> PamResult<String>,
args: Vec<String>,
) -> Fido2LuksResult<()> {
let args: HashMap<String, String> = args
.into_iter()
.filter_map(|arg| {
let mut parts = arg.split("=");
parts
.by_ref()
.next()
.map(|key| (key.to_string(), parts.collect::<Vec<_>>().join("=")))
})
.collect();
let credentials = match args.get("credentials").map(|creds| {
<CommaSeparated<String>>::from_str(creds)
.map(|cs| cs.0)
.map_err(|_| ConfigurationError::InvalidValue("credentials".into(), creds.into()))
}) {
Some(creds) => creds?,
_ => Vec::new(),
};
let pin = args.get("pin");
let pin_prefix = args
.get("pin-prefix")
.map(|p| p.parse::<bool>().unwrap_or_default())
.unwrap_or_default();
let device = args
.get("device")
.map(|device| device.replace("%user%", user.as_str()));
let name = args
.get("name")
.map(|name| name.replace("%user%", user.as_str()));
let mut attempts = args
.get("attempts")
.and_then(|a| a.parse::<usize>().ok())
.unwrap_or(3);
if let (Some(device), Some(name)) = (device, name) {
if !Path::new(&device).exists() || Path::new(&format!("/dev/mapper/{}", name)).exists()
{
return Ok(());
}
// root required to mount luks
match sudo::check() {
RunningAs::User => return Err(Fido2LuksError::MissingPrivileges),
_ => {
sudo::escalate_if_needed().map_err(|_| Fido2LuksError::MissingPrivileges)?;
}
}
let mut device = LuksDevice::load(device)?;
let mut additional_credentials: HashSet<String> = HashSet::new();
if device.is_luks2()? {
for token in device.tokens()? {
let (_, token) = token?;
additional_credentials.extend(token.credential.into_iter());
}
}
let credentials: Vec<FidoCredential> = credentials
.into_iter()
.chain(additional_credentials.into_iter())
.map(|cred| HexEncoded::from_str(cred.as_str()))
.map(|cred| FidoCredential {
id: cred.unwrap().0,
public_key: None,
})
.collect();
let credentials: Vec<&FidoCredential> = credentials.iter().collect();
if !credentials.is_empty() {
loop {
let (pin, pass) = if pin_prefix {
let password = password("PIN + FIDO2 salt (pin:password):")
.map_err(|e| Fido2LuksError::AskPassError { cause: e.into() })?;
let mut parts = password.split(":");
(
parts.next().map(|p| p.to_string()).or(pin.cloned()),
parts.collect::<Vec<_>>().join(":"),
)
} else {
(
pin.cloned(),
password("FIDO2 salt: ")
.map_err(|e| Fido2LuksError::AskPassError { cause: e.into() })?,
)
};
let salt = util::sha256(&[pass.as_bytes()]);
let secret = util::sha256(&[
&salt,
&perform_challenge(
&credentials[..],
&salt,
Duration::from_secs(15),
pin.as_ref().map(String::as_str),
)?
.0[..],
]);
match device.activate(name.as_str(), &secret[..], None) {
Ok(_) => return Ok(()),
_ if attempts > 0 => {
attempts -= 1;
continue;
}
Err(e) => break Err(e),
}
}
} else {
Err(ConfigurationError::Missing(vec!["credentials".into()]).into())
}
} else {
Ok(())
}
}
}
impl PamServiceModule for PamFido2Luks {
fn authenticate(pamh: Pam, _flag: PamFlag, args: Vec<String>) -> PamError {
let perfrom_authenticate = move || -> Fido2LuksResult<()> {
let user = match pamh.get_cached_user() {
Err(e) => Err(AskPassError::Pam(e))?,
Ok(p) => p.map(|s| s.to_str().map(str::to_string).unwrap()),
};
let mut password = match pamh.get_authtok(None) {
Err(e) => Err(AskPassError::Pam(e))?,
Ok(p) => p.map(|s| s.to_str().map(str::to_string).unwrap()),
};
if let Some(user) = user {
PamFido2Luks.open(
user,
move |q: &str| match password.take() {
Some(pass) => Ok(pass),
None => pamh
.conv(Some(q), PamMsgStyle::PROMPT_ECHO_OFF)
.map(|s| s.map(|s| s.to_str().unwrap()).unwrap_or("").to_string()),
},
args,
)
} else {
Err(AskPassError::Pam(PamError::AUTH_ERR))?
}
};
match perfrom_authenticate() {
Ok(_) => PamError::SUCCESS,
Err(e) => {
eprintln!("{}", e);
PamError::AUTH_ERR
}
}
}
}
pam_module!(PamFido2Luks);