refactor luks code #1

Merged
shimun merged 11 commits from luks_refr into master 2020-06-22 22:47:21 +02:00
3 changed files with 496 additions and 244 deletions

View File

@ -1,5 +1,4 @@
use crate::error::*; use crate::error::*;
use crate::luks;
use crate::*; use crate::*;
use structopt::StructOpt; use structopt::StructOpt;
@ -12,8 +11,10 @@ use std::io::Write;
use std::process::exit; use std::process::exit;
use std::thread; use std::thread;
use crate::luks::{Fido2LuksToken, LuksDevice};
use crate::util::sha256; use crate::util::sha256;
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashSet;
use std::time::SystemTime; use std::time::SystemTime;
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
@ -25,6 +26,12 @@ impl Display for HexEncoded {
} }
} }
impl AsRef<[u8]> for HexEncoded {
fn as_ref(&self) -> &[u8] {
&self.0[..]
}
}
impl FromStr for HexEncoded { impl FromStr for HexEncoded {
type Err = hex::FromHexError; type Err = hex::FromHexError;
@ -59,7 +66,7 @@ impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub struct Credentials { 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")] #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub ids: CommaSeparated<HexEncoded>, pub ids: CommaSeparated<HexEncoded>,
} }
@ -282,6 +289,45 @@ pub enum Command {
/// Check if an authenticator is connected /// Check if an authenticator is connected
#[structopt(name = "connected")] #[structopt(name = "connected")]
Connected, Connected,
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 {
#[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<u32>,
},
/// Remove all unassigned tokens
GC {
#[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf,
},
} }
pub fn parse_cmdline() -> Args { pub fn parse_cmdline() -> Args {
@ -371,7 +417,9 @@ pub fn run_cli() -> Fido2LuksResult<()> {
secret.salt.obtain(&secret.password_helper) secret.salt.obtain(&secret.password_helper)
} }
}; };
let other_secret = |salt_q: &str, verify: bool| -> Fido2LuksResult<(Vec<u8>, Option<FidoCredential>)> { let other_secret = |salt_q: &str,
verify: bool|
-> Fido2LuksResult<(Vec<u8>, Option<FidoCredential>)> {
match other_secret { match other_secret {
OtherSecret { OtherSecret {
keyfile: Some(file), keyfile: Some(file),
@ -386,7 +434,10 @@ pub fn run_cli() -> Fido2LuksResult<()> {
pin.as_deref(), pin.as_deref(),
) )
.map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?), .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)> { let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> {
@ -397,22 +448,20 @@ pub fn run_cli() -> Fido2LuksResult<()> {
pin.as_deref(), pin.as_deref(),
) )
}; };
let mut luks_dev = LuksDevice::load(&luks.device)?;
// Non overlap // Non overlap
match &args.command { match &args.command {
Command::AddKey { Command::AddKey { exclusive, .. } => {
exclusive, ..
} => {
let (existing_secret, _) = other_secret("Current password", false)?; let (existing_secret, _) = other_secret("Current password", false)?;
let (new_secret, cred) = secret(true)?; let (new_secret, cred) = secret(true)?;
let added_slot = luks::add_key( let added_slot = luks_dev.add_key(
&luks.device,
&new_secret, &new_secret,
&existing_secret[..], &existing_secret[..],
luks_mod.kdf_time.or(Some(10)), luks_mod.kdf_time.or(Some(10)),
Some(&cred.id[..]).filter(|_| *token), Some(&cred.id[..]).filter(|_| *token),
)?; )?;
if *exclusive { if *exclusive {
let destroyed = luks::remove_keyslots(&luks.device, &[added_slot])?; let destroyed = luks_dev.remove_keyslots(&[added_slot])?;
println!( println!(
"Added to key to device {}, slot: {}\nRemoved {} old keys", "Added to key to device {}, slot: {}\nRemoved {} old keys",
luks.device.display(), luks.device.display(),
@ -432,16 +481,14 @@ pub fn run_cli() -> Fido2LuksResult<()> {
let (existing_secret, _) = secret(false)?; let (existing_secret, _) = secret(false)?;
let (replacement_secret, cred) = other_secret("Replacement password", true)?; let (replacement_secret, cred) = other_secret("Replacement password", true)?;
let slot = if *add_password { let slot = if *add_password {
luks::add_key( luks_dev.add_key(
&luks.device,
&replacement_secret[..], &replacement_secret[..],
&existing_secret, &existing_secret,
luks_mod.kdf_time, luks_mod.kdf_time,
cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]), cred.as_ref().filter(|_| *token).map(|cred| &cred.id[..]),
) )
} else { } else {
luks::replace_key( luks_dev.replace_key(
&luks.device,
&replacement_secret[..], &replacement_secret[..],
&existing_secret, &existing_secret,
luks_mod.kdf_time, luks_mod.kdf_time,
@ -499,14 +546,12 @@ pub fn run_cli() -> Fido2LuksResult<()> {
}; };
let mut retries = *retries; let mut retries = *retries;
let mut luks_dev = LuksDevice::load(&luks.device)?;
loop { loop {
let secret = match &args.command { let secret = match &args.command {
Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0)) Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0))
.and_then(|(secret, _cred)| { .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)),
luks::open_container(&luks.device, &name, &secret, luks.slot) Command::OpenToken { .. } => luks_dev.activate_token(
}),
Command::OpenToken { .. } => luks::open_container_token(
&luks.device,
&name, &name,
Box::new(|credentials: Vec<String>| { Box::new(|credentials: Vec<String>| {
let creds = credentials let creds = credentials
@ -516,6 +561,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
secret(Cow::Owned(creds)) secret(Cow::Owned(creds))
.map(|(secret, cred)| (secret, hex::encode(&cred.id))) .map(|(secret, cred)| (secret, hex::encode(&cred.id)))
}), }),
luks.slot,
), ),
_ => unreachable!(), _ => unreachable!(),
}; };
@ -531,7 +577,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
retries -= 1; retries -= 1;
eprintln!("{}", e); eprintln!("{}", e);
} }
res => break res, res => break res.map(|_| ()),
} }
} }
} }
@ -542,5 +588,129 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} }
_ => exit(1), _ => exit(1),
}, },
Command::Token(cmd) => match cmd {
TokenCommand::List {
device,
csv: dump_credentials,
} => {
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());
if *dump_credentials {
print!("{}{}", if creds.len() == 1 { "" } else { "," }, cred);
}
}
}
if *dump_credentials {
continue;
}
println!(
"{}:\n\tSlots: {}\n\tCredentials: {}",
id,
if token.keyslots.is_empty() {
"None".into()
} else {
token.keyslots.iter().cloned().collect::<Vec<_>>().join(",")
},
token
.credential
.iter()
.map(|cred| format!(
"{} ({})",
cred,
creds.iter().position(|c| c == cred).unwrap().to_string()
))
.collect::<Vec<_>>()
.join(",")
);
}
if *dump_credentials {
println!();
}
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));
}
}
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
.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.to_string() == cred))
.collect();
dev.update_token(id, &token)?;
}
println!("Updated {} tokens", count);
Ok(())
}
TokenCommand::GC { device } => {
let mut dev = LuksDevice::load(device)?;
let mut creds: HashSet<String> = HashSet::new();
let mut remove = Vec::new();
for token in dev.tokens()? {
let (id, token) = token?;
if token.keyslots.is_empty() || token.credential.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::<Vec<_>>().join(",")
);
Ok(())
}
},
} }
} }

