diff --git a/src/cli.rs b/src/cli.rs index 8be3944..ef6f334 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -12,8 +12,10 @@ use std::io::Write; use std::process::exit; use std::thread; +use crate::luks::LuksDevice; use crate::util::sha256; use std::borrow::Cow; +use std::collections::HashSet; use std::time::SystemTime; #[derive(Debug, Eq, PartialEq, Clone)] @@ -282,6 +284,41 @@ pub enum Command { /// Check if an authenticator is connected #[structopt(name = "connected")] Connected, + Token(TokenCommand), +} + +#[derive(Debug, StructOpt)] +pub enum TokenCommand { + /// List all tokens associated with the specified device + List { + #[structopt(env = "FIDO2LUKS_DEVICE")] + device: PathBuf, + }, + /// 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: Option, + }, + /// Remove all unassigned tokens + GC { + #[structopt(env = "FIDO2LUKS_DEVICE")] + device: PathBuf, + }, } pub fn parse_cmdline() -> Args { @@ -371,7 +408,9 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret.salt.obtain(&secret.password_helper) } }; - let other_secret = |salt_q: &str, verify: bool| -> Fido2LuksResult<(Vec, Option)> { + let other_secret = |salt_q: &str, + verify: bool| + -> Fido2LuksResult<(Vec, Option)> { match other_secret { OtherSecret { keyfile: Some(file), @@ -386,7 +425,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { pin.as_deref(), ) .map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?), - _ => Ok((util::read_password(salt_q, verify)?.as_bytes().to_vec(), None)), + _ => Ok(( + util::read_password(salt_q, verify)?.as_bytes().to_vec(), + None, + )), } }; let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> { @@ -399,9 +441,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { }; // Non overlap match &args.command { - Command::AddKey { - exclusive, .. - } => { + Command::AddKey { exclusive, .. } => { let (existing_secret, _) = other_secret("Current password", false)?; let (new_secret, cred) = secret(true)?; let added_slot = luks::add_key( @@ -542,5 +582,61 @@ pub fn run_cli() -> Fido2LuksResult<()> { } _ => exit(1), }, + Command::Token(cmd) => match cmd { + TokenCommand::List { device } => { + let mut dev = LuksDevice::load(device)?; + let mut creds = Vec::new(); + for token in dev.tokens()? { + let (id, token) = token?; + for cred in token.credential.iter() { + if !creds.contains(cred) { + creds.push(cred.clone()); + } + } + println!( + "{}:\n\tSlots: {}\n\tCredentials: {}", + id, + if token.keyslots.is_empty() { + "None".into() + } else { + token.keyslots.join(",") + }, + token + .credential + .iter() + .map(|cred| format!( + "{} ({})", + cred, + creds.iter().position(|c| c == cred).unwrap().to_string() + )) + .collect::>() + .join(",") + ); + } + Ok(()) + } + TokenCommand::GC { device } => { + let mut dev = LuksDevice::load(device)?; + let mut creds: HashSet = HashSet::new(); + let mut remove = Vec::new(); + for token in dev.tokens()? { + let (id, token) = token?; + if token.keyslots.is_empty() { + creds.extend(token.credential); + remove.push(id); + } + } + for id in remove.iter().rev() { + dev.remove_token(*id)?; + } + println!( + "Removed {} tokens, affected credentials: {}", + remove.len(), + creds.into_iter().collect::>().join(",") + ); + Ok(()) + } + _ => unimplemented!(), + }, } } diff --git a/src/luks.rs b/src/luks.rs index 67a9540..387b81b 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -88,7 +88,9 @@ impl LuksDevice { } pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { - self.device.token_handle().json_set(TokenInput::RemoveToken(token))?; + self.device + .token_handle() + .json_set(TokenInput::RemoveToken(token))?; Ok(()) }