From 8f47f7a55699ec07da27e49bc3f92f93aea2d90f Mon Sep 17 00:00:00 2001 From: shimun Date: Fri, 19 Jun 2020 21:16:01 +0200 Subject: [PATCH 01/11] refractor luks code --- src/error.rs | 6 + src/luks.rs | 425 +++++++++++++++++++++++++++++---------------------- 2 files changed, 247 insertions(+), 184 deletions(-) diff --git a/src/error.rs b/src/error.rs index 18e3bee..0f7a34c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -75,6 +75,12 @@ impl LuksError { } } +impl From for Fido2LuksError { + fn from(e: LuksError) -> Self { + Fido2LuksError::LuksError { cause: e } + } +} + use libcryptsetup_rs::LibcryptErr; use std::io::ErrorKind; use std::string::FromUtf8Error; diff --git a/src/luks.rs b/src/luks.rs index c38359c..efdc0fc 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -7,18 +7,240 @@ use libcryptsetup_rs::{ use std::collections::{HashMap, HashSet}; use std::path::Path; -fn load_device_handle>(path: P) -> Fido2LuksResult { - let mut device = CryptInit::init(path.as_ref())?; - device.context_handle().load::<()>(None, None)?; - Ok(device) +struct LuksDevice { + device: CryptDevice, + luks2: Option, } -fn check_luks2(device: &mut CryptDevice) -> Fido2LuksResult<()> { - match device.format_handle().get_type()? { - EncryptionFormat::Luks2 => Ok(()), - _ => Err(Fido2LuksError::LuksError { - cause: LuksError::Luks2Required, - }), +impl LuksDevice { + pub fn load>(path: P) -> Fido2LuksResult { + let mut device = CryptInit::init(path.as_ref())?; + device.context_handle().load::<()>(None, None)?; + Ok(Self { + device, + luks2: None, + }) + } + + pub fn is_luks2(&mut self) -> Fido2LuksResult { + if let Some(luks2) = self.luks2 { + Ok(luks2) + } else { + self.luks2 = Some(match self.device.format_handle().get_type()? { + EncryptionFormat::Luks2 => true, + _ => false, + }); + self.is_luks2() + } + } + + pub fn tokens<'a>( + &'a mut self, + ) -> Fido2LuksResult> + 'a>> + { + Ok(Box::new((0..32).filter_map(move |i| { + let status = match self.device.token_handle().status(i) { + Ok(status) => status, + Err(err) => return Some(Err(Fido2LuksError::from(err))), + }; + match status { + CryptTokenInfo::Inactive => return None, + CryptTokenInfo::Internal(s) + | CryptTokenInfo::InternalUnknown(s) + | CryptTokenInfo::ExternalUnknown(s) + | CryptTokenInfo::External(s) + if &s != "fido2luks" => + { + return None + } + _ => (), + }; + let json = match self.device.token_handle().json_get(i) { + Ok(json) => json, + Err(err) => return Some(Err(Fido2LuksError::from(err))), + }; + let info: Fido2LuksToken = + match serde_json::from_value(json.clone()).map_err(|_| Fido2LuksError::LuksError { + cause: LuksError::InvalidToken(json.to_string()), + }) { + Ok(info) => info, + Err(err) => return Some(Err(Fido2LuksError::from(err))), + }; + Some(Ok((i, info))) + }))) + } + + pub fn find_token(&mut self, slot: u32) -> Fido2LuksResult> { + let slot_str = slot.to_string(); + for token in self.tokens()? { + let (id, token) = token?; + if token.keyslots.contains(&slot_str) { + return Ok(Some((id, token))); + } + } + Ok(None) + } + + pub fn add_key( + &mut self, + secret: &[u8], + old_secret: &[u8], + iteration_time: Option, + credential_id: Option<&[u8]>, + ) -> Fido2LuksResult { + if let Some(millis) = iteration_time { + self.device.settings_handle().set_iteration_time(millis) + } + let slot = self + .device + .keyslot_handle() + .add_by_passphrase(None, old_secret, secret)?; + if let Some(id) = credential_id { + self.device.token_handle().json_set(TokenInput::AddToken( + &serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(), + ))?; + } + + Ok(slot) + } + + pub fn remove_keyslots(&mut self, exclude: &[u32]) -> Fido2LuksResult { + let mut destroyed = 0; + let mut tokens = Vec::new(); + for slot in 0..256 { + match self.device.keyslot_handle().status(slot)? { + KeyslotInfo::Inactive => continue, + KeyslotInfo::Active | KeyslotInfo::ActiveLast if !exclude.contains(&slot) => { + if self.is_luks2()? { + if let Some((id, _token)) = self.find_token(slot)? { + tokens.push(id); + } + } + self.device.keyslot_handle().destroy(slot)?; + destroyed += 1; + } + KeyslotInfo::ActiveLast => break, + _ => (), + } + if self.device.keyslot_handle().status(slot)? == KeyslotInfo::ActiveLast { + break; + } + } + // Ensure indices stay valid + tokens.sort(); + for token in tokens.iter().rev() { + self.device + .token_handle() + .json_set(TokenInput::RemoveToken(*token))?; + } + Ok(destroyed) + } + + pub fn replace_key( + &mut self, + secret: &[u8], + old_secret: &[u8], + iteration_time: Option, + credential_id: Option<&[u8]>, + ) -> Fido2LuksResult { + if let Some(millis) = iteration_time { + self.device.settings_handle().set_iteration_time(millis) + } + // Use activate dry-run to locate keyslot + let slot = self.device.activate_handle().activate_by_passphrase( + None, + None, + old_secret, + CryptActivateFlags::empty(), + )?; + self.device.keyslot_handle().change_by_passphrase( + Some(slot), + Some(slot), + old_secret, + secret, + )? as u32; + if let Some(id) = credential_id { + if self.is_luks2()? { + let token = self.find_token(slot)?.map(|(t, _)| t); + let json = serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(); + if let Some(token) = token { + self.device + .token_handle() + .json_set(TokenInput::ReplaceToken(token, &json))?; + } else { + self.device + .token_handle() + .json_set(TokenInput::AddToken(&json))?; + } + } + } + Ok(slot) + } + + pub fn activate( + &mut self, + name: &str, + secret: &[u8], + slot_hint: Option, + ) -> Fido2LuksResult { + self.device + .activate_handle() + .activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty()) + .map_err(|_e| Fido2LuksError::WrongSecret) + } + + pub fn activate_token( + &mut self, + name: &str, + secret: impl Fn(Vec) -> Fido2LuksResult<([u8; 32], String)>, + slot_hint: Option, + ) -> Fido2LuksResult { + if !self.is_luks2()? { + return Err(LuksError::Luks2Required.into()); + } + let mut creds: HashMap> = HashMap::new(); + for token in self.tokens()? { + let token = match token { + Ok((_id, t)) => t, + _ => continue, // An corrupted token should't lock the user out + }; + let slots = || { + token + .keyslots + .iter() + .filter_map(|slot| slot.parse::().ok()) + }; + for cred in token.credential.iter() { + creds + .entry(cred.clone()) + .or_insert_with(|| slots().collect::>()) + .extend(slots()); + } + } + if creds.is_empty() { + return Err(Fido2LuksError::LuksError { + cause: LuksError::NoToken, + }); + } + let (secret, credential) = secret(creds.keys().cloned().collect())?; + let slots = creds.get(&credential).unwrap(); + let slots = slots + .iter() + .cloned() + .map(Option::Some) + .chain(std::iter::once(None).take(slots.is_empty() as usize)); + for slot in slots { + match self + .device + .activate_handle() + .activate_by_passphrase(Some(name), slot, &secret, CryptActivateFlags::empty()) + .map_err(LuksError::activate) + { + Err(Fido2LuksError::WrongSecret) => (), + res => return res, + } + } + self.activate(name, &secret, slot_hint) } } @@ -46,12 +268,9 @@ pub fn open_container>( secret: &[u8], slot_hint: Option, ) -> Fido2LuksResult<()> { - let mut device = load_device_handle(path)?; - device - .activate_handle() - .activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty()) - .map(|_slot| ()) - .map_err(|_e| Fido2LuksError::WrongSecret) + LuksDevice::load(path)? + .activate(name, secret, slot_hint) + .map(|_| ()) } pub fn open_container_token>( @@ -59,65 +278,9 @@ pub fn open_container_token>( name: &str, secret: impl Fn(Vec) -> Fido2LuksResult<([u8; 32], String)>, ) -> Fido2LuksResult<()> { - let mut device = load_device_handle(path)?; - check_luks2(&mut device)?; - - let mut creds = HashMap::new(); - for i in 0..256 { - let status = device.token_handle().status(i)?; - match status { - CryptTokenInfo::Inactive => break, - CryptTokenInfo::Internal(s) - | CryptTokenInfo::InternalUnknown(s) - | CryptTokenInfo::ExternalUnknown(s) - | CryptTokenInfo::External(s) - if &s != "fido2luks" => - { - continue - } - _ => (), - }; - let json = device.token_handle().json_get(i)?; - let info: Fido2LuksToken = - serde_json::from_value(json.clone()).map_err(|_| Fido2LuksError::LuksError { - cause: LuksError::InvalidToken(json.to_string()), - })?; - let slots = || { - info.keyslots - .iter() - .filter_map(|slot| slot.parse::().ok()) - }; - for cred in info.credential.iter().cloned() { - creds - .entry(cred) - .or_insert_with(|| slots().collect::>()) - .extend(slots()); - } - } - if creds.is_empty() { - return Err(Fido2LuksError::LuksError { - cause: LuksError::NoToken, - }); - } - let (secret, credential) = secret(creds.keys().cloned().collect())?; - let slots = creds.get(&credential).unwrap(); - let slots = slots - .iter() - .cloned() - .map(Option::Some) - .chain(std::iter::once(None).take(slots.is_empty() as usize)); - for slot in slots { - match device - .activate_handle() - .activate_by_passphrase(Some(name), slot, &secret, CryptActivateFlags::empty()) - .map(|_slot| ()) - .map_err(LuksError::activate) - { - Err(Fido2LuksError::WrongSecret) => (), - res => return res, - } - } - Err(Fido2LuksError::WrongSecret) + LuksDevice::load(path)? + .activate_token(name, secret, None) + .map(|_| ()) } pub fn add_key>( @@ -127,88 +290,11 @@ pub fn add_key>( iteration_time: Option, credential_id: Option<&[u8]>, ) -> Fido2LuksResult { - let mut device = load_device_handle(path)?; - if let Some(millis) = iteration_time { - device.settings_handle().set_iteration_time(millis) - } - let slot = device - .keyslot_handle() - .add_by_passphrase(None, old_secret, secret)?; - if let Some(id) = credential_id { - /* if let e @ Err(_) = check_luks2(&mut device) { - //rollback - device.keyslot_handle(Some(slot)).destroy()?; - return e.map(|_| 0u32); - }*/ - device.token_handle().json_set(TokenInput::AddToken( - &serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(), - ))?; - } - - Ok(slot) -} - -fn find_token( - device: &mut CryptDevice, - slot: u32, -) -> Fido2LuksResult> { - for i in 0..256 { - let status = device.token_handle().status(i)?; - match status { - CryptTokenInfo::Inactive => break, - CryptTokenInfo::Internal(s) - | CryptTokenInfo::InternalUnknown(s) - | CryptTokenInfo::ExternalUnknown(s) - | CryptTokenInfo::External(s) - if &s != "fido2luks" => - { - continue - } - _ => (), - }; - let json = device.token_handle().json_get(i)?; - let info: Fido2LuksToken = - serde_json::from_value(json.clone()).map_err(|_| Fido2LuksError::LuksError { - cause: LuksError::InvalidToken(json.to_string()), - })?; - if info.keyslots.contains(&slot.to_string()) { - return Ok(Some((i, info))); - } - } - Ok(None) + LuksDevice::load(path)?.add_key(secret, old_secret, iteration_time, credential_id) } pub fn remove_keyslots>(path: P, exclude: &[u32]) -> Fido2LuksResult { - let mut device = load_device_handle(path)?; - let mut destroyed = 0; - let mut tokens = Vec::new(); - for slot in 0..256 { - match device.keyslot_handle().status(slot)? { - KeyslotInfo::Inactive => continue, - KeyslotInfo::Active | KeyslotInfo::ActiveLast if !exclude.contains(&slot) => { - if let Ok(_) = check_luks2(&mut device) { - if let Some((token, _)) = find_token(&mut device, slot)? { - tokens.push(token); - } - } - device.keyslot_handle().destroy(slot)?; - destroyed += 1; - } - KeyslotInfo::ActiveLast => break, - _ => (), - } - if device.keyslot_handle().status(slot)? == KeyslotInfo::ActiveLast { - break; - } - } - // Ensure indices stay valid - tokens.sort(); - for token in tokens.iter().rev() { - device - .token_handle() - .json_set(TokenInput::RemoveToken(*token))?; - } - Ok(destroyed) + LuksDevice::load(path)?.remove_keyslots(exclude) } pub fn replace_key>( @@ -218,34 +304,5 @@ pub fn replace_key>( iteration_time: Option, credential_id: Option<&[u8]>, ) -> Fido2LuksResult { - let mut device = load_device_handle(path)?; - if let Some(millis) = iteration_time { - device.settings_handle().set_iteration_time(millis) - } - // Use activate dry-run to locate keyslot - let slot = device.activate_handle().activate_by_passphrase( - None, - None, - old_secret, - CryptActivateFlags::empty(), - )?; - device - .keyslot_handle() - .change_by_passphrase(Some(slot), Some(slot), old_secret, secret)? as u32; - if let Some(id) = credential_id { - if check_luks2(&mut device).is_ok() { - let token = find_token(&mut device, slot)?.map(|(t, _)| t); - let json = serde_json::to_value(&Fido2LuksToken::new(id, slot)).unwrap(); - if let Some(token) = token { - device - .token_handle() - .json_set(TokenInput::ReplaceToken(token, &json))?; - } else { - device - .token_handle() - .json_set(TokenInput::AddToken(&json))?; - } - } - } - Ok(slot) + LuksDevice::load(path)?.replace_key(secret, old_secret, iteration_time, credential_id) } -- 2.49.0 From 6c2f5fc7ec9ca08895a349ceb185d59dc6f2e4c7 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 21 Jun 2020 19:11:04 +0200 Subject: [PATCH 02/11] broaden luks api --- src/luks.rs | 100 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/src/luks.rs b/src/luks.rs index efdc0fc..67a9540 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -7,7 +7,7 @@ use libcryptsetup_rs::{ use std::collections::{HashMap, HashSet}; use std::path::Path; -struct LuksDevice { +pub struct LuksDevice { device: CryptDevice, luks2: Option, } @@ -38,36 +38,42 @@ impl LuksDevice { &'a mut self, ) -> Fido2LuksResult> + 'a>> { - Ok(Box::new((0..32).filter_map(move |i| { - let status = match self.device.token_handle().status(i) { - Ok(status) => status, - Err(err) => return Some(Err(Fido2LuksError::from(err))), - }; - match status { - CryptTokenInfo::Inactive => return None, - CryptTokenInfo::Internal(s) - | CryptTokenInfo::InternalUnknown(s) - | CryptTokenInfo::ExternalUnknown(s) - | CryptTokenInfo::External(s) - if &s != "fido2luks" => - { - return None - } - _ => (), - }; - let json = match self.device.token_handle().json_get(i) { - Ok(json) => json, - Err(err) => return Some(Err(Fido2LuksError::from(err))), - }; - let info: Fido2LuksToken = - match serde_json::from_value(json.clone()).map_err(|_| Fido2LuksError::LuksError { - cause: LuksError::InvalidToken(json.to_string()), - }) { - Ok(info) => info, - Err(err) => return Some(Err(Fido2LuksError::from(err))), - }; - Some(Ok((i, info))) - }))) + Ok(Box::new( + (0..32) + .map(move |i| { + let status = match self.device.token_handle().status(i) { + Ok(status) => status, + Err(err) => return Some(Err(Fido2LuksError::from(err))), + }; + match status { + CryptTokenInfo::Inactive => return None, + CryptTokenInfo::Internal(s) + | CryptTokenInfo::InternalUnknown(s) + | CryptTokenInfo::ExternalUnknown(s) + | CryptTokenInfo::External(s) + if &s != Fido2LuksToken::default_type() => + { + return None + } + _ => (), + }; + let json = match self.device.token_handle().json_get(i) { + Ok(json) => json, + Err(err) => return Some(Err(Fido2LuksError::from(err))), + }; + let info: Fido2LuksToken = + match serde_json::from_value(json.clone()).map_err(|_| { + Fido2LuksError::LuksError { + cause: LuksError::InvalidToken(json.to_string()), + } + }) { + Ok(info) => info, + Err(err) => return Some(Err(Fido2LuksError::from(err))), + }; + Some(Ok((i, info))) + }) + .filter_map(|o| o), + )) } pub fn find_token(&mut self, slot: u32) -> Fido2LuksResult> { @@ -81,6 +87,11 @@ impl LuksDevice { Ok(None) } + pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { + self.device.token_handle().json_set(TokenInput::RemoveToken(token))?; + Ok(()) + } + pub fn add_key( &mut self, secret: &[u8], @@ -129,9 +140,7 @@ impl LuksDevice { // Ensure indices stay valid tokens.sort(); for token in tokens.iter().rev() { - self.device - .token_handle() - .json_set(TokenInput::RemoveToken(*token))?; + self.remove_token(*token)?; } Ok(destroyed) } @@ -245,19 +254,32 @@ impl LuksDevice { } #[derive(Debug, Clone, Serialize, Deserialize)] -struct Fido2LuksToken { +pub struct Fido2LuksToken { #[serde(rename = "type")] - type_: String, - credential: Vec, - keyslots: Vec, + pub type_: String, + pub credential: Vec, + pub keyslots: Vec, } impl Fido2LuksToken { fn new(credential_id: impl AsRef<[u8]>, slot: u32) -> Self { Self { - type_: "fido2luks\0".into(), // Doubles as c style string credential: vec![hex::encode(credential_id)], keyslots: vec![slot.to_string()], + ..Default::default() + } + } + fn default_type() -> &'static str { + "fido2luks" + } +} + +impl Default for Fido2LuksToken { + fn default() -> Self { + Self { + type_: Self::default_type().into(), + credential: Vec::new(), + keyslots: Vec::new(), } } } -- 2.49.0 From ce43cf857dbd49ba8f2c315dd212816b59e3f0a9 Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 21 Jun 2020 19:11:29 +0200 Subject: [PATCH 03/11] basic token operations --- src/cli.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++++++--- src/luks.rs | 4 +- 2 files changed, 104 insertions(+), 6 deletions(-) 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(()) } -- 2.49.0 From e3bd32c985acaf839f81fefe467e58699501b0cc Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 21 Jun 2020 19:41:00 +0200 Subject: [PATCH 04/11] more operations --- src/cli.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/luks.rs | 22 +++++++++++++----- 2 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index ef6f334..20cd88c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -312,7 +312,7 @@ pub enum TokenCommand { credentials: Credentials, /// Token from which the credentials will be removed #[structopt(long = "token")] - token: Option, + token_id: Option, }, /// Remove all unassigned tokens GC { @@ -599,7 +599,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { if token.keyslots.is_empty() { "None".into() } else { - token.keyslots.join(",") + token.keyslots.iter().cloned().collect::>().join(",") }, token .credential @@ -615,6 +615,67 @@ pub fn run_cli() -> Fido2LuksResult<()> { } Ok(()) } + TokenCommand::Add { + device, + credentials, + slot, + } => { + let mut dev = LuksDevice::load(device)?; + let mut tokens = Vec::new(); + for token in dev.tokens()? { + let (id, token) = token?; + if token.keyslots.contains(&slot.to_string()) { + tokens.push((id, token)); + } + } + if tokens.is_empty() { + unimplemented!("// TODO: create new token") + } + let count = tokens.len(); + for (id, mut token) in tokens { + token + .credential + .extend(credentials.ids.0.iter().map(|h| h.to_string())); + dev.update_token(id, &token)?; + } + println!("Updated {} tokens", count); + Ok(()) + } + TokenCommand::Remove { + device, + credentials, + token_id, + } => { + let mut dev = LuksDevice::load(device)?; + let mut tokens = Vec::new(); + for token in dev.tokens()? { + let (id, token) = token?; + if let Some(token_id) = token_id { + if id == *token_id { + tokens.push((id, token)); + } + } else { + tokens.push((id, token)); + } + } + let count = tokens.len(); + for (id, mut token) in tokens { + token.credential = token + .credential + .into_iter() + .filter(|cred| { + !credentials + .ids + .0 + .iter() + .any(|h| &h.0[..] == cred.as_bytes()) + }) + .collect(); + dev.update_token(id, &token)?; + } + println!("Updated {} tokens", count); + Ok(()) + } TokenCommand::GC { device } => { let mut dev = LuksDevice::load(device)?; let mut creds: HashSet = HashSet::new(); @@ -636,7 +697,6 @@ pub fn run_cli() -> Fido2LuksResult<()> { ); Ok(()) } - _ => unimplemented!(), }, } } diff --git a/src/luks.rs b/src/luks.rs index 387b81b..ce96c48 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -94,6 +94,16 @@ impl LuksDevice { Ok(()) } + pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> { + self.device + .token_handle() + .json_set(TokenInput::ReplaceToken( + token, + &serde_json::to_value(&data).unwrap(), + ))?; + Ok(()) + } + pub fn add_key( &mut self, secret: &[u8], @@ -259,15 +269,15 @@ impl LuksDevice { pub struct Fido2LuksToken { #[serde(rename = "type")] pub type_: String, - pub credential: Vec, - pub keyslots: Vec, + pub credential: HashSet, + pub keyslots: HashSet, } impl Fido2LuksToken { fn new(credential_id: impl AsRef<[u8]>, slot: u32) -> Self { Self { - credential: vec![hex::encode(credential_id)], - keyslots: vec![slot.to_string()], + credential: vec![hex::encode(credential_id)].into_iter().collect(), + keyslots: vec![slot.to_string()].into_iter().collect(), ..Default::default() } } @@ -280,8 +290,8 @@ impl Default for Fido2LuksToken { fn default() -> Self { Self { type_: Self::default_type().into(), - credential: Vec::new(), - keyslots: Vec::new(), + credential: HashSet::new(), + keyslots: HashSet::new(), } } } -- 2.49.0 From eed2dad08fc86ed028e55767ab31e53f49121acc Mon Sep 17 00:00:00 2001 From: shimun Date: Sun, 21 Jun 2020 22:28:34 +0200 Subject: [PATCH 05/11] create new token if none exists --- src/cli.rs | 18 +++++++++++++----- src/luks.rs | 23 ++++++++++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 20cd88c..0e0de73 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::luks::LuksDevice; +use crate::luks::{Fido2LuksToken, LuksDevice}; use crate::util::sha256; use std::borrow::Cow; use std::collections::HashSet; @@ -27,6 +27,12 @@ impl Display for HexEncoded { } } +impl AsRef<[u8]> for HexEncoded { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} + impl FromStr for HexEncoded { type Err = hex::FromHexError; @@ -628,10 +634,12 @@ pub fn run_cli() -> Fido2LuksResult<()> { tokens.push((id, token)); } } - if tokens.is_empty() { - unimplemented!("// TODO: create new token") - } - let count = tokens.len(); + let count = if tokens.is_empty() { + dev.add_token(&Fido2LuksToken::with_credentials(&credentials.ids.0, *slot))?; + 1 + } else { + tokens.len() + }; for (id, mut token) in tokens { token .credential diff --git a/src/luks.rs b/src/luks.rs index ce96c48..2c6b9df 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -87,6 +87,13 @@ impl LuksDevice { Ok(None) } + pub fn add_token(&mut self, data: &Fido2LuksToken) -> Fido2LuksResult<()> { + self.device + .token_handle() + .json_set(TokenInput::AddToken(&serde_json::to_value(&data).unwrap()))?; + Ok(()) + } + pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { self.device .token_handle() @@ -274,14 +281,24 @@ pub struct Fido2LuksToken { } impl Fido2LuksToken { - fn new(credential_id: impl AsRef<[u8]>, slot: u32) -> Self { + pub fn new(credential_id: impl AsRef<[u8]>, slot: u32) -> Self { + Self::with_credentials(std::iter::once(credential_id), slot) + } + + pub fn with_credentials, B: AsRef<[u8]>>( + credentials: I, + slot: u32, + ) -> Self { Self { - credential: vec![hex::encode(credential_id)].into_iter().collect(), + credential: credentials + .into_iter() + .map(|cred| hex::encode(cred.as_ref())) + .collect(), keyslots: vec![slot.to_string()].into_iter().collect(), ..Default::default() } } - fn default_type() -> &'static str { + pub fn default_type() -> &'static str { "fido2luks" } } -- 2.49.0 From 36dc36d4546e933e834498697b7e8dd88a83ab8f Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 22 Jun 2020 02:30:35 +0200 Subject: [PATCH 06/11] compare str == str instead of str bytes == bytes --- src/cli.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 0e0de73..5082aba 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -671,13 +671,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { token.credential = token .credential .into_iter() - .filter(|cred| { - !credentials - .ids - .0 - .iter() - .any(|h| &h.0[..] == cred.as_bytes()) - }) + .filter(|cred| !credentials.ids.0.iter().any(|h| &h.to_string() == cred)) .collect(); dev.update_token(id, &token)?; } @@ -690,7 +684,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { let mut remove = Vec::new(); for token in dev.tokens()? { let (id, token) = token?; - if token.keyslots.is_empty() { + if token.keyslots.is_empty() || token.credential.is_empty() { creds.extend(token.credential); remove.push(id); } -- 2.49.0 From 85e4a3042021caddcb2f20dc972e8cfc15153af9 Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 22 Jun 2020 16:24:25 +0200 Subject: [PATCH 07/11] check for luks2 --- src/luks.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/luks.rs b/src/luks.rs index 2c6b9df..f9ab0a9 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -38,6 +38,9 @@ impl LuksDevice { &'a mut self, ) -> Fido2LuksResult> + 'a>> { + if !self.is_luks2()? { + return Err(LuksError::Luks2Required.into()); + } Ok(Box::new( (0..32) .map(move |i| { -- 2.49.0 From 95fb630a0bccf1f7a51b5997676294e9cacda120 Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 22 Jun 2020 17:46:13 +0200 Subject: [PATCH 08/11] move luks2 check into own fn --- src/luks.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/luks.rs b/src/luks.rs index f9ab0a9..019ffa2 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -34,13 +34,18 @@ impl LuksDevice { } } + fn require_luks2(&mut self) -> Fido2LuksResult<()> { + if !self.is_luks2()? { + return Err(LuksError::Luks2Required.into()); + } + Ok(()) + } + pub fn tokens<'a>( &'a mut self, ) -> Fido2LuksResult> + 'a>> { - if !self.is_luks2()? { - return Err(LuksError::Luks2Required.into()); - } + self.require_luks2()?; Ok(Box::new( (0..32) .map(move |i| { @@ -91,6 +96,7 @@ impl LuksDevice { } pub fn add_token(&mut self, data: &Fido2LuksToken) -> Fido2LuksResult<()> { + self.require_luks2()?; self.device .token_handle() .json_set(TokenInput::AddToken(&serde_json::to_value(&data).unwrap()))?; @@ -98,6 +104,7 @@ impl LuksDevice { } pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { + self.require_luks2()?; self.device .token_handle() .json_set(TokenInput::RemoveToken(token))?; @@ -105,6 +112,7 @@ impl LuksDevice { } pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> { + self.require_luks2()?; self.device .token_handle() .json_set(TokenInput::ReplaceToken( -- 2.49.0 From e28828cc2bf565131122d1ef9f38541b144e6cba Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 22 Jun 2020 18:06:06 +0200 Subject: [PATCH 09/11] cleanup luks.rs --- src/cli.rs | 24 ++++++++----------- src/luks.rs | 66 ++++++++++++----------------------------------------- 2 files changed, 24 insertions(+), 66 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 5082aba..693ec43 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,5 +1,4 @@ use crate::error::*; -use crate::luks; use crate::*; use structopt::StructOpt; @@ -445,20 +444,20 @@ pub fn run_cli() -> Fido2LuksResult<()> { pin.as_deref(), ) }; + let mut luks_dev = LuksDevice::load(&luks.device)?; // Non overlap match &args.command { Command::AddKey { exclusive, .. } => { let (existing_secret, _) = other_secret("Current password", false)?; let (new_secret, cred) = secret(true)?; - let added_slot = luks::add_key( - &luks.device, + let added_slot = luks_dev.add_key( &new_secret, &existing_secret[..], luks_mod.kdf_time.or(Some(10)), Some(&cred.id[..]).filter(|_| *token), )?; if *exclusive { - let destroyed = luks::remove_keyslots(&luks.device, &[added_slot])?; + let destroyed = luks_dev.remove_keyslots(&[added_slot])?; println!( "Added to key to device {}, slot: {}\nRemoved {} old keys", luks.device.display(), @@ -478,16 +477,14 @@ pub fn run_cli() -> Fido2LuksResult<()> { let (existing_secret, _) = secret(false)?; let (replacement_secret, cred) = other_secret("Replacement password", true)?; let slot = if *add_password { - luks::add_key( - &luks.device, + luks_dev.add_key( &replacement_secret[..], &existing_secret, luks_mod.kdf_time, cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]), ) } else { - luks::replace_key( - &luks.device, + luks_dev.replace_key( &replacement_secret[..], &existing_secret, luks_mod.kdf_time, @@ -545,14 +542,12 @@ pub fn run_cli() -> Fido2LuksResult<()> { }; let mut retries = *retries; + let mut luks_dev = LuksDevice::load(&luks.device)?; loop { let secret = match &args.command { Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0)) - .and_then(|(secret, _cred)| { - luks::open_container(&luks.device, &name, &secret, luks.slot) - }), - Command::OpenToken { .. } => luks::open_container_token( - &luks.device, + .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)), + Command::OpenToken { .. } => luks_dev.activate_token( &name, Box::new(|credentials: Vec| { let creds = credentials @@ -562,6 +557,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { secret(Cow::Owned(creds)) .map(|(secret, cred)| (secret, hex::encode(&cred.id))) }), + luks.slot, ), _ => unreachable!(), }; @@ -577,7 +573,7 @@ pub fn run_cli() -> Fido2LuksResult<()> { retries -= 1; eprintln!("{}", e); } - res => break res, + res => break res.map(|_| ()), } } } diff --git a/src/luks.rs b/src/luks.rs index 019ffa2..e381a7e 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -262,12 +262,19 @@ impl LuksDevice { }); } let (secret, credential) = secret(creds.keys().cloned().collect())?; - let slots = creds.get(&credential).unwrap(); - let slots = slots - .iter() - .cloned() - .map(Option::Some) - .chain(std::iter::once(None).take(slots.is_empty() as usize)); + let slots = if let Some(slots) = creds.get(&credential) { + slots + } else { + return Err(Fido2LuksError::LuksError { + cause: LuksError::NoToken, + }); + }; + //Try slots associated with the credential used + let slots = slots.iter().cloned().map(Option::Some).chain( + std::iter::once(slot_hint) // Try slot hint if there is one + .take(slot_hint.is_some() as usize) + .chain(std::iter::once(None).take(slots.is_empty() as usize)), // Try all slots as last resort + ); for slot in slots { match self .device @@ -279,7 +286,7 @@ impl LuksDevice { res => return res, } } - self.activate(name, &secret, slot_hint) + Err(Fido2LuksError::WrongSecret) } } @@ -323,48 +330,3 @@ impl Default for Fido2LuksToken { } } } - -pub fn open_container>( - path: P, - name: &str, - secret: &[u8], - slot_hint: Option, -) -> Fido2LuksResult<()> { - LuksDevice::load(path)? - .activate(name, secret, slot_hint) - .map(|_| ()) -} - -pub fn open_container_token>( - path: P, - name: &str, - secret: impl Fn(Vec) -> Fido2LuksResult<([u8; 32], String)>, -) -> Fido2LuksResult<()> { - LuksDevice::load(path)? - .activate_token(name, secret, None) - .map(|_| ()) -} - -pub fn add_key>( - path: P, - secret: &[u8], - old_secret: &[u8], - iteration_time: Option, - credential_id: Option<&[u8]>, -) -> Fido2LuksResult { - LuksDevice::load(path)?.add_key(secret, old_secret, iteration_time, credential_id) -} - -pub fn remove_keyslots>(path: P, exclude: &[u32]) -> Fido2LuksResult { - LuksDevice::load(path)?.remove_keyslots(exclude) -} - -pub fn replace_key>( - path: P, - secret: &[u8], - old_secret: &[u8], - iteration_time: Option, - credential_id: Option<&[u8]>, -) -> Fido2LuksResult { - LuksDevice::load(path)?.replace_key(secret, old_secret, iteration_time, credential_id) -} -- 2.49.0 From 5cb3982d6544746f3a8588cc7231ef3610fb96a3 Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 22 Jun 2020 22:30:12 +0200 Subject: [PATCH 10/11] handle additional credentials --- src/luks.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/luks.rs b/src/luks.rs index e381a7e..6996eef 100644 --- a/src/luks.rs +++ b/src/luks.rs @@ -225,7 +225,7 @@ impl LuksDevice { self.device .activate_handle() .activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty()) - .map_err(|_e| Fido2LuksError::WrongSecret) + .map_err(LuksError::activate) } pub fn activate_token( @@ -262,12 +262,12 @@ impl LuksDevice { }); } let (secret, credential) = secret(creds.keys().cloned().collect())?; + let empty; let slots = if let Some(slots) = creds.get(&credential) { slots } else { - return Err(Fido2LuksError::LuksError { - cause: LuksError::NoToken, - }); + empty = HashSet::new(); + &empty }; //Try slots associated with the credential used let slots = slots.iter().cloned().map(Option::Some).chain( @@ -276,12 +276,7 @@ impl LuksDevice { .chain(std::iter::once(None).take(slots.is_empty() as usize)), // Try all slots as last resort ); for slot in slots { - match self - .device - .activate_handle() - .activate_by_passphrase(Some(name), slot, &secret, CryptActivateFlags::empty()) - .map_err(LuksError::activate) - { + match self.activate(name, &secret, slot) { Err(Fido2LuksError::WrongSecret) => (), res => return res, } -- 2.49.0 From 2bc0e2d64a89fcba4d8463f6874922680a9e0f63 Mon Sep 17 00:00:00 2001 From: shimun Date: Mon, 22 Jun 2020 22:32:49 +0200 Subject: [PATCH 11/11] add flag to print credentials as csv --- src/cli.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/cli.rs b/src/cli.rs index 693ec43..c392c83 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -66,7 +66,7 @@ impl FromStr for CommaSeparated { #[derive(Debug, StructOpt)] pub struct Credentials { - /// FIDO credential ids, seperated by ',' generate using fido2luks credential + /// FIDO credential ids, separated by ',' generate using fido2luks credential #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] pub ids: CommaSeparated, } @@ -292,12 +292,16 @@ pub enum Command { 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 { @@ -585,7 +589,10 @@ pub fn run_cli() -> Fido2LuksResult<()> { _ => exit(1), }, Command::Token(cmd) => match cmd { - TokenCommand::List { device } => { + TokenCommand::List { + device, + csv: dump_credentials, + } => { let mut dev = LuksDevice::load(device)?; let mut creds = Vec::new(); for token in dev.tokens()? { @@ -593,8 +600,14 @@ pub fn run_cli() -> Fido2LuksResult<()> { for cred in token.credential.iter() { if !creds.contains(cred) { creds.push(cred.clone()); + if *dump_credentials { + print!("{}{}", if creds.len() == 1 { "" } else { "," }, cred); + } } } + if *dump_credentials { + continue; + } println!( "{}:\n\tSlots: {}\n\tCredentials: {}", id, @@ -615,6 +628,9 @@ pub fn run_cli() -> Fido2LuksResult<()> { .join(",") ); } + if *dump_credentials { + println!(); + } Ok(()) } TokenCommand::Add { -- 2.49.0