View File

@ -75,6 +75,12 @@ impl LuksError {
} }
} }
impl From<LuksError> for Fido2LuksError {
fn from(e: LuksError) -> Self {
Fido2LuksError::LuksError { cause: e }
}
}
use libcryptsetup_rs::LibcryptErr; use libcryptsetup_rs::LibcryptErr;
use std::io::ErrorKind; use std::io::ErrorKind;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;

View File

@ -7,245 +7,321 @@ use libcryptsetup_rs::{
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::path::Path; use std::path::Path;
fn load_device_handle<P: AsRef<Path>>(path: P) -> Fido2LuksResult<CryptDevice> { pub struct LuksDevice {
let mut device = CryptInit::init(path.as_ref())?; device: CryptDevice,
device.context_handle().load::<()>(None, None)?; luks2: Option<bool>,
Ok(device)
} }
fn check_luks2(device: &mut CryptDevice) -> Fido2LuksResult<()> { impl LuksDevice {
match device.format_handle().get_type()? { pub fn load<P: AsRef<Path>>(path: P) -> Fido2LuksResult<LuksDevice> {
EncryptionFormat::Luks2 => Ok(()), let mut device = CryptInit::init(path.as_ref())?;
_ => Err(Fido2LuksError::LuksError { device.context_handle().load::<()>(None, None)?;
cause: LuksError::Luks2Required, Ok(Self {
}), device,
luks2: None,
})
}
pub fn is_luks2(&mut self) -> Fido2LuksResult<bool> {
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()
}
}
fn require_luks2(&mut self) -> Fido2LuksResult<()> {
if !self.is_luks2()? {
return Err(LuksError::Luks2Required.into());
}
Ok(())
}
pub fn tokens<'a>(
&'a mut self,
) -> Fido2LuksResult<Box<dyn Iterator<Item = Fido2LuksResult<(u32, Fido2LuksToken)>> + 'a>>
{
self.require_luks2()?;
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<Option<(u32, Fido2LuksToken)>> {
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_token(&mut self, data: &Fido2LuksToken) -> Fido2LuksResult<()> {
self.require_luks2()?;
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.require_luks2()?;
self.device
.token_handle()
.json_set(TokenInput::RemoveToken(token))?;
Ok(())
}
pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> {
self.require_luks2()?;
self.device
.token_handle()
.json_set(TokenInput::ReplaceToken(
token,
&serde_json::to_value(&data).unwrap(),
))?;
Ok(())
}
pub fn add_key(
&mut self,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
credential_id: Option<&[u8]>,
) -> Fido2LuksResult<u32> {
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<u32> {
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.remove_token(*token)?;
}
Ok(destroyed)
}
pub fn replace_key(
&mut self,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
credential_id: Option<&[u8]>,
) -> Fido2LuksResult<u32> {
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<u32>,
) -> Fido2LuksResult<u32> {
self.device
.activate_handle()
.activate_by_passphrase(Some(name), slot_hint, secret, CryptActivateFlags::empty())
.map_err(LuksError::activate)
}
pub fn activate_token(
&mut self,
name: &str,
secret: impl Fn(Vec<String>) -> Fido2LuksResult<([u8; 32], String)>,
slot_hint: Option<u32>,
) -> Fido2LuksResult<u32> {
if !self.is_luks2()? {
return Err(LuksError::Luks2Required.into());
}
let mut creds: HashMap<String, HashSet<u32>> = 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::<u32>().ok())
};
for cred in token.credential.iter() {
creds
.entry(cred.clone())
.or_insert_with(|| slots().collect::<HashSet<u32>>())
.extend(slots());
}
}
if creds.is_empty() {
return Err(Fido2LuksError::LuksError {
cause: LuksError::NoToken,
});
}
let (secret, credential) = secret(creds.keys().cloned().collect())?;
let empty;
let slots = if let Some(slots) = creds.get(&credential) {
slots
} else {
empty = HashSet::new();
&empty
};
//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.activate(name, &secret, slot) {
Err(Fido2LuksError::WrongSecret) => (),
res => return res,
}
}
Err(Fido2LuksError::WrongSecret)
} }
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
struct Fido2LuksToken { pub struct Fido2LuksToken {
#[serde(rename = "type")] #[serde(rename = "type")]
type_: String, pub type_: String,
credential: Vec<String>, pub credential: HashSet<String>,
keyslots: Vec<String>, pub keyslots: HashSet<String>,
} }
impl 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<I: IntoIterator<Item = B>, B: AsRef<[u8]>>(
credentials: I,
slot: u32,
) -> Self {
Self { Self {
type_: "fido2luks\0".into(), // Doubles as c style string credential: credentials
credential: vec![hex::encode(credential_id)], .into_iter()
keyslots: vec![slot.to_string()], .map(|cred| hex::encode(cred.as_ref()))
.collect(),
keyslots: vec![slot.to_string()].into_iter().collect(),
..Default::default()
} }
} }
pub fn default_type() -> &'static str {
"fido2luks"
}
} }
pub fn open_container<P: AsRef<Path>>( impl Default for Fido2LuksToken {
path: P, fn default() -> Self {
name: &str, Self {
secret: &[u8], type_: Self::default_type().into(),
slot_hint: Option<u32>, credential: HashSet::new(),
) -> Fido2LuksResult<()> { keyslots: HashSet::new(),
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)
}
pub fn open_container_token<P: AsRef<Path>>(
path: P,
name: &str,
secret: impl Fn(Vec<String>) -> 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::<u32>().ok())
};
for cred in info.credential.iter().cloned() {
creds
.entry(cred)
.or_insert_with(|| slots().collect::<HashSet<u32>>())
.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)
}
pub fn add_key<P: AsRef<Path>>(
path: P,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
credential_id: Option<&[u8]>,
) -> Fido2LuksResult<u32> {
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<Option<(u32, Fido2LuksToken)>> {
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)
}
pub fn remove_keyslots<P: AsRef<Path>>(path: P, exclude: &[u32]) -> Fido2LuksResult<u32> {
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)
}
pub fn replace_key<P: AsRef<Path>>(
path: P,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
credential_id: Option<&[u8]>,
) -> Fido2LuksResult<u32> {
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)
} }