From b8ae9d91f0e79d108216923b11683c3f758dd2af Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 29 Apr 2020 19:56:18 +0200 Subject: [PATCH] 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(