Compare commits

...

17 Commits

Author SHA1 Message Date
dc55edff4d obvious password promt
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-07 11:10:46 +01:00
b0404f2fc1 minimum YubiKey firmware version
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2020-11-17 13:05:45 +01:00
shimunn
de21e3ef8d Merge pull request #21 from aacebedo/master
Added an helper script to be used with pam_mount
2020-11-01 21:18:13 +01:00
Alexandre ACEBEDO
8a7b3addbb Added an helper script to be used with pam_mount 2020-11-01 18:21:04 +01:00
e7e44cd61b fix test
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-13 14:17:22 +02:00
0ec859f4a6 remove trailing newline from pin
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-25 12:40:34 +02:00
55bae4161e add credentials situated in the luks header to credential list
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-19 18:23:21 +02:00
086c1a0594 file path must be relative to src
Some checks failed
continuous-integration/drone/push Build is failing
2020-09-05 19:35:36 +02:00
c2e38eb06f generate shell completions during build
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/tag Build is failing
2020-09-05 18:52:07 +02:00
03ef5721e0 bump version
All checks were successful
continuous-integration/drone/tag Build is passing
continuous-integration/drone/push Build is passing
2020-09-03 14:56:36 +02:00
008e644024 auto detect current version 2020-09-03 14:56:22 +02:00
e1f762ddc9 add subcommand to generate bash completions
All checks were successful
continuous-integration/drone/push Build is passing
2020-09-03 14:45:52 +02:00
Saravanan Palanisamy
2266754a95 create PKGBUILD file for archlinux package (#17)
Some checks reported errors
continuous-integration/drone/push Build encountered an error
* create PKGBUILD file

* use build & install method

* add package dependencies
2020-09-02 14:14:40 +02:00
8811cff6d1 0.2.12
All checks were successful
continuous-integration/drone/tag Build is passing
2020-08-31 00:04:24 +02:00
99787b614c Merge branch 'pin_source' into master
Some checks reported errors
continuous-integration/drone/push Build encountered an error
2020-08-31 00:00:42 +02:00
ee28f87148 always print the full error message
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-30 17:09:57 +02:00
196356fe3b structopt does not allow for flags to be linked to env atm 2020-08-25 21:47:25 +02:00
13 changed files with 740 additions and 397 deletions

4
.gitignore vendored
View File

@@ -2,3 +2,7 @@
**/*.rs.bk
.idea/
*.iml
fido2luks.bash
fido2luks.elv
fido2luks.fish
fido2luks.zsh

113
Cargo.lock generated
View File

@@ -52,9 +52,9 @@ checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "autocfg"
version = "1.0.0"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "backtrace"
@@ -72,9 +72,9 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.54.1"
version = "0.54.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4d49b80beb70d76cdac92f5681e666f9a697c737c4f4117a67229a0386dc736"
checksum = "66c0bb6167449588ff70803f4127f0684f9063097eca5016f37eb52b92c2cf36"
dependencies = [
"bitflags",
"cexpr",
@@ -86,7 +86,7 @@ dependencies = [
"lazycell",
"log",
"peeking_take_while",
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"regex",
"rustc-hash",
@@ -118,9 +118,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.58"
version = "1.0.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518"
checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381"
[[package]]
name = "cexpr"
@@ -150,9 +150,9 @@ dependencies = [
[[package]]
name = "clap"
version = "2.33.2"
version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10040cdf04294b565d9e0319955430099ec3813a64c952b86a41200ad714ae48"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"atty",
@@ -188,12 +188,12 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6"
checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
dependencies = [
"cfg-if",
"crossbeam-utils",
"maybe-uninit",
]
[[package]]
@@ -213,7 +213,7 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [
"autocfg 1.0.0",
"autocfg 1.0.1",
"cfg-if",
"crossbeam-utils",
"lazy_static",
@@ -239,7 +239,7 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg 1.0.0",
"autocfg 1.0.1",
"cfg-if",
"lazy_static",
]
@@ -292,10 +292,10 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"strsim 0.9.3",
"syn 1.0.38",
"syn 1.0.40",
]
[[package]]
@@ -306,7 +306,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
]
[[package]]
@@ -317,9 +317,9 @@ checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
"darling",
"derive_builder_core",
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
]
[[package]]
@@ -329,9 +329,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
"darling",
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
]
[[package]]
@@ -369,15 +369,15 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
"synstructure",
]
[[package]]
name = "fido2luks"
version = "0.2.11"
version = "0.2.16"
dependencies = [
"ctap_hmac",
"failure",
@@ -474,15 +474,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.2.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.74"
version = "0.2.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10"
checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3"
[[package]]
name = "libcryptsetup-rs"
@@ -548,14 +548,14 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
dependencies = [
"autocfg 1.0.0",
"autocfg 1.0.1",
]
[[package]]
name = "miniz_oxide"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722"
dependencies = [
"adler",
]
@@ -587,7 +587,7 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
"autocfg 1.0.0",
"autocfg 1.0.1",
]
[[package]]
@@ -615,9 +615,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
"version_check",
]
@@ -627,7 +627,7 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"version_check",
]
@@ -643,9 +643,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.19"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
checksum = "175c513d55719db99da20232b06cda8bab6b83ec2d04e3283edf0213c37c1a29"
dependencies = [
"unicode-xid 0.2.1",
]
@@ -671,7 +671,7 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
]
[[package]]
@@ -922,9 +922,9 @@ version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48"
dependencies = [
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
]
[[package]]
@@ -958,9 +958,9 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "structopt"
version = "0.3.16"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5472fb24d7e80ae84a7801b7978f95a19ec32cb1876faea59ab711eb901976"
checksum = "6cc388d94ffabf39b5ed5fadddc40147cb21e605f53db6f8f36a625d27489ac5"
dependencies = [
"clap",
"lazy_static",
@@ -969,15 +969,15 @@ dependencies = [
[[package]]
name = "structopt-derive"
version = "0.4.9"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0eb37335aeeebe51be42e2dc07f031163fbabfa6ac67d7ea68b5c2f68d5f99"
checksum = "5e2513111825077552a6751dfad9e11ce0fba07d7276a3943a037d7e93e64c5f"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
]
[[package]]
@@ -993,11 +993,11 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.38"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4"
checksum = "963f7d3cc59b59b9325165add223142bbf1df27655d07789f109896d353d8350"
dependencies = [
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"unicode-xid 0.2.1",
]
@@ -1008,9 +1008,9 @@ version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2 1.0.19",
"proc-macro2 1.0.20",
"quote 1.0.7",
"syn 1.0.38",
"syn 1.0.40",
"unicode-xid 0.2.1",
]
@@ -1043,11 +1043,12 @@ dependencies = [
[[package]]
name = "time"
version = "0.1.43"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
@@ -1102,6 +1103,12 @@ version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "which"
version = "3.1.1"

View File

@@ -1,6 +1,6 @@
[package]
name = "fido2luks"
version = "0.2.11"
version = "0.2.16"
authors = ["shimunn <shimun@shimun.net>"]
edition = "2018"
@@ -25,6 +25,15 @@ serde_json = "1.0.51"
serde_derive = "1.0.106"
serde = "1.0.106"
[build-dependencies]
ctap_hmac = { version="0.4.2", features = ["request_multiple"] }
hex = "0.3.2"
ring = "0.13.5"
failure = "0.1.5"
rpassword = "4.0.1"
libcryptsetup-rs = "0.4.1"
structopt = "0.3.2"
[profile.release]
lto = true
opt-level = 'z'
@@ -38,6 +47,8 @@ build-depends = "libclang-dev, libcryptsetup-dev"
extended-description = "Decrypt your LUKS partition using a FIDO2 compatible authenticator"
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"],

26
PKGBUILD Normal file
View File

@@ -0,0 +1,26 @@
# Maintainer: shimunn <shimun@shimun.net>
pkgname=fido2luks
pkgver=0.2.12
pkgrel=1
makedepends=('rust' 'cargo' 'cryptsetup' 'clang')
depends=('cryptsetup')
arch=('i686' 'x86_64' 'armv6h' 'armv7h')
pkgdesc="Decrypt your LUKS partition using a FIDO2 compatible authenticator"
url="https://github.com/shimunn/fido2luks"
license=('MPL-2.0')
pkgver() {
# Use tag version if possible otherwise concat project version and git ref
git describe --exact-match --tags HEAD 2> /dev/null || \
echo "$(cargo pkgid | cut -d'#' -f2).$(git describe --always)"
}
build() {
cargo build --release --locked --all-features --target-dir=target
}
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"
}

View File

@@ -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

24
build.rs Normal file
View File

@@ -0,0 +1,24 @@
#![allow(warnings)]
#[macro_use]
extern crate failure;
extern crate ctap_hmac as ctap;
#[path = "src/cli_args/mod.rs"]
mod cli_args;
#[path = "src/error.rs"]
mod error;
#[path = "src/util.rs"]
mod util;
use cli_args::Args;
use std::env;
use std::str::FromStr;
use structopt::clap::Shell;
use structopt::StructOpt;
fn main() {
// generate completion scripts, zsh does panic for some reason
for shell in Shell::variants().iter().filter(|shell| **shell != "zsh") {
Args::clap().gen_completions(env!("CARGO_PKG_NAME"), Shell::from_str(shell).unwrap(), ".");
}
}

View File

@@ -7,4 +7,8 @@ if [ -z "$FIDO2LUKS_PASSWORD_HELPER" ]; then
export FIDO2LUKS_PASSWORD_HELPER="plymouth ask-for-password --prompt '$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

220
pam_mount/fido2luksmounthelper.sh Executable file
View File

@@ -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 <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

@@ -1,151 +1,36 @@
use crate::error::*;
use crate::luks::{Fido2LuksToken, LuksDevice};
use crate::util::sha256;
use crate::*;
use cli_args::*;
use structopt::clap::Shell;
use structopt::StructOpt;
use ctap::{FidoCredential, FidoErrorKind};
use failure::_core::fmt::{Display, Error, Formatter};
use failure::_core::str::FromStr;
use failure::_core::time::Duration;
use std::io::{Read, Write};
use std::process::exit;
use std::thread;
use crate::luks::{Fido2LuksToken, LuksDevice};
use crate::util::sha256;
use std::io::{Read, Write};
use std::str::FromStr;
use std::thread;
use std::time::Duration;
use std::borrow::Cow;
use std::collections::HashSet;
use std::fs::File;
use std::time::SystemTime;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct HexEncoded(pub Vec<u8>);
pub use cli_args::Args;
impl Display for HexEncoded {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str(&hex::encode(&self.0))
fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult<String> {
if let Some(src) = ap.pin_source.as_ref() {
let mut pin = String::new();
File::open(src)?.read_to_string(&mut pin)?;
Ok(pin.trim_end_matches("\n").to_string()) //remove trailing newline
} else {
util::read_password("Authenticator PIN", false)
}
}
impl AsRef<[u8]> for HexEncoded {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl FromStr for HexEncoded {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(HexEncoded(hex::decode(s)?))
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct CommaSeparated<T: FromStr + Display>(pub Vec<T>);
impl<T: Display + FromStr> Display for CommaSeparated<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for i in &self.0 {
f.write_str(&i.to_string())?;
f.write_str(",")?;
}
Ok(())
}
}
impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(CommaSeparated(
s.split(',')
.map(|part| <T as FromStr>::from_str(part))
.collect::<Result<Vec<_>, _>>()?,
))
}
}
#[derive(Debug, StructOpt)]
pub struct Credentials {
/// FIDO credential ids, separated by ',' generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub ids: CommaSeparated<HexEncoded>,
}
#[derive(Debug, StructOpt)]
pub struct AuthenticatorParameters {
/// Request a PIN to unlock the authenticator
#[structopt(short = "P", long = "pin", env = "FIDO2LUKS_PIN")]
pub pin: bool,
/// Location to read PIN from
#[structopt(long = "pin-source", env = "FIDO2LUKS_PIN_SOURCE")]
pub pin_source: Option<PathBuf>,
/// Await for an authenticator to be connected, timeout after n seconds
#[structopt(
long = "await-dev",
name = "await-dev",
env = "FIDO2LUKS_DEVICE_AWAIT",
default_value = "15"
)]
pub await_time: u64,
}
impl AuthenticatorParameters {
fn read_pin(&self) -> Fido2LuksResult<String> {
if let Some(src) = self.pin_source.as_ref() {
let mut pin = String::new();
File::open(src)?.read_to_string(&mut pin)?;
Ok(pin)
} else {
util::read_password("Authenticator PIN", false)
}
}
}
#[derive(Debug, StructOpt)]
pub struct LuksParameters {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
/// Try to unlock the device using a specifc keyslot, ignore all other slots
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
slot: Option<u32>,
}
#[derive(Debug, StructOpt, Clone)]
pub struct LuksModParameters {
/// Number of milliseconds required to derive the volume decryption key
/// Defaults to 10ms when using an authenticator or the default by cryptsetup when using a password
#[structopt(long = "kdf-time", name = "kdf-time")]
kdf_time: Option<u64>,
}
#[derive(Debug, StructOpt)]
pub struct SecretParameters {
/// Salt for secret generation, defaults to 'ask'
///
/// Options:{n}
/// - ask : Prompt user using password helper{n}
/// - file:<PATH> : Will read <FILE>{n}
/// - string:<STRING> : Will use <STRING>, which will be handled like a password provided to the 'ask' option{n}
#[structopt(
name = "salt",
long = "salt",
env = "FIDO2LUKS_SALT",
default_value = "ask"
)]
pub salt: InputSalt,
/// Script used to obtain passwords, overridden by --interactive flag
#[structopt(
name = "password-helper",
env = "FIDO2LUKS_PASSWORD_HELPER",
default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
)]
pub password_helper: PasswordHelper,
}
fn derive_secret(
credentials: &[HexEncoded],
salt: &[u8; 32],
@@ -182,167 +67,6 @@ fn derive_secret(
Ok((sha256(&[salt, &unsalted[..]]), cred.clone()))
}
#[derive(Debug, StructOpt)]
pub struct Args {
/// Request passwords via Stdin instead of using the password helper
#[structopt(short = "i", long = "interactive")]
pub interactive: bool,
#[structopt(subcommand)]
pub command: Command,
}
#[derive(Debug, StructOpt, Clone)]
pub struct OtherSecret {
/// Use a keyfile instead of a password
#[structopt(short = "d", long = "keyfile", conflicts_with = "fido_device")]
keyfile: Option<PathBuf>,
/// Use another fido device instead of a password
/// Note: this requires for the credential fot the other device to be passed as argument as well
#[structopt(short = "f", long = "fido-device", conflicts_with = "keyfile")]
fido_device: bool,
}
#[derive(Debug, StructOpt)]
pub enum Command {
#[structopt(name = "print-secret")]
PrintSecret {
/// Prints the secret as binary instead of hex encoded
#[structopt(short = "b", long = "bin")]
binary: bool,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
},
/// Adds a generated key to the specified LUKS device
#[structopt(name = "add-key")]
AddKey {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
/// Will wipe all other keys
#[structopt(short = "e", long = "exclusive")]
exclusive: bool,
/// Will add an token to your LUKS 2 header, including the credential id
#[structopt(short = "t", long = "token")]
token: bool,
#[structopt(flatten)]
existing_secret: OtherSecret,
#[structopt(flatten)]
luks_mod: LuksModParameters,
},
/// Replace a previously added key with a password
#[structopt(name = "replace-key")]
ReplaceKey {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
/// Add the password and keep the key
#[structopt(short = "a", long = "add-password")]
add_password: bool,
/// Will add an token to your LUKS 2 header, including the credential id
#[structopt(short = "t", long = "token")]
token: bool,
#[structopt(flatten)]
replacement: OtherSecret,
#[structopt(flatten)]
luks_mod: LuksModParameters,
},
/// Open the LUKS device
#[structopt(name = "open")]
Open {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
name: String,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
#[structopt(short = "r", long = "max-retries", default_value = "0")]
retries: i32,
},
/// Open the LUKS device using credentials embedded in the LUKS 2 header
#[structopt(name = "open-token")]
OpenToken {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
name: String,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
#[structopt(short = "r", long = "max-retries", default_value = "0")]
retries: i32,
},
/// Generate a new FIDO credential
#[structopt(name = "credential")]
Credential {
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
/// Name to be displayed on the authenticator if it has a display
#[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME")]
name: Option<String>,
},
/// Check if an authenticator is connected
#[structopt(name = "connected")]
Connected,
Token(TokenCommand),
}
///LUKS2 token related operations
#[derive(Debug, StructOpt)]
pub enum TokenCommand {
/// List all tokens associated with the specified device
List {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
/// Dump all credentials as CSV
#[structopt(long = "csv")]
csv: bool,
},
/// Add credential to a keyslot
Add {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
credentials: Credentials,
/// Slot to which the credentials will be added
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
slot: u32,
},
/// Remove credentials from token(s)
Remove {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
credentials: Credentials,
/// Token from which the credentials will be removed
#[structopt(long = "token")]
token_id: Option<u32>,
},
/// Remove all unassigned tokens
GC {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
},
}
pub fn parse_cmdline() -> Args {
Args::from_args()
}
@@ -358,7 +82,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} => {
let pin_string;
let pin = if authenticator.pin {
pin_string = authenticator.read_pin()?;
pin_string = read_pin(authenticator)?;
Some(pin_string.as_ref())
} else {
None
@@ -375,7 +99,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} => {
let pin_string;
let pin = if authenticator.pin {
pin_string = authenticator.read_pin()?;
pin_string = read_pin(authenticator)?;
Some(pin_string.as_ref())
} else {
None
@@ -383,7 +107,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
let salt = if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed("Password", false)
} else {
secret.salt.obtain(&secret.password_helper)
secret.salt.obtain_sha256(&secret.password_helper)
}?;
let (secret, _cred) = derive_secret(
credentials.ids.0.as_slice(),
@@ -419,7 +143,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
..
} => {
let pin = if authenticator.pin {
Some(authenticator.read_pin()?)
Some(read_pin(authenticator)?)
} else {
None
};
@@ -427,7 +151,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify)
} else {
secret.salt.obtain(&secret.password_helper)
secret.salt.obtain_sha256(&secret.password_helper)
}
};
let other_secret = |salt_q: &str,
@@ -453,10 +177,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
)),
}
};
let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> {
let secret = |q: &str, verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> {
derive_secret(
&credentials.ids.0,
&salt("Password", verify)?,
&salt(q, verify)?,
authenticator.await_time,
pin.as_deref(),
)
@@ -466,7 +190,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[..],
@@ -491,7 +215,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(
@@ -535,7 +259,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} => {
let pin_string;
let pin = if authenticator.pin {
pin_string = authenticator.read_pin()?;
pin_string = read_pin(authenticator)?;
Some(pin_string.as_ref())
} else {
None
@@ -544,7 +268,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
if interactive || secret.password_helper == PasswordHelper::Stdin {
util::read_password_hashed(q, verify)
} else {
secret.salt.obtain(&secret.password_helper)
secret.salt.obtain_sha256(&secret.password_helper)
}
};
@@ -725,5 +449,17 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Ok(())
}
},
Command::GenerateCompletions { shell, out_dir } => {
Args::clap().gen_completions(
env!("CARGO_PKG_NAME"),
match shell.as_ref() {
"bash" => Shell::Bash,
"fish" => Shell::Fish,
_ => unreachable!("structopt shouldn't allow us to reach this point"),
},
&out_dir,
);
Ok(())
}
}
}

View File

@@ -10,33 +10,33 @@ use std::process::Command;
use std::str::FromStr;
#[derive(Debug, Clone, PartialEq)]
pub enum InputSalt {
pub enum SecretInput {
AskPassword,
String(String),
File { path: PathBuf },
}
impl Default for InputSalt {
impl Default for SecretInput {
fn default() -> Self {
InputSalt::AskPassword
SecretInput::AskPassword
}
}
impl From<&str> for InputSalt {
impl From<&str> for SecretInput {
fn from(s: &str) -> Self {
let mut parts = s.split(':');
match parts.next() {
Some("ask") | Some("Ask") => InputSalt::AskPassword,
Some("file") => InputSalt::File {
Some("ask") | Some("Ask") => SecretInput::AskPassword,
Some("file") => SecretInput::File {
path: parts.collect::<Vec<_>>().join(":").into(),
},
Some("string") => InputSalt::String(parts.collect::<Vec<_>>().join(":")),
Some("string") => SecretInput::String(parts.collect::<Vec<_>>().join(":")),
_ => Self::default(),
}
}
}
impl FromStr for InputSalt {
impl FromStr for SecretInput {
type Err = Fido2LuksError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
@@ -44,21 +44,42 @@ impl FromStr for InputSalt {
}
}
impl fmt::Display for InputSalt {
impl fmt::Display for SecretInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.write_str(&match self {
InputSalt::AskPassword => "ask".to_string(),
InputSalt::String(s) => ["string", s].join(":"),
InputSalt::File { path } => ["file", path.display().to_string().as_str()].join(":"),
SecretInput::AskPassword => "ask".to_string(),
SecretInput::String(s) => ["string", s].join(":"),
SecretInput::File { path } => ["file", path.display().to_string().as_str()].join(":"),
})
}
}
impl InputSalt {
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
impl SecretInput {
pub fn obtain_string(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<String> {
Ok(String::from_utf8(self.obtain(password_helper)?)?)
}
pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<Vec<u8>> {
let mut secret = Vec::new();
match self {
SecretInput::File { path } => {
//TODO: replace with try_blocks
let mut do_io = || File::open(path)?.read_to_end(&mut secret);
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
}
SecretInput::AskPassword => {
secret.extend_from_slice(password_helper.obtain()?.as_bytes())
}
SecretInput::String(s) => secret.extend_from_slice(s.as_bytes()),
}
Ok(secret)
}
pub fn obtain_sha256(&self, password_helper: &PasswordHelper) -> Fido2LuksResult<[u8; 32]> {
let mut digest = digest::Context::new(&digest::SHA256);
match self {
InputSalt::File { path } => {
SecretInput::File { path } => {
let mut do_io = || {
let mut reader = File::open(path)?;
let mut buf = [0u8; 512];
@@ -73,10 +94,7 @@ impl InputSalt {
};
do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?;
}
InputSalt::AskPassword => {
digest.update(password_helper.obtain()?.as_bytes());
}
InputSalt::String(s) => digest.update(s.as_bytes()),
_ => digest.update(self.obtain(password_helper)?.as_slice()),
}
let mut salt = [0u8; 32];
salt.as_mut().copy_from_slice(digest.finish().as_ref());
@@ -157,24 +175,30 @@ mod test {
#[test]
fn input_salt_from_str() {
assert_eq!(
"file:/tmp/abc".parse::<InputSalt>().unwrap(),
InputSalt::File {
"file:/tmp/abc".parse::<SecretInput>().unwrap(),
SecretInput::File {
path: "/tmp/abc".into()
}
);
assert_eq!(
"string:abc".parse::<InputSalt>().unwrap(),
InputSalt::String("abc".into())
"string:abc".parse::<SecretInput>().unwrap(),
SecretInput::String("abc".into())
);
assert_eq!(
"ask".parse::<SecretInput>().unwrap(),
SecretInput::AskPassword
);
assert_eq!(
"lol".parse::<SecretInput>().unwrap(),
SecretInput::default()
);
assert_eq!("ask".parse::<InputSalt>().unwrap(), InputSalt::AskPassword);
assert_eq!("lol".parse::<InputSalt>().unwrap(), InputSalt::default());
}
#[test]
fn input_salt_obtain() {
assert_eq!(
InputSalt::String("abc".into())
.obtain(&PasswordHelper::Stdin)
SecretInput::String("abc".into())
.obtain_sha256(&PasswordHelper::Stdin)
.unwrap(),
[
186, 120, 22, 191, 143, 1, 207, 234, 65, 65, 64, 222, 93, 174, 34, 35, 176, 3, 97,

293
src/cli_args/mod.rs Normal file
View File

@@ -0,0 +1,293 @@
use std::fmt::{Display, Error, Formatter};
use std::path::PathBuf;
use std::str::FromStr;
use structopt::clap::AppSettings;
use structopt::StructOpt;
mod config;
pub use config::*;
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct HexEncoded(pub Vec<u8>);
impl Display for HexEncoded {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str(&hex::encode(&self.0))
}
}
impl AsRef<[u8]> for HexEncoded {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl FromStr for HexEncoded {
type Err = hex::FromHexError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(HexEncoded(hex::decode(s)?))
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct CommaSeparated<T: FromStr + Display>(pub Vec<T>);
impl<T: Display + FromStr> Display for CommaSeparated<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for i in &self.0 {
f.write_str(&i.to_string())?;
f.write_str(",")?;
}
Ok(())
}
}
impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(CommaSeparated(
s.split(',')
.map(|part| <T as FromStr>::from_str(part))
.collect::<Result<Vec<_>, _>>()?,
))
}
}
#[derive(Debug, StructOpt)]
pub struct Credentials {
/// FIDO credential ids, separated by ',' generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub ids: CommaSeparated<HexEncoded>,
}
#[derive(Debug, StructOpt)]
pub struct AuthenticatorParameters {
/// Request a PIN to unlock the authenticator
#[structopt(short = "P", long = "pin")]
pub pin: bool,
/// Location to read PIN from
#[structopt(long = "pin-source", env = "FIDO2LUKS_PIN_SOURCE")]
pub pin_source: Option<PathBuf>,
/// Await for an authenticator to be connected, timeout after n seconds
#[structopt(
long = "await-dev",
name = "await-dev",
env = "FIDO2LUKS_DEVICE_AWAIT",
default_value = "15"
)]
pub await_time: u64,
}
#[derive(Debug, StructOpt)]
pub struct LuksParameters {
#[structopt(env = "FIDO2LUKS_DEVICE")]
pub device: PathBuf,
/// Try to unlock the device using a specifc keyslot, ignore all other slots
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
pub slot: Option<u32>,
}
#[derive(Debug, StructOpt, Clone)]
pub struct LuksModParameters {
/// Number of milliseconds required to derive the volume decryption key
/// Defaults to 10ms when using an authenticator or the default by cryptsetup when using a password
#[structopt(long = "kdf-time", name = "kdf-time")]
pub kdf_time: Option<u64>,
}
#[derive(Debug, StructOpt)]
pub struct SecretParameters {
/// Salt for secret generation, defaults to 'ask'
///
/// Options:{n}
/// - ask : Prompt user using password helper{n}
/// - file:<PATH> : Will read <FILE>{n}
/// - string:<STRING> : Will use <STRING>, which will be handled like a password provided to the 'ask' option{n}
#[structopt(
name = "salt",
long = "salt",
env = "FIDO2LUKS_SALT",
default_value = "ask"
)]
pub salt: SecretInput,
/// Script used to obtain passwords, overridden by --interactive flag
#[structopt(
name = "password-helper",
env = "FIDO2LUKS_PASSWORD_HELPER",
default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
)]
pub password_helper: PasswordHelper,
}
#[derive(Debug, StructOpt)]
pub struct Args {
/// Request passwords via Stdin instead of using the password helper
#[structopt(short = "i", long = "interactive")]
pub interactive: bool,
#[structopt(subcommand)]
pub command: Command,
}
#[derive(Debug, StructOpt, Clone)]
pub struct OtherSecret {
/// Use a keyfile instead of a password
#[structopt(short = "d", long = "keyfile", conflicts_with = "fido_device")]
pub keyfile: Option<PathBuf>,
/// Use another fido device instead of a password
/// Note: this requires for the credential fot the other device to be passed as argument as well
#[structopt(short = "f", long = "fido-device", conflicts_with = "keyfile")]
pub fido_device: bool,
}
#[derive(Debug, StructOpt)]
pub enum Command {
#[structopt(name = "print-secret")]
PrintSecret {
/// Prints the secret as binary instead of hex encoded
#[structopt(short = "b", long = "bin")]
binary: bool,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
},
/// Adds a generated key to the specified LUKS device
#[structopt(name = "add-key")]
AddKey {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
/// Will wipe all other keys
#[structopt(short = "e", long = "exclusive")]
exclusive: bool,
/// Will add an token to your LUKS 2 header, including the credential id
#[structopt(short = "t", long = "token")]
token: bool,
#[structopt(flatten)]
existing_secret: OtherSecret,
#[structopt(flatten)]
luks_mod: LuksModParameters,
},
/// Replace a previously added key with a password
#[structopt(name = "replace-key")]
ReplaceKey {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
/// Add the password and keep the key
#[structopt(short = "a", long = "add-password")]
add_password: bool,
/// Will add an token to your LUKS 2 header, including the credential id
#[structopt(short = "t", long = "token")]
token: bool,
#[structopt(flatten)]
replacement: OtherSecret,
#[structopt(flatten)]
luks_mod: LuksModParameters,
},
/// Open the LUKS device
#[structopt(name = "open")]
Open {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
name: String,
#[structopt(flatten)]
credentials: Credentials,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
#[structopt(short = "r", long = "max-retries", default_value = "0")]
retries: i32,
},
/// Open the LUKS device using credentials embedded in the LUKS 2 header
#[structopt(name = "open-token")]
OpenToken {
#[structopt(flatten)]
luks: LuksParameters,
#[structopt(env = "FIDO2LUKS_MAPPER_NAME")]
name: String,
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
#[structopt(flatten)]
secret: SecretParameters,
#[structopt(short = "r", long = "max-retries", default_value = "0")]
retries: i32,
},
/// Generate a new FIDO credential
#[structopt(name = "credential")]
Credential {
#[structopt(flatten)]
authenticator: AuthenticatorParameters,
/// Name to be displayed on the authenticator if it has a display
#[structopt(env = "FIDO2LUKS_CREDENTIAL_NAME")]
name: Option<String>,
},
/// Check if an authenticator is connected
#[structopt(name = "connected")]
Connected,
Token(TokenCommand),
/// Generate bash completion scripts
#[structopt(name = "completions", setting = AppSettings::Hidden)]
GenerateCompletions {
/// Shell to generate completions for: bash, fish
#[structopt(possible_values = &["bash", "fish"])]
shell: String,
out_dir: PathBuf,
},
}
///LUKS2 token related operations
#[derive(Debug, StructOpt)]
pub enum TokenCommand {
/// List all tokens associated with the specified device
List {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
/// Dump all credentials as CSV
#[structopt(long = "csv")]
csv: bool,
},
/// Add credential to a keyslot
Add {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
credentials: Credentials,
/// Slot to which the credentials will be added
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
slot: u32,
},
/// Remove credentials from token(s)
Remove {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
#[structopt(flatten)]
credentials: Credentials,
/// Token from which the credentials will be removed
#[structopt(long = "token")]
token_id: Option<u32>,
},
/// Remove all unassigned tokens
GC {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
},
}

View File

@@ -1,5 +1,9 @@
use ctap::FidoError;
use libcryptsetup_rs::LibcryptErr;
use std::io;
use std::io::ErrorKind;
use std::string::FromUtf8Error;
use Fido2LuksError::*;
pub type Fido2LuksResult<T> = Result<T, Fido2LuksError>;
@@ -81,11 +85,6 @@ impl From<LuksError> for Fido2LuksError {
}
}
use libcryptsetup_rs::LibcryptErr;
use std::io::ErrorKind;
use std::string::FromUtf8Error;
use Fido2LuksError::*;
impl From<FidoError> for Fido2LuksError {
fn from(e: FidoError) -> Self {
AuthenticatorError { cause: e }

View File

@@ -4,15 +4,13 @@ extern crate ctap_hmac as ctap;
#[macro_use]
extern crate serde_derive;
use crate::cli::*;
use crate::config::*;
use crate::device::*;
use crate::error::*;
use std::io;
use std::path::PathBuf;
use std::process::exit;
mod cli;
mod config;
pub mod cli_args;
mod device;
mod error;
mod luks;
@@ -21,10 +19,7 @@ mod util;
fn main() -> Fido2LuksResult<()> {
match run_cli() {
Err(e) => {
#[cfg(debug_assertions)]
eprintln!("{:?}", e);
#[cfg(not(debug_assertions))]
eprintln!("{}", e);
exit(e.exit_code())
}
_ => exit(0),