From c2e38eb06fc96d69e627d93d56bad8bfb630665b Mon Sep 17 00:00:00 2001 From: shimun Date: Fri, 4 Sep 2020 22:41:50 +0200 Subject: [PATCH] generate shell completions during build --- .gitignore | 4 + Cargo.lock | 2 +- Cargo.toml | 12 +- PKGBUILD | 3 +- build.rs | 24 +++ src/cli.rs | 329 +++-------------------------------- src/{ => cli_args}/config.rs | 76 +++++--- src/cli_args/mod.rs | 293 +++++++++++++++++++++++++++++++ src/error.rs | 9 +- src/main.rs | 4 +- 10 files changed, 411 insertions(+), 345 deletions(-) create mode 100644 build.rs rename src/{ => cli_args}/config.rs (65%) create mode 100644 src/cli_args/mod.rs diff --git a/.gitignore b/.gitignore index 7248da6..057a1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ **/*.rs.bk .idea/ *.iml +fido2luks.bash +fido2luks.elv +fido2luks.fish +fido2luks.zsh \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 2689a27..594406f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "fido2luks" -version = "0.2.13" +version = "0.2.14" dependencies = [ "ctap_hmac", "failure", diff --git a/Cargo.toml b/Cargo.toml index 7882575..ad486f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fido2luks" -version = "0.2.13" +version = "0.2.14" authors = ["shimunn "] 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,7 @@ 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"], ["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 ac50d70..0a80f98 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -17,10 +17,9 @@ pkgver() { build() { cargo build --release --locked --all-features --target-dir=target - ./target/release/fido2luks completions bash target } package() { install -Dm 755 target/release/${pkgname} -t "${pkgdir}/usr/bin" - install -Dm 644 target/fido2luks.bash "${pkgdir}/usr/share/bash-completion/completions/fido2luks" + install -Dm 644 fido2luks.bash "${pkgdir}/usr/share/bash-completion/completions/fido2luks" } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..c520b10 --- /dev/null +++ b/build.rs @@ -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(), "."); + } +} diff --git a/src/cli.rs b/src/cli.rs index ecb844c..e1e06d2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,152 +1,36 @@ use crate::error::*; +use crate::luks::{Fido2LuksToken, LuksDevice}; +use crate::util::sha256; use crate::*; +use cli_args::*; -use structopt::clap::{AppSettings, Shell}; +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::str::FromStr; use std::thread; +use std::time::Duration; -use crate::luks::{Fido2LuksToken, LuksDevice}; -use crate::util::sha256; 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); - -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; +pub use cli_args::Args; - fn from_str(s: &str) -> Result { - Ok(HexEncoded(hex::decode(s)?)) +fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult { + if let Some(src) = ap.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, Eq, PartialEq, Clone)] -pub struct CommaSeparated(pub Vec); - -impl Display for CommaSeparated { - fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { - for i in &self.0 { - f.write_str(&i.to_string())?; - f.write_str(",")?; - } - Ok(()) - } -} - -impl FromStr for CommaSeparated { - type Err = ::Err; - fn from_str(s: &str) -> Result { - Ok(CommaSeparated( - s.split(',') - .map(|part| ::from_str(part)) - .collect::, _>>()?, - )) - } -} - -#[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, -} - -#[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, - - /// 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 { - 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, -} - -#[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, -} - -#[derive(Debug, StructOpt)] -pub struct SecretParameters { - /// Salt for secret generation, defaults to 'ask' - /// - /// Options:{n} - /// - ask : Prompt user using password helper{n} - /// - file: : Will read {n} - /// - string: : Will use , 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], @@ -183,175 +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, - /// 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, - }, - /// 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, - }, - /// Remove all unassigned tokens - GC { - #[structopt(env = "FIDO2LUKS_DEVICE")] - device: PathBuf, - }, -} - pub fn parse_cmdline() -> Args { Args::from_args() } @@ -367,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 @@ -384,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 @@ -392,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(), @@ -428,7 +143,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { .. } => { let pin = if authenticator.pin { - Some(authenticator.read_pin()?) + Some(read_pin(authenticator)?) } else { None }; @@ -436,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, @@ -544,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 @@ -553,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) } }; diff --git a/src/config.rs b/src/cli_args/config.rs similarity index 65% rename from src/config.rs rename to src/cli_args/config.rs index 28faa69..d2ef46c 100644 --- a/src/config.rs +++ b/src/cli_args/config.rs @@ -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::>().join(":").into(), }, - Some("string") => InputSalt::String(parts.collect::>().join(":")), + Some("string") => SecretInput::String(parts.collect::>().join(":")), _ => Self::default(), } } } -impl FromStr for InputSalt { +impl FromStr for SecretInput { type Err = Fido2LuksError; fn from_str(s: &str) -> Result { @@ -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 { + Ok(String::from_utf8(self.obtain(password_helper)?)?) + } + + pub fn obtain(&self, password_helper: &PasswordHelper) -> Fido2LuksResult> { + 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,23 +175,29 @@ mod test { #[test] fn input_salt_from_str() { assert_eq!( - "file:/tmp/abc".parse::().unwrap(), - InputSalt::File { + "file:/tmp/abc".parse::().unwrap(), + SecretInput::File { path: "/tmp/abc".into() } ); assert_eq!( - "string:abc".parse::().unwrap(), - InputSalt::String("abc".into()) + "string:abc".parse::().unwrap(), + SecretInput::String("abc".into()) + ); + assert_eq!( + "ask".parse::().unwrap(), + SecretInput::AskPassword + ); + assert_eq!( + "lol".parse::().unwrap(), + SecretInput::default() ); - assert_eq!("ask".parse::().unwrap(), InputSalt::AskPassword); - assert_eq!("lol".parse::().unwrap(), InputSalt::default()); } #[test] fn input_salt_obtain() { assert_eq!( - InputSalt::String("abc".into()) + SecretInput::String("abc".into()) .obtain(&PasswordHelper::Stdin) .unwrap(), [ diff --git a/src/cli_args/mod.rs b/src/cli_args/mod.rs new file mode 100644 index 0000000..b02799f --- /dev/null +++ b/src/cli_args/mod.rs @@ -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); + +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 { + Ok(HexEncoded(hex::decode(s)?)) + } +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct CommaSeparated(pub Vec); + +impl Display for CommaSeparated { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + for i in &self.0 { + f.write_str(&i.to_string())?; + f.write_str(",")?; + } + Ok(()) + } +} + +impl FromStr for CommaSeparated { + type Err = ::Err; + fn from_str(s: &str) -> Result { + Ok(CommaSeparated( + s.split(',') + .map(|part| ::from_str(part)) + .collect::, _>>()?, + )) + } +} + +#[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, +} + +#[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, + + /// 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, +} + +#[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, +} + +#[derive(Debug, StructOpt)] +pub struct SecretParameters { + /// Salt for secret generation, defaults to 'ask' + /// + /// Options:{n} + /// - ask : Prompt user using password helper{n} + /// - file: : Will read {n} + /// - string: : Will use , 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, + /// 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, + }, + /// 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, + }, + /// Remove all unassigned tokens + GC { + #[structopt(env = "FIDO2LUKS_DEVICE")] + device: PathBuf, + }, +} diff --git a/src/error.rs b/src/error.rs index 0f7a34c..03d9741 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 = Result; @@ -81,11 +85,6 @@ impl From for Fido2LuksError { } } -use libcryptsetup_rs::LibcryptErr; -use std::io::ErrorKind; -use std::string::FromUtf8Error; -use Fido2LuksError::*; - impl From for Fido2LuksError { fn from(e: FidoError) -> Self { AuthenticatorError { cause: e } diff --git a/src/main.rs b/src/main.rs index deae9c8..cc5f42e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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;