From c3d6425e2dc971266c192bacdb439ccd492d87fa Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 29 Apr 2020 18:50:55 +0200 Subject: [PATCH 1/5] reorganised cli --- src/cli.rs | 358 ++++++++++++++++++++++++++++++++------------------ src/config.rs | 2 +- src/main.rs | 21 --- src/util.rs | 4 + 4 files changed, 235 insertions(+), 150 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 3f3e39b..40ba728 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,7 +12,7 @@ use std::io::Write; use std::process::exit; use std::thread; -use crate::util::read_password; +use crate::util::sha256; use std::time::SystemTime; #[derive(Debug, Eq, PartialEq, Clone)] @@ -56,6 +56,99 @@ impl FromStr for CommaSeparated { } } +#[derive(Debug, StructOpt)] +pub struct AuthenticatorParameters { + /// FIDO credential ids, seperated by ',' generate using fido2luks credential + #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] + pub credential_ids: CommaSeparated, + + /// 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")] + device: PathBuf, +} + +#[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], + timeout: u64, +) -> Fido2LuksResult<[u8; 32]> { + let timeout = Duration::from_secs(timeout); + let start = SystemTime::now(); + + while let Ok(el) = start.elapsed() { + if el > timeout { + return Err(error::Fido2LuksError::NoAuthenticatorError); + } + if get_devices() + .map(|devices| !devices.is_empty()) + .unwrap_or(false) + { + break; + } + thread::sleep(Duration::from_millis(500)); + } + + Ok(sha256(&[ + &perform_challenge( + &credentials + .iter() + .map(|hex| FidoCredential { + id: hex.0.clone(), + public_key: None, + }) + .collect::>() + .iter() + .collect::>()[..], + salt, + timeout - start.elapsed().unwrap(), + )?[..], + salt, + ])) +} + #[derive(Debug, StructOpt)] pub struct Args { /// Request passwords via Stdin instead of using the password helper @@ -109,70 +202,6 @@ pub struct SecretGeneration { pub verify_password: Option, } -impl SecretGeneration { - pub fn patch(&self, args: &Args, verify_password: Option) -> Self { - let mut me = self.clone(); - if args.interactive { - me.password_helper = PasswordHelper::Stdin; - } - me.verify_password = me.verify_password.or(verify_password); - me - } - - pub fn obtain_secret(&self, password_query: &str) -> Fido2LuksResult<[u8; 32]> { - let mut salt = [0u8; 32]; - match self.password_helper { - PasswordHelper::Stdin if !self.verify_password.unwrap_or(true) => { - salt.copy_from_slice(&util::sha256(&[&read_password( - password_query, - self.verify_password.unwrap_or(true), - )? - .as_bytes()[..]])); - } - _ => { - salt = self.salt.obtain(&self.password_helper)?; - } - } - let timeout = Duration::from_secs(self.await_authenticator); - let start = SystemTime::now(); - - while let Ok(el) = start.elapsed() { - if el > timeout { - return Err(error::Fido2LuksError::NoAuthenticatorError); - } - if get_devices() - .map(|devices| !devices.is_empty()) - .unwrap_or(false) - { - break; - } - thread::sleep(Duration::from_millis(500)); - } - let credentials = &self - .credential_ids - .0 - .iter() - .map(|HexEncoded(id)| FidoCredential { - id: id.to_vec(), - public_key: None, - }) - .collect::>(); - let credentials = credentials.iter().collect::>(); - Ok(assemble_secret( - &perform_challenge(&credentials[..], &salt, timeout - start.elapsed().unwrap())?, - &salt, - )) - } -} - -#[derive(Debug, StructOpt, Clone)] -pub struct LuksSettings { - /// 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, Clone)] pub struct OtherSecret { /// Use a keyfile instead of a password @@ -184,24 +213,6 @@ pub struct OtherSecret { fido_device: bool, } -impl OtherSecret { - pub fn obtain( - &self, - secret_gen: &SecretGeneration, - verify_password: bool, - password_question: &str, - ) -> Fido2LuksResult> { - match &self.keyfile { - Some(keyfile) => util::read_keyfile(keyfile.clone()), - None if self.fido_device => { - Ok(Vec::from(&secret_gen.obtain_secret(password_question)?[..])) - } - None => util::read_password(password_question, verify_password) - .map(|p| p.as_bytes().to_vec()), - } - } -} - #[derive(Debug, StructOpt)] pub enum Command { #[structopt(name = "print-secret")] @@ -210,49 +221,57 @@ pub enum Command { #[structopt(short = "b", long = "bin")] binary: bool, #[structopt(flatten)] - secret_gen: SecretGeneration, + authenticator: AuthenticatorParameters, + #[structopt(flatten)] + secret: SecretParameters, }, /// Adds a generated key to the specified LUKS device #[structopt(name = "add-key")] AddKey { - #[structopt(env = "FIDO2LUKS_DEVICE")] - device: PathBuf, + #[structopt(flatten)] + luks: LuksParameters, + #[structopt(flatten)] + authenticator: AuthenticatorParameters, + #[structopt(flatten)] + secret: SecretParameters, /// Will wipe all other keys #[structopt(short = "e", long = "exclusive")] exclusive: bool, #[structopt(flatten)] existing_secret: OtherSecret, #[structopt(flatten)] - secret_gen: SecretGeneration, - #[structopt(flatten)] - luks_settings: LuksSettings, + luks_mod: LuksModParameters, }, /// Replace a previously added key with a password #[structopt(name = "replace-key")] ReplaceKey { - #[structopt(env = "FIDO2LUKS_DEVICE")] - device: PathBuf, + #[structopt(flatten)] + luks: LuksParameters, + #[structopt(flatten)] + authenticator: AuthenticatorParameters, + #[structopt(flatten)] + secret: SecretParameters, /// Add the password and keep the key #[structopt(short = "a", long = "add-password")] add_password: bool, #[structopt(flatten)] replacement: OtherSecret, #[structopt(flatten)] - secret_gen: SecretGeneration, - #[structopt(flatten)] - luks_settings: LuksSettings, + luks_mod: LuksModParameters, }, /// Open the LUKS device #[structopt(name = "open")] Open { - #[structopt(env = "FIDO2LUKS_DEVICE")] - device: PathBuf, + #[structopt(flatten)] + luks: LuksParameters, + #[structopt(flatten)] + authenticator: AuthenticatorParameters, + #[structopt(flatten)] + secret: SecretParameters, #[structopt(env = "FIDO2LUKS_MAPPER_NAME")] name: String, #[structopt(short = "r", long = "max-retries", default_value = "0")] retries: i32, - #[structopt(flatten)] - secret_gen: SecretGeneration, }, /// Generate a new FIDO credential #[structopt(name = "credential")] @@ -273,6 +292,7 @@ pub fn parse_cmdline() -> Args { pub fn run_cli() -> Fido2LuksResult<()> { let mut stdout = io::stdout(); let args = parse_cmdline(); + let interactive = args.interactive; match &args.command { Command::Credential { name } => { let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()))?; @@ -281,11 +301,19 @@ pub fn run_cli() -> Fido2LuksResult<()> { } Command::PrintSecret { binary, - ref secret_gen, + authenticator, + secret, } => { - let secret = secret_gen - .patch(&args, Some(false)) - .obtain_secret("Password")?; + let salt = if interactive || secret.password_helper == PasswordHelper::Stdin { + util::read_password_hashed("Password", false) + } else { + secret.salt.obtain(&secret.password_helper) + }?; + let secret = derive_secret( + authenticator.credential_ids.0.as_slice(), + &salt, + authenticator.await_time, + )?; if *binary { stdout.write_all(&secret[..])?; } else { @@ -294,72 +322,146 @@ pub fn run_cli() -> Fido2LuksResult<()> { Ok(stdout.flush()?) } Command::AddKey { - device, + luks, + authenticator, + secret, exclusive, existing_secret, - ref secret_gen, - luks_settings, + luks_mod, } => { - let secret_gen = secret_gen.patch(&args, None); - let old_secret = existing_secret.obtain(&secret_gen, false, "Existing password")?; - let secret = secret_gen.obtain_secret("Password")?; + let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { + if interactive || secret.password_helper == PasswordHelper::Stdin { + util::read_password_hashed(q, verify) + } else { + secret.salt.obtain(&secret.password_helper) + } + }; + let old_secret = match existing_secret { + OtherSecret { + keyfile: Some(file), + .. + } => util::read_keyfile(file)?, + OtherSecret { + fido_device: true, .. + } => derive_secret( + &authenticator.credential_ids.0, + &salt("Existing password", false)?, + authenticator.await_time, + )?[..] + .to_vec(), + _ => util::read_password("Existing password", false)? + .as_bytes() + .to_vec(), + }; + let secret = derive_secret( + &authenticator.credential_ids.0, + &salt("Password", false)?, + authenticator.await_time, + )?; let added_slot = luks::add_key( - device.clone(), + &luks.device, &secret, &old_secret[..], - luks_settings.kdf_time.or(Some(10)), + luks_mod.kdf_time.or(Some(10)), )?; if *exclusive { - let destroyed = luks::remove_keyslots(&device, &[added_slot])?; + let destroyed = luks::remove_keyslots(&luks.device, &[added_slot])?; println!( "Added to key to device {}, slot: {}\nRemoved {} old keys", - device.display(), + luks.device.display(), added_slot, destroyed ); } else { println!( "Added to key to device {}, slot: {}", - device.display(), + luks.device.display(), added_slot ); } Ok(()) } Command::ReplaceKey { - device, + luks, + authenticator, + secret, add_password, replacement, - ref secret_gen, - luks_settings, + luks_mod, } => { - let secret_gen = secret_gen.patch(&args, Some(false)); - let secret = secret_gen.obtain_secret("Password")?; - let new_secret = replacement.obtain(&secret_gen, true, "Replacement password")?; + let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { + if interactive || secret.password_helper == PasswordHelper::Stdin { + util::read_password_hashed(q, verify) + } else { + secret.salt.obtain(&secret.password_helper) + } + }; + let replacement_secret = match replacement { + OtherSecret { + keyfile: Some(file), + .. + } => util::read_keyfile(file)?, + OtherSecret { + fido_device: true, .. + } => derive_secret( + &authenticator.credential_ids.0, + &salt("Replacement password", true)?, + authenticator.await_time, + )?[..] + .to_vec(), + _ => util::read_password("Replacement password", true)? + .as_bytes() + .to_vec(), + }; + let existing_secret = derive_secret( + &authenticator.credential_ids.0, + &salt("Password", false)?, + authenticator.await_time, + )?; let slot = if *add_password { - luks::add_key(device, &new_secret[..], &secret, luks_settings.kdf_time) + luks::add_key( + &luks.device, + &replacement_secret[..], + &existing_secret, + luks_mod.kdf_time, + ) } else { - luks::replace_key(device, &new_secret[..], &secret, luks_settings.kdf_time) + luks::replace_key( + &luks.device, + &replacement_secret[..], + &existing_secret, + luks_mod.kdf_time, + ) }?; println!( "Added to password to device {}, slot: {}", - device.display(), + luks.device.display(), slot ); Ok(()) } Command::Open { - device, + luks, + authenticator, + secret, name, retries, - ref secret_gen, } => { + let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { + if interactive || secret.password_helper == PasswordHelper::Stdin { + util::read_password_hashed(q, verify) + } else { + secret.salt.obtain(&secret.password_helper) + } + }; let mut retries = *retries; loop { - match secret_gen - .patch(&args, Some(false)) - .obtain_secret("Password") - .and_then(|secret| luks::open_container(&device, &name, &secret)) + match derive_secret( + &authenticator.credential_ids.0, + &salt("Password", false)?, + authenticator.await_time, + ) + .and_then(|secret| luks::open_container(&luks.device, &name, &secret)) { Err(e) => { match e { diff --git a/src/config.rs b/src/config.rs index 5a6b593..88c13c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -84,7 +84,7 @@ impl InputSalt { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum PasswordHelper { Script(String), #[allow(dead_code)] diff --git a/src/main.rs b/src/main.rs index e0c701c..5bd54c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,10 +16,6 @@ mod error; mod luks; mod util; -fn assemble_secret(hmac_result: &[u8], salt: &[u8]) -> [u8; 32] { - util::sha256(&[salt, hmac_result]) -} - fn main() -> Fido2LuksResult<()> { match run_cli() { Err(e) => { @@ -32,20 +28,3 @@ fn main() -> Fido2LuksResult<()> { _ => exit(0), } } - -#[cfg(test)] -mod test { - - use super::*; - - #[test] - fn test_assemble_secret() { - assert_eq!( - assemble_secret(b"abc", b"def"), - [ - 46, 82, 82, 140, 142, 159, 249, 196, 227, 160, 142, 72, 151, 77, 59, 62, 180, 36, - 33, 47, 241, 94, 17, 232, 133, 103, 247, 32, 152, 253, 43, 19 - ] - ) - } -} diff --git a/src/util.rs b/src/util.rs index 3fb2585..3676480 100644 --- a/src/util.rs +++ b/src/util.rs @@ -29,6 +29,10 @@ pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult { } } +pub fn read_password_hashed(q: &str, verify: bool) -> Fido2LuksResult<[u8; 32]> { + read_password(q, verify).map(|pass| sha256(&[pass.as_bytes()])) +} + pub fn read_keyfile>(path: P) -> Fido2LuksResult> { let mut file = File::open(path.into())?; let mut key = Vec::new(); From fcdd2a2d3db5dd5d833ae4fd710d8b64f5b056cf Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 29 Apr 2020 18:55:25 +0200 Subject: [PATCH 2/5] add option to specify keyslot --- src/cli.rs | 5 ++++- src/luks.rs | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 40ba728..98bb020 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -76,6 +76,9 @@ pub struct AuthenticatorParameters { pub struct LuksParameters { #[structopt(env = "FIDO2LUKS_DEVICE")] device: PathBuf, + + #[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")] + slot_hint: Option, } #[derive(Debug, StructOpt, Clone)] @@ -461,7 +464,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { &salt("Password", false)?, authenticator.await_time, ) - .and_then(|secret| luks::open_container(&luks.device, &name, &secret)) + .and_then(|secret| luks::open_container(&luks.device, &name, &secret, luks.slot_hint)) { Err(e) => { match e { diff --git a/src/luks.rs b/src/luks.rs index 2b1f7ac..c767d4f 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -18,11 +18,11 @@ fn load_device_handle>(path: P) -> Fido2LuksResult { Ok(device) } -pub fn open_container>(path: P, name: &str, secret: &[u8]) -> Fido2LuksResult<()> { +pub fn open_container>(path: P, name: &str, secret: &[u8], slot_hint: Option) -> Fido2LuksResult<()> { let mut device = load_device_handle(path)?; device .activate_handle() - .activate_by_passphrase(Some(name), None, secret, CryptActivateFlags::empty()) + .activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty()) .map(|_slot| ()) .map_err(|_e| Fido2LuksError::WrongSecret) } From b8ae9d91f0e79d108216923b11683c3f758dd2af Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 29 Apr 2020 19:56:18 +0200 Subject: [PATCH 3/5] rudimentary pin support --- src/cli.rs | 136 +++++++++++++++++++++++++++++--------------------- src/device.rs | 16 +++++- 2 files changed, 94 insertions(+), 58 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 98bb020..f244576 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -57,10 +57,17 @@ impl FromStr for CommaSeparated { } #[derive(Debug, StructOpt)] -pub struct AuthenticatorParameters { +pub struct Credentials { /// FIDO credential ids, seperated by ',' generate using fido2luks credential #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] - pub credential_ids: CommaSeparated, + pub ids: CommaSeparated, +} + +#[derive(Debug, StructOpt)] +pub struct AuthenticatorParameters { + /// Request a PIN to unlock the authenticator + #[structopt(short = "P", long = "pin")] + pub pin: bool, /// Await for an authenticator to be connected, timeout after n seconds #[structopt( @@ -77,8 +84,9 @@ 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_hint: Option, + slot: Option, } #[derive(Debug, StructOpt, Clone)] @@ -117,6 +125,7 @@ fn derive_secret( credentials: &[HexEncoded], salt: &[u8; 32], timeout: u64, + pin: Option<&str>, ) -> Fido2LuksResult<[u8; 32]> { let timeout = Duration::from_secs(timeout); let start = SystemTime::now(); @@ -147,11 +156,16 @@ fn derive_secret( .collect::>()[..], salt, timeout - start.elapsed().unwrap(), + pin, )?[..], salt, ])) } +fn read_pin() -> Fido2LuksResult { + util::read_password("Authenticator PIN", false) +} + #[derive(Debug, StructOpt)] pub struct Args { /// Request passwords via Stdin instead of using the password helper @@ -161,50 +175,6 @@ pub struct Args { pub command: Command, } -#[derive(Debug, StructOpt, Clone)] -pub struct SecretGeneration { - /// FIDO credential ids, seperated by ',' generate using fido2luks credential - #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] - pub credential_ids: CommaSeparated, - /// 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, - - /// 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_authenticator: u64, - - /// Request the password twice to ensure it being correct - #[structopt( - long = "verify-password", - env = "FIDO2LUKS_VERIFY_PASSWORD", - hidden = true - )] - pub verify_password: Option, -} - #[derive(Debug, StructOpt, Clone)] pub struct OtherSecret { /// Use a keyfile instead of a password @@ -224,6 +194,8 @@ pub enum Command { #[structopt(short = "b", long = "bin")] binary: bool, #[structopt(flatten)] + credentials: Credentials, + #[structopt(flatten)] authenticator: AuthenticatorParameters, #[structopt(flatten)] secret: SecretParameters, @@ -234,6 +206,8 @@ pub enum Command { #[structopt(flatten)] luks: LuksParameters, #[structopt(flatten)] + credentials: Credentials, + #[structopt(flatten)] authenticator: AuthenticatorParameters, #[structopt(flatten)] secret: SecretParameters, @@ -251,6 +225,8 @@ pub enum Command { #[structopt(flatten)] luks: LuksParameters, #[structopt(flatten)] + credentials: Credentials, + #[structopt(flatten)] authenticator: AuthenticatorParameters, #[structopt(flatten)] secret: SecretParameters, @@ -268,6 +244,8 @@ pub enum Command { #[structopt(flatten)] luks: LuksParameters, #[structopt(flatten)] + credentials: Credentials, + #[structopt(flatten)] authenticator: AuthenticatorParameters, #[structopt(flatten)] secret: SecretParameters, @@ -279,6 +257,8 @@ pub enum Command { /// 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, @@ -297,25 +277,44 @@ pub fn run_cli() -> Fido2LuksResult<()> { let args = parse_cmdline(); let interactive = args.interactive; match &args.command { - Command::Credential { name } => { - let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()))?; + Command::Credential { + authenticator, + name, + } => { + let pin_string; + let pin = if authenticator.pin { + pin_string = read_pin()?; + Some(pin_string.as_ref()) + } else { + None + }; + let cred = make_credential_id(name.as_ref().map(|n| n.as_ref()), pin)?; println!("{}", hex::encode(&cred.id)); Ok(()) } Command::PrintSecret { binary, authenticator, + credentials, secret, } => { + let pin_string; + let pin = if authenticator.pin { + pin_string = read_pin()?; + Some(pin_string.as_ref()) + } else { + None + }; let salt = if interactive || secret.password_helper == PasswordHelper::Stdin { util::read_password_hashed("Password", false) } else { secret.salt.obtain(&secret.password_helper) }?; let secret = derive_secret( - authenticator.credential_ids.0.as_slice(), + credentials.ids.0.as_slice(), &salt, authenticator.await_time, + pin, )?; if *binary { stdout.write_all(&secret[..])?; @@ -327,11 +326,17 @@ pub fn run_cli() -> Fido2LuksResult<()> { Command::AddKey { luks, authenticator, + credentials, secret, exclusive, existing_secret, luks_mod, } => { + let pin = if authenticator.pin { + Some(read_pin()?) + } else { + None + }; let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { if interactive || secret.password_helper == PasswordHelper::Stdin { util::read_password_hashed(q, verify) @@ -347,9 +352,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { OtherSecret { fido_device: true, .. } => derive_secret( - &authenticator.credential_ids.0, + &credentials.ids.0, &salt("Existing password", false)?, authenticator.await_time, + pin.as_deref(), )?[..] .to_vec(), _ => util::read_password("Existing password", false)? @@ -357,9 +363,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { .to_vec(), }; let secret = derive_secret( - &authenticator.credential_ids.0, + &credentials.ids.0, &salt("Password", false)?, authenticator.await_time, + pin.as_deref(), )?; let added_slot = luks::add_key( &luks.device, @@ -387,11 +394,17 @@ pub fn run_cli() -> Fido2LuksResult<()> { Command::ReplaceKey { luks, authenticator, + credentials, secret, add_password, replacement, luks_mod, } => { + let pin = if authenticator.pin { + Some(read_pin()?) + } else { + None + }; let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { if interactive || secret.password_helper == PasswordHelper::Stdin { util::read_password_hashed(q, verify) @@ -407,9 +420,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { OtherSecret { fido_device: true, .. } => derive_secret( - &authenticator.credential_ids.0, + &credentials.ids.0, &salt("Replacement password", true)?, authenticator.await_time, + pin.as_deref(), )?[..] .to_vec(), _ => util::read_password("Replacement password", true)? @@ -417,9 +431,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { .to_vec(), }; let existing_secret = derive_secret( - &authenticator.credential_ids.0, + &credentials.ids.0, &salt("Password", false)?, authenticator.await_time, + pin.as_deref(), )?; let slot = if *add_password { luks::add_key( @@ -446,10 +461,18 @@ pub fn run_cli() -> Fido2LuksResult<()> { Command::Open { luks, authenticator, + credentials, secret, name, retries, } => { + let pin_string; + let pin = if authenticator.pin { + pin_string = read_pin()?; + Some(pin_string.as_ref()) + } else { + None + }; let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { if interactive || secret.password_helper == PasswordHelper::Stdin { util::read_password_hashed(q, verify) @@ -460,11 +483,12 @@ pub fn run_cli() -> Fido2LuksResult<()> { let mut retries = *retries; loop { match derive_secret( - &authenticator.credential_ids.0, + &credentials.ids.0, &salt("Password", false)?, authenticator.await_time, + pin, ) - .and_then(|secret| luks::open_container(&luks.device, &name, &secret, luks.slot_hint)) + .and_then(|secret| luks::open_container(&luks.device, &name, &secret, luks.slot)) { Err(e) => { match e { diff --git a/src/device.rs b/src/device.rs index 12cae6f..9cc9eb6 100644 --- a/src/device.rs +++ b/src/device.rs @@ -9,13 +9,21 @@ use std::time::Duration; const RP_ID: &str = "fido2luks"; -pub fn make_credential_id(name: Option<&str>) -> Fido2LuksResult { +pub fn make_credential_id( + name: Option<&str>, + pin: Option<&str>, +) -> Fido2LuksResult { let mut request = FidoCredentialRequestBuilder::default().rp_id(RP_ID); if let Some(user_name) = name { request = request.user_name(user_name); } let request = request.build().unwrap(); - let make_credential = |device: &mut FidoDevice| device.make_hmac_credential(&request); + let make_credential = |device: &mut FidoDevice| { + if let Some(pin) = pin { + device.unlock(pin)?; + } + device.make_hmac_credential(&request) + }; Ok(request_multiple_devices( get_devices()? .iter_mut() @@ -28,6 +36,7 @@ pub fn perform_challenge( credentials: &[&FidoCredential], salt: &[u8; 32], timeout: Duration, + pin: Option<&str>, ) -> Fido2LuksResult<[u8; 32]> { let request = FidoAssertionRequestBuilder::default() .rp_id(RP_ID) @@ -35,6 +44,9 @@ pub fn perform_challenge( .build() .unwrap(); let get_assertion = |device: &mut FidoDevice| { + if let Some(pin) = pin { + device.unlock(pin)?; + } device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None) }; let (_, (secret, _)) = request_multiple_devices( From 69732a1ad638d73c75e5b45343c69dc3e0ba35f7 Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 29 Apr 2020 20:33:28 +0200 Subject: [PATCH 4/5] restore order --- src/cli.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index f244576..86e34a7 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -243,14 +243,15 @@ pub enum Command { 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(env = "FIDO2LUKS_MAPPER_NAME")] - name: String, + #[structopt(short = "r", long = "max-retries", default_value = "0")] retries: i32, }, From a26b79bcd64df54c6d448ac1b5ea87adc31c3a9a Mon Sep 17 00:00:00 2001 From: shimun Date: Tue, 5 May 2020 23:53:50 +0200 Subject: [PATCH 5/5] reduced redundant code --- src/cli.rs | 187 +++++++++++++++++++++++------------------------------ 1 file changed, 82 insertions(+), 105 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 86e34a7..5cc4178 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -329,77 +329,18 @@ pub fn run_cli() -> Fido2LuksResult<()> { authenticator, credentials, secret, - exclusive, - existing_secret, luks_mod, - } => { - let pin = if authenticator.pin { - Some(read_pin()?) - } else { - None - }; - let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { - if interactive || secret.password_helper == PasswordHelper::Stdin { - util::read_password_hashed(q, verify) - } else { - secret.salt.obtain(&secret.password_helper) - } - }; - let old_secret = match existing_secret { - OtherSecret { - keyfile: Some(file), - .. - } => util::read_keyfile(file)?, - OtherSecret { - fido_device: true, .. - } => derive_secret( - &credentials.ids.0, - &salt("Existing password", false)?, - authenticator.await_time, - pin.as_deref(), - )?[..] - .to_vec(), - _ => util::read_password("Existing password", false)? - .as_bytes() - .to_vec(), - }; - let secret = derive_secret( - &credentials.ids.0, - &salt("Password", false)?, - authenticator.await_time, - pin.as_deref(), - )?; - let added_slot = luks::add_key( - &luks.device, - &secret, - &old_secret[..], - luks_mod.kdf_time.or(Some(10)), - )?; - if *exclusive { - let destroyed = luks::remove_keyslots(&luks.device, &[added_slot])?; - println!( - "Added to key to device {}, slot: {}\nRemoved {} old keys", - luks.device.display(), - added_slot, - destroyed - ); - } else { - println!( - "Added to key to device {}, slot: {}", - luks.device.display(), - added_slot - ); - } - Ok(()) + existing_secret: other_secret, + .. } - Command::ReplaceKey { + | Command::ReplaceKey { luks, authenticator, credentials, secret, - add_password, - replacement, luks_mod, + replacement: other_secret, + .. } => { let pin = if authenticator.pin { Some(read_pin()?) @@ -413,51 +354,87 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret.salt.obtain(&secret.password_helper) } }; - let replacement_secret = match replacement { - OtherSecret { - keyfile: Some(file), - .. - } => util::read_keyfile(file)?, - OtherSecret { - fido_device: true, .. - } => derive_secret( + let other_secret = |salt_q: &str, verify: bool| -> Fido2LuksResult> { + match other_secret { + OtherSecret { + keyfile: Some(file), + .. + } => util::read_keyfile(file), + OtherSecret { + fido_device: true, .. + } => Ok(derive_secret( + &credentials.ids.0, + &salt(salt_q, verify)?, + authenticator.await_time, + pin.as_deref(), + )?[..] + .to_vec()), + _ => Ok(util::read_password(salt_q, verify)?.as_bytes().to_vec()), + } + }; + let secret = |verify: bool| -> Fido2LuksResult<[u8; 32]> { + derive_secret( &credentials.ids.0, - &salt("Replacement password", true)?, + &salt("Password", verify)?, authenticator.await_time, pin.as_deref(), - )?[..] - .to_vec(), - _ => util::read_password("Replacement password", true)? - .as_bytes() - .to_vec(), + ) }; - let existing_secret = derive_secret( - &credentials.ids.0, - &salt("Password", false)?, - authenticator.await_time, - pin.as_deref(), - )?; - let slot = if *add_password { - luks::add_key( - &luks.device, - &replacement_secret[..], - &existing_secret, - luks_mod.kdf_time, - ) - } else { - luks::replace_key( - &luks.device, - &replacement_secret[..], - &existing_secret, - luks_mod.kdf_time, - ) - }?; - println!( - "Added to password to device {}, slot: {}", - luks.device.display(), - slot - ); - Ok(()) + // Non overlap + match &args.command { + Command::AddKey { exclusive, .. } => { + let existing_secret = other_secret("Current password", false)?; + let new_secret = secret(true)?; + let added_slot = luks::add_key( + &luks.device, + &new_secret, + &existing_secret[..], + luks_mod.kdf_time.or(Some(10)), + )?; + if *exclusive { + let destroyed = luks::remove_keyslots(&luks.device, &[added_slot])?; + println!( + "Added to key to device {}, slot: {}\nRemoved {} old keys", + luks.device.display(), + added_slot, + destroyed + ); + } else { + println!( + "Added to key to device {}, slot: {}", + luks.device.display(), + added_slot + ); + } + Ok(()) + } + Command::ReplaceKey { add_password, .. } => { + let existing_secret = secret(false)?; + let replacement_secret = other_secret("Replacement password", true)?; + let slot = if *add_password { + luks::add_key( + &luks.device, + &replacement_secret[..], + &existing_secret, + luks_mod.kdf_time, + ) + } else { + luks::replace_key( + &luks.device, + &replacement_secret[..], + &existing_secret, + luks_mod.kdf_time, + ) + }?; + println!( + "Added to password to device {}, slot: {}", + luks.device.display(), + slot + ); + Ok(()) + } + _ => unreachable!(), + } } Command::Open { luks,