Compare commits

..

16 Commits
0.2.3 ... 0.2.7

10 changed files with 983 additions and 493 deletions

946
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "fido2luks" name = "fido2luks"
version = "0.2.3" version = "0.2.7"
authors = ["shimunn <shimun@shimun.net>"] authors = ["shimunn <shimun@shimun.net>"]
edition = "2018" edition = "2018"
@@ -14,16 +14,13 @@ categories = ["command-line-utilities"]
license-file = "LICENSE" license-file = "LICENSE"
[dependencies] [dependencies]
ctap_hmac = "0.2.1" ctap_hmac = { version="0.4.2", features = ["request_multiple"] }
cryptsetup-rs = "0.2.1"
libcryptsetup-sys = "0.1.2"
hex = "0.3.2" hex = "0.3.2"
ring = "0.13.5" ring = "0.13.5"
failure = "0.1.5" failure = "0.1.5"
rpassword = "4.0.1" rpassword = "4.0.1"
structopt = "0.3.2" structopt = "0.3.2"
libcryptsetup-rs = "0.2.0"
[profile.release] [profile.release]
lto = true lto = true

View File

@@ -9,7 +9,7 @@ Note: This has only been tested under Fedora 31 using a Solo Key, Trezor Model T
### Prerequisites ### Prerequisites
``` ```
dnf install cargo cryptsetup-devel -y dnf install clang cargo cryptsetup-devel -y
``` ```
### Device ### Device

View File

@@ -1,85 +1,24 @@
use crate::error::*; use crate::error::*;
use crate::luks;
use crate::*; use crate::*;
use cryptsetup_rs as luks;
use cryptsetup_rs::api::{CryptDeviceHandle, CryptDeviceOpenBuilder, Luks1Params};
use cryptsetup_rs::{CryptDevice, Luks1CryptDevice};
use libcryptsetup_sys::crypt_keyslot_info;
use structopt::StructOpt; use structopt::StructOpt;
use failure::_core::fmt::{Error, Formatter}; use ctap::{FidoCredential, FidoErrorKind};
use failure::_core::fmt::{Display, Error, Formatter};
use failure::_core::str::FromStr; use failure::_core::str::FromStr;
use failure::_core::time::Duration; use failure::_core::time::Duration;
use std::io::Write; use std::io::Write;
use std::process::exit; use std::process::exit;
use std::thread; use std::thread;
use crate::util::read_password;
use std::time::SystemTime; use std::time::SystemTime;
pub fn add_key_to_luks(
device: PathBuf,
secret: &[u8; 32],
old_secret: Box<dyn Fn() -> Fido2LuksResult<Vec<u8>>>,
exclusive: bool,
) -> Fido2LuksResult<u8> {
fn offer_format(
_dev: CryptDeviceOpenBuilder,
) -> Fido2LuksResult<CryptDeviceHandle<Luks1Params>> {
unimplemented!()
}
let dev =
|| -> luks::device::Result<CryptDeviceOpenBuilder> { luks::open(&device.canonicalize()?) };
let prev_key = old_secret()?;
let mut handle = match dev()?.luks1() {
Ok(handle) => handle,
Err(luks::device::Error::BlkidError(_)) => offer_format(dev()?)?,
Err(luks::device::Error::CryptsetupError(errno)) => {
//if i32::from(errno) == 555
dbg!(errno);
offer_format(dev()?)?
} //TODO: find correct errorno and offer to format as luks
err => err?,
};
handle.set_iteration_time(50);
let slot = handle.add_keyslot(secret, Some(prev_key.as_slice()), None)?;
if exclusive {
for old_slot in 0..8u8 {
if old_slot != slot
&& (handle.keyslot_status(old_slot.into()) == crypt_keyslot_info::CRYPT_SLOT_ACTIVE
|| handle.keyslot_status(old_slot.into())
== crypt_keyslot_info::CRYPT_SLOT_ACTIVE_LAST)
{
handle.destroy_keyslot(old_slot)?;
}
}
}
Ok(slot)
}
pub fn add_password_to_luks(
device: PathBuf,
secret: &[u8; 32],
new_secret: Box<dyn Fn() -> Fido2LuksResult<Vec<u8>>>,
add_password: bool,
) -> Fido2LuksResult<u8> {
let dev = luks::open(&device.canonicalize()?)?;
let mut handle = dev.luks1()?;
let prev_slot = if add_password {
Some(handle.add_keyslot(&secret[..], Some(&secret[..]), None)?)
} else {
None
};
let slot = handle.update_keyslot(&new_secret()?[..], &secret[..], prev_slot)?;
Ok(slot)
}
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub struct HexEncoded(Vec<u8>); pub struct HexEncoded(pub Vec<u8>);
impl std::fmt::Display for HexEncoded { impl Display for HexEncoded {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
f.write_str(&hex::encode(&self.0)) f.write_str(&hex::encode(&self.0))
} }
@@ -93,6 +32,30 @@ impl FromStr for HexEncoded {
} }
} }
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct CommaSeparated<T: FromStr + Display>(pub Vec<T>);
impl<T: Display + FromStr> Display for CommaSeparated<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
for i in &self.0 {
f.write_str(&i.to_string())?;
f.write_str(",")?;
}
Ok(())
}
}
impl<T: Display + FromStr> FromStr for CommaSeparated<T> {
type Err = <T as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(CommaSeparated(
s.split(',')
.map(|part| <T as FromStr>::from_str(part))
.collect::<Result<Vec<_>, _>>()?,
))
}
}
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
pub struct Args { pub struct Args {
/// Request passwords via Stdin instead of using the password helper /// Request passwords via Stdin instead of using the password helper
@@ -104,9 +67,9 @@ pub struct Args {
#[derive(Debug, StructOpt, Clone)] #[derive(Debug, StructOpt, Clone)]
pub struct SecretGeneration { pub struct SecretGeneration {
/// FIDO credential id, generate using fido2luks credential /// FIDO credential ids, seperated by ',' generate using fido2luks credential
#[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")] #[structopt(name = "credential-id", env = "FIDO2LUKS_CREDENTIAL_ID")]
pub credential_id: HexEncoded, pub credential_ids: CommaSeparated<HexEncoded>,
/// Salt for secret generation, defaults to 'ask' /// Salt for secret generation, defaults to 'ask'
/// ///
/// Options:{n} /// Options:{n}
@@ -136,25 +99,46 @@ pub struct SecretGeneration {
default_value = "15" default_value = "15"
)] )]
pub await_authenticator: u64, 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<bool>,
} }
impl SecretGeneration { impl SecretGeneration {
pub fn patch(&self, args: &Args) -> Self { pub fn patch(&self, args: &Args, verify_password: Option<bool>) -> Self {
let mut me = self.clone(); let mut me = self.clone();
if args.interactive { if args.interactive {
me.password_helper = PasswordHelper::Stdin; me.password_helper = PasswordHelper::Stdin;
} }
me.verify_password = me.verify_password.or(verify_password);
me me
} }
pub fn obtain_secret(&self) -> Fido2LuksResult<[u8; 32]> { pub fn obtain_secret(&self, password_query: &str) -> Fido2LuksResult<[u8; 32]> {
let salt = self.salt.obtain(&self.password_helper)?; 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 timeout = Duration::from_secs(self.await_authenticator);
let start = SystemTime::now(); let start = SystemTime::now();
while let Ok(el) = start.elapsed() { while let Ok(el) = start.elapsed() {
if el > timeout { if el > timeout {
Err(error::Fido2LuksError::NoAuthenticatorError)?; return Err(error::Fido2LuksError::NoAuthenticatorError);
} }
if get_devices() if get_devices()
.map(|devices| !devices.is_empty()) .map(|devices| !devices.is_empty())
@@ -164,13 +148,60 @@ impl SecretGeneration {
} }
thread::sleep(Duration::from_millis(500)); 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::<Vec<_>>();
let credentials = credentials.iter().collect::<Vec<_>>();
Ok(assemble_secret( Ok(assemble_secret(
&perform_challenge(&self.credential_id.0, &salt)?, &perform_challenge(&credentials[..], &salt, timeout - start.elapsed().unwrap())?,
&salt, &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<u64>,
}
#[derive(Debug, StructOpt, Clone)]
pub struct OtherSecret {
/// Use a keyfile instead of a password
#[structopt(short = "d", long = "keyfile", conflicts_with = "fido_device")]
keyfile: Option<PathBuf>,
/// Use another fido device instead of a password
/// Note: this requires for the credential fot the other device to be passed as argument as well
#[structopt(short = "f", long = "fido-device", conflicts_with = "keyfile")]
fido_device: bool,
}
impl OtherSecret {
pub fn obtain(
&self,
secret_gen: &SecretGeneration,
verify_password: bool,
password_question: &str,
) -> Fido2LuksResult<Vec<u8>> {
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)] #[derive(Debug, StructOpt)]
pub enum Command { pub enum Command {
#[structopt(name = "print-secret")] #[structopt(name = "print-secret")]
@@ -189,11 +220,12 @@ pub enum Command {
/// Will wipe all other keys /// Will wipe all other keys
#[structopt(short = "e", long = "exclusive")] #[structopt(short = "e", long = "exclusive")]
exclusive: bool, exclusive: bool,
/// Use a keyfile instead of typing a previous password #[structopt(flatten)]
#[structopt(short = "d", long = "keyfile")] existing_secret: OtherSecret,
keyfile: Option<PathBuf>,
#[structopt(flatten)] #[structopt(flatten)]
secret_gen: SecretGeneration, secret_gen: SecretGeneration,
#[structopt(flatten)]
luks_settings: LuksSettings,
}, },
/// Replace a previously added key with a password /// Replace a previously added key with a password
#[structopt(name = "replace-key")] #[structopt(name = "replace-key")]
@@ -203,11 +235,12 @@ pub enum Command {
/// Add the password and keep the key /// Add the password and keep the key
#[structopt(short = "a", long = "add-password")] #[structopt(short = "a", long = "add-password")]
add_password: bool, add_password: bool,
/// Use a keyfile instead of typing a previous password #[structopt(flatten)]
#[structopt(short = "d", long = "keyfile")] replacement: OtherSecret,
keyfile: Option<PathBuf>,
#[structopt(flatten)] #[structopt(flatten)]
secret_gen: SecretGeneration, secret_gen: SecretGeneration,
#[structopt(flatten)]
luks_settings: LuksSettings,
}, },
/// Open the LUKS device /// Open the LUKS device
#[structopt(name = "open")] #[structopt(name = "open")]
@@ -250,59 +283,64 @@ pub fn run_cli() -> Fido2LuksResult<()> {
binary, binary,
ref secret_gen, ref secret_gen,
} => { } => {
let secret = secret_gen.patch(&args).obtain_secret()?; let secret = secret_gen
.patch(&args, Some(false))
.obtain_secret("Password")?;
if *binary { if *binary {
stdout.write(&secret[..])?; stdout.write_all(&secret[..])?;
} else { } else {
stdout.write(hex::encode(&secret[..]).as_bytes())?; stdout.write_all(hex::encode(&secret[..]).as_bytes())?;
} }
Ok(stdout.flush()?) Ok(stdout.flush()?)
} }
Command::AddKey { Command::AddKey {
device, device,
exclusive, exclusive,
keyfile, existing_secret,
ref secret_gen, ref secret_gen,
luks_settings,
} => { } => {
let secret = secret_gen.patch(&args).obtain_secret()?; let secret_gen = secret_gen.patch(&args, None);
let slot = add_key_to_luks( let old_secret = existing_secret.obtain(&secret_gen, false, "Existing password")?;
let secret = secret_gen.obtain_secret("Password")?;
let added_slot = luks::add_key(
device.clone(), device.clone(),
&secret, &secret,
if let Some(keyfile) = keyfile.clone() { &old_secret[..],
Box::new(move || util::read_keyfile(keyfile.clone())) luks_settings.kdf_time.or(Some(10)),
} else {
Box::new(|| {
util::read_password("Old password", true).map(|p| p.as_bytes().to_vec())
})
},
*exclusive,
)?; )?;
println!( if *exclusive {
"Added to key to device {}, slot: {}", let destroyed = luks::remove_keyslots(&device, &[added_slot])?;
device.display(), println!(
slot "Added to key to device {}, slot: {}\nRemoved {} old keys",
); device.display(),
added_slot,
destroyed
);
} else {
println!(
"Added to key to device {}, slot: {}",
device.display(),
added_slot
);
}
Ok(()) Ok(())
} }
Command::ReplaceKey { Command::ReplaceKey {
device, device,
add_password, add_password,
keyfile, replacement,
ref secret_gen, ref secret_gen,
luks_settings,
} => { } => {
let secret = secret_gen.patch(&args).obtain_secret()?; let secret_gen = secret_gen.patch(&args, Some(false));
let slot = add_password_to_luks( let secret = secret_gen.obtain_secret("Password")?;
device.clone(), let new_secret = replacement.obtain(&secret_gen, true, "Replacement password")?;
&secret, let slot = if *add_password {
if let Some(keyfile) = keyfile.clone() { luks::add_key(device, &new_secret[..], &secret, luks_settings.kdf_time)
Box::new(move || util::read_keyfile(keyfile.clone())) } else {
} else { luks::replace_key(device, &new_secret[..], &secret, luks_settings.kdf_time)
Box::new(|| { }?;
util::read_password("Password to add", true).map(|p| p.as_bytes().to_vec())
})
},
*add_password,
)?;
println!( println!(
"Added to password to device {}, slot: {}", "Added to password to device {}, slot: {}",
device.display(), device.display(),
@@ -318,16 +356,22 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} => { } => {
let mut retries = *retries; let mut retries = *retries;
loop { loop {
let secret = secret_gen.patch(&args).obtain_secret()?; match secret_gen
match open_container(&device, &name, &secret) { .patch(&args, Some(false))
Err(e) => match e { .obtain_secret("Password")
Fido2LuksError::WrongSecret if retries > 0 => { .and_then(|secret| luks::open_container(&device, &name, &secret))
retries -= 1; {
eprintln!("{}", e); Err(e) => {
continue; match e {
Fido2LuksError::WrongSecret if retries > 0 => {}
Fido2LuksError::AuthenticatorError { ref cause }
if cause.kind() == FidoErrorKind::Timeout && retries > 0 => {}
e => return Err(e),
} }
e => Err(e)?, retries -= 1;
}, eprintln!("{}", e);
}
res => break res, res => break res,
} }
} }

View File

@@ -24,7 +24,7 @@ impl Default for InputSalt {
impl From<&str> for InputSalt { impl From<&str> for InputSalt {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
let mut parts = s.split(":").into_iter(); let mut parts = s.split(':');
match parts.next() { match parts.next() {
Some("ask") | Some("Ask") => InputSalt::AskPassword, Some("ask") | Some("Ask") => InputSalt::AskPassword,
Some("file") => InputSalt::File { Some("file") => InputSalt::File {
@@ -87,6 +87,7 @@ impl InputSalt {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum PasswordHelper { pub enum PasswordHelper {
Script(String), Script(String),
#[allow(dead_code)]
Systemd, Systemd,
Stdin, Stdin,
} }
@@ -134,7 +135,7 @@ impl PasswordHelper {
Systemd => unimplemented!(), Systemd => unimplemented!(),
Stdin => Ok(util::read_password("Password", true)?), Stdin => Ok(util::read_password("Password", true)?),
Script(password_helper) => { Script(password_helper) => {
let mut helper_parts = password_helper.split(" "); let mut helper_parts = password_helper.split(' ');
let password = Command::new((&mut helper_parts).next().unwrap()) let password = Command::new((&mut helper_parts).next().unwrap())
.args(helper_parts) .args(helper_parts)

View File

@@ -1,91 +1,49 @@
use crate::error::*; use crate::error::*;
use crate::util::sha256;
use crate::util;
use ctap::{ use ctap::{
self, self, extensions::hmac::HmacExtension, request_multiple_devices, FidoAssertionRequestBuilder,
extensions::hmac::{FidoHmacCredential, HmacExtension}, FidoCredential, FidoCredentialRequestBuilder, FidoDevice, FidoError, FidoErrorKind,
AuthenticatorOptions, FidoDevice, FidoError, FidoErrorKind, PublicKeyCredentialRpEntity,
PublicKeyCredentialUserEntity,
}; };
use std::time::Duration;
const RP_ID: &'static str = "fido2luks"; const RP_ID: &str = "fido2luks";
fn authenticator_options() -> Option<AuthenticatorOptions> { pub fn make_credential_id(name: Option<&str>) -> Fido2LuksResult<FidoCredential> {
Some(AuthenticatorOptions { let mut request = FidoCredentialRequestBuilder::default().rp_id(RP_ID);
uv: false, //TODO: should get this from config if let Some(user_name) = name {
rk: true, request = request.user_name(user_name);
})
}
fn authenticator_rp() -> PublicKeyCredentialRpEntity<'static> {
PublicKeyCredentialRpEntity {
id: RP_ID,
name: None,
icon: None,
} }
let request = request.build().unwrap();
let make_credential = |device: &mut FidoDevice| device.make_hmac_credential(&request);
Ok(request_multiple_devices(
get_devices()?
.iter_mut()
.map(|device| (device, &make_credential)),
None,
)?)
} }
fn authenticator_user(name: Option<&str>) -> PublicKeyCredentialUserEntity { pub fn perform_challenge(
PublicKeyCredentialUserEntity { credentials: &[&FidoCredential],
id: &[0u8], salt: &[u8; 32],
name: name.unwrap_or(""), timeout: Duration,
icon: None, ) -> Fido2LuksResult<[u8; 32]> {
display_name: name, let request = FidoAssertionRequestBuilder::default()
} .rp_id(RP_ID)
} .credentials(credentials)
.build()
pub fn make_credential_id(name: Option<&str>) -> Fido2LuksResult<FidoHmacCredential> { .unwrap();
let mut errs = Vec::new(); let get_assertion = |device: &mut FidoDevice| {
match get_devices()? { device.get_hmac_assertion(&request, &util::sha256(&[&salt[..]]), None)
ref devs if devs.is_empty() => Err(Fido2LuksError::NoAuthenticatorError)?,
devs => {
for mut dev in devs.into_iter() {
match dev
.make_hmac_credential_full(
authenticator_rp(),
authenticator_user(name),
&[0u8; 32],
&[],
authenticator_options(),
)
.map(|cred| cred.into())
{
//TODO: make credentials device specific
Ok(cred) => {
return Ok(cred);
}
Err(e) => {
errs.push(e);
}
}
}
}
}
Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)?
}
pub fn perform_challenge(credential_id: &[u8], salt: &[u8; 32]) -> Fido2LuksResult<[u8; 32]> {
let cred = FidoHmacCredential {
id: credential_id.to_vec(),
rp_id: RP_ID.to_string(),
}; };
let mut errs = Vec::new(); let (_, (secret, _)) = request_multiple_devices(
match get_devices()? { get_devices()?
ref devs if devs.is_empty() => Err(Fido2LuksError::NoAuthenticatorError)?, .iter_mut()
devs => { .map(|device| (device, &get_assertion)),
for mut dev in devs.into_iter() { Some(timeout),
match dev.get_hmac_assertion(&cred, &sha256(&[&salt[..]]), None, None) { )?;
Ok(secret) => { Ok(secret)
return Ok(secret.0);
}
Err(e) => {
errs.push(e);
}
}
}
}
}
Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)?
} }
pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> { pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
@@ -94,7 +52,7 @@ pub fn get_devices() -> Fido2LuksResult<Vec<FidoDevice>> {
match FidoDevice::new(&di) { match FidoDevice::new(&di) {
Err(e) => match e.kind() { Err(e) => match e.kind() {
FidoErrorKind::ParseCtap | FidoErrorKind::DeviceUnsupported => (), FidoErrorKind::ParseCtap | FidoErrorKind::DeviceUnsupported => (),
err => Err(FidoError::from(err))?, err => return Err(FidoError::from(err).into()),
}, },
Ok(dev) => devices.push(dev), Ok(dev) => devices.push(dev),
} }

View File

@@ -14,7 +14,9 @@ pub enum Fido2LuksError {
#[fail(display = "no authenticator found, please ensure your device is plugged in")] #[fail(display = "no authenticator found, please ensure your device is plugged in")]
NoAuthenticatorError, NoAuthenticatorError,
#[fail(display = "luks err")] #[fail(display = "luks err")]
LuksError { cause: cryptsetup_rs::device::Error }, LuksError {
cause: libcryptsetup_rs::LibcryptErr,
},
#[fail(display = "no authenticator found, please ensure your device is plugged in")] #[fail(display = "no authenticator found, please ensure your device is plugged in")]
IoError { cause: io::Error }, IoError { cause: io::Error },
#[fail(display = "supplied secret isn't valid for this device")] #[fail(display = "supplied secret isn't valid for this device")]
@@ -44,6 +46,7 @@ pub enum AskPassError {
Mismatch, Mismatch,
} }
use libcryptsetup_rs::LibcryptErr;
use std::string::FromUtf8Error; use std::string::FromUtf8Error;
use Fido2LuksError::*; use Fido2LuksError::*;
@@ -53,17 +56,16 @@ impl From<FidoError> for Fido2LuksError {
} }
} }
impl From<cryptsetup_rs::device::Error> for Fido2LuksError { impl From<LibcryptErr> for Fido2LuksError {
fn from(e: cryptsetup_rs::device::Error) -> Self { fn from(e: LibcryptErr) -> Self {
match e { match e {
cryptsetup_rs::device::Error::CryptsetupError(error_no) if error_no.0 == 1i32 => { LibcryptErr::IOError(e) if e.raw_os_error().iter().any(|code| code == &1i32) => {
WrongSecret WrongSecret
} }
e => LuksError { cause: e }, _ => LuksError { cause: e },
} }
} }
} }
impl From<io::Error> for Fido2LuksError { impl From<io::Error> for Fido2LuksError {
fn from(e: io::Error) -> Self { fn from(e: io::Error) -> Self {
IoError { cause: e } IoError { cause: e }

82
src/luks.rs Normal file
View File

@@ -0,0 +1,82 @@
use crate::error::*;
use libcryptsetup_rs::{CryptActivateFlags, CryptDevice, CryptInit, EncryptionFormat, KeyslotInfo};
use std::path::Path;
fn load_device_handle<P: AsRef<Path>>(path: P) -> Fido2LuksResult<CryptDevice> {
let mut device = CryptInit::init(path.as_ref())?;
//TODO: determine luks version some way other way than just trying
let mut load = |format| device.context_handle().load::<()>(format, None).map(|_| ());
vec![EncryptionFormat::Luks2, EncryptionFormat::Luks1]
.into_iter()
.fold(None, |res, format| match res {
Some(Ok(())) => res,
Some(e) => Some(e.or_else(|_| load(format))),
None => Some(load(format)),
})
.unwrap()?;
Ok(device)
}
pub fn open_container<P: AsRef<Path>>(path: P, name: &str, secret: &[u8]) -> Fido2LuksResult<()> {
let mut device = load_device_handle(path)?;
device
.activate_handle()
.activate_by_passphrase(Some(name), None, secret, CryptActivateFlags::empty())
.map(|_slot| ())
.map_err(|_e| Fido2LuksError::WrongSecret)
}
pub fn add_key<P: AsRef<Path>>(
path: P,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
) -> 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(None)
.add_by_passphrase(old_secret, secret)?;
Ok(slot)
}
pub fn remove_keyslots<P: AsRef<Path>>(path: P, exclude: &[u32]) -> Fido2LuksResult<u32> {
let mut device = load_device_handle(path)?;
let mut handle;
let mut destroyed = 0;
//TODO: detect how many keyslots there are instead of trying within a given range
for slot in 0..1024 {
handle = device.keyslot_handle(Some(slot));
match handle.status()? {
KeyslotInfo::Inactive => continue,
KeyslotInfo::Active if !exclude.contains(&slot) => {
handle.destroy()?;
destroyed += 1;
}
_ => (),
}
if let KeyslotInfo::ActiveLast = handle.status()? {
break;
}
}
Ok(destroyed)
}
pub fn replace_key<P: AsRef<Path>>(
path: P,
secret: &[u8],
old_secret: &[u8],
iteration_time: Option<u64>,
) -> Fido2LuksResult<u32> {
let mut device = load_device_handle(path)?;
// Set iteration time not sure wether this applies to luks2 as well
if let Some(millis) = iteration_time {
device.settings_handle().set_iteration_time(millis)
}
Ok(device
.keyslot_handle(None)
.change_by_passphrase(None, None, old_secret, secret)? as u32)
}

View File

@@ -5,9 +5,6 @@ use crate::cli::*;
use crate::config::*; use crate::config::*;
use crate::device::*; use crate::device::*;
use crate::error::*; use crate::error::*;
use cryptsetup_rs as luks;
use cryptsetup_rs::Luks1CryptDevice;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::exit; use std::process::exit;
@@ -16,14 +13,9 @@ mod cli;
mod config; mod config;
mod device; mod device;
mod error; mod error;
mod luks;
mod util; mod util;
fn open_container(device: &PathBuf, name: &str, secret: &[u8; 32]) -> Fido2LuksResult<()> {
let mut handle = luks::open(device.canonicalize()?)?.luks1()?;
let _slot = handle.activate(name, &secret[..])?;
Ok(())
}
fn assemble_secret(hmac_result: &[u8], salt: &[u8]) -> [u8; 32] { fn assemble_secret(hmac_result: &[u8], salt: &[u8]) -> [u8; 32] {
util::sha256(&[salt, hmac_result]) util::sha256(&[salt, hmac_result])
} }

View File

@@ -4,7 +4,7 @@ use std::fs::File;
use std::io::Read; use std::io::Read;
use std::path::PathBuf; use std::path::PathBuf;
pub fn sha256<'a>(messages: &[&[u8]]) -> [u8; 32] { pub fn sha256(messages: &[&[u8]]) -> [u8; 32] {
let mut digest = digest::Context::new(&digest::SHA256); let mut digest = digest::Context::new(&digest::SHA256);
for m in messages.iter() { for m in messages.iter() {
digest.update(m); digest.update(m);
@@ -23,7 +23,7 @@ pub fn read_password(q: &str, verify: bool) -> Fido2LuksResult<String> {
{ {
Err(Fido2LuksError::AskPassError { Err(Fido2LuksError::AskPassError {
cause: AskPassError::Mismatch, cause: AskPassError::Mismatch,
})? })
} }
pass => Ok(pass), pass => Ok(pass),
} }