2
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing

This commit is contained in:
shimun 2020-10-11 21:11:42 +02:00
parent bd29452980
commit 8954de3558
Signed by: shimun
GPG Key ID: E81D8382DC2F971B
5 changed files with 216 additions and 111 deletions

View File

@ -8,13 +8,13 @@ steps:
- rustup component add rustfmt - rustup component add rustfmt
- cargo fmt --all -- --check - cargo fmt --all -- --check
- name: test - name: test
image: shimun/fido2luks@sha256:c1c9b0fb36f24555bc575dd6f643efd3a83f9db25a7ed88af3e22eacf44a89b8 image: shimun/fido2luks@sha256:6d0b4017bffbec5fac8f25d383d68671fcc9930efb02e97ce5ea81acf0060ece
environment: environment:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
commands: commands:
- cargo test --locked - cargo test --locked
- name: publish - name: publish
image: shimun/fido2luks@sha256:c1c9b0fb36f24555bc575dd6f643efd3a83f9db25a7ed88af3e22eacf44a89b8 image: shimun/fido2luks@sha256:6d0b4017bffbec5fac8f25d383d68671fcc9930efb02e97ce5ea81acf0060ece
environment: environment:
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
CARGO_REGISTRY_TOKEN: CARGO_REGISTRY_TOKEN:

View File

@ -20,17 +20,13 @@ use std::fs::File;
use std::time::SystemTime; use std::time::SystemTime;
pub use cli_args::Args; pub use cli_args::Args;
use failure::ResultExt;
use std::collections::hash_map::RandomState;
use std::iter::FromIterator; use std::iter::FromIterator;
use std::path::PathBuf; use std::path::PathBuf;
fn read_pin(ap: &AuthenticatorParameters) -> Fido2LuksResult<String> { fn read_pin() -> Fido2LuksResult<String> {
if let Some(src) = ap.pin_source.as_ref() { util::read_password("Authenticator PIN", false)
let mut pin = String::new();
File::open(src)?.read_to_string(&mut pin)?;
Ok(pin.trim_end_matches("\n").to_string()) //remove trailing newline
} else {
util::read_password("Authenticator PIN", false)
}
} }
fn derive_secret( fn derive_secret(
@ -74,10 +70,15 @@ pub fn extend_creds_device(
creds: &[HexEncoded], creds: &[HexEncoded],
luks_dev: &mut LuksDevice, luks_dev: &mut LuksDevice,
) -> Fido2LuksResult<Vec<HexEncoded>> { ) -> Fido2LuksResult<Vec<HexEncoded>> {
let mut additional = HashSet::from_iter(creds.iter().cloned()); let mut additional = HashSet::new();
additional.extend(creds.iter().cloned());
for token in luks_dev.tokens()? { for token in luks_dev.tokens()? {
for cred in token?.1.credential { for cred in token?.1.credential {
let parsed = HexEncoded::from_str(cred.as_str())?; let parsed = HexEncoded::from_str(cred.as_str()).map_err(|_e| {
Fido2LuksError::HexEncodingError {
string: cred.clone(),
}
})?;
additional.insert(parsed); additional.insert(parsed);
} }
} }
@ -107,7 +108,6 @@ pub fn parse_cmdline() -> Args {
pub fn run_cli() -> Fido2LuksResult<()> { pub fn run_cli() -> Fido2LuksResult<()> {
let mut stdout = io::stdout(); let mut stdout = io::stdout();
let args = parse_cmdline(); let args = parse_cmdline();
let interactive = args.interactive;
match &args.command { match &args.command {
Command::Credential { Command::Credential {
authenticator, authenticator,
@ -115,7 +115,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} => { } => {
let pin_string; let pin_string;
let pin = if authenticator.pin { let pin = if authenticator.pin {
pin_string = read_pin(authenticator)?; pin_string = read_pin()?;
Some(pin_string.as_ref()) Some(pin_string.as_ref())
} else { } else {
None None
@ -131,13 +131,6 @@ pub fn run_cli() -> Fido2LuksResult<()> {
secret, secret,
device, device,
} => { } => {
let pin_string;
let pin = if authenticator.pin {
pin_string = read_pin(authenticator)?;
Some(pin_string.as_ref())
} else {
None
};
let (pin, salt) = match ( let (pin, salt) = match (
&secret.password_helper, &secret.password_helper,
authenticator.pin, authenticator.pin,
@ -147,15 +140,12 @@ pub fn run_cli() -> Fido2LuksResult<()> {
(Some(phelper), true, true, false) => { (Some(phelper), true, true, false) => {
read_password_pin_prefixed(|| phelper.obtain())? read_password_pin_prefixed(|| phelper.obtain())?
} }
(Some(phelper), false, false, false) => (None, secret.salt.obtain_sha256(&phelper)), (Some(phelper), false, false, false) => {
(None, secret.salt.obtain_sha256(&phelper)?)
}
(phelper, pin, _, true) => ( (phelper, pin, _, _) => (
if pin { if pin { Some(read_pin()?) } else { None },
pin_string = read_pin(authenticator)?;
Some(pin_string.as_ref())
} else {
None
},
match phelper { match phelper {
None | Some(PasswordHelper::Stdin) => { None | Some(PasswordHelper::Stdin) => {
util::read_password_hashed("Password", false) util::read_password_hashed("Password", false)
@ -165,9 +155,17 @@ pub fn run_cli() -> Fido2LuksResult<()> {
), ),
}; };
let credentials = if let Some(dev) = device { let credentials = if let Some(dev) = device {
extend_creds_device(credentials.ids.0.as_slice(), &mut LuksDevice::load(dev)?)? extend_creds_device(
credentials
.ids
.clone()
.map(|cs| cs.0)
.unwrap_or_default()
.as_slice(),
&mut LuksDevice::load(dev)?,
)?
} else { } else {
credentials.ids.0 credentials.ids.clone().map(|cs| cs.0).unwrap_or_default()
}; };
let (secret, _cred) = derive_secret( let (secret, _cred) = derive_secret(
credentials.as_slice(), credentials.as_slice(),
@ -189,7 +187,6 @@ pub fn run_cli() -> Fido2LuksResult<()> {
secret, secret,
luks_mod, luks_mod,
existing_secret: other_secret, existing_secret: other_secret,
auto_credential,
.. ..
} }
| Command::ReplaceKey { | Command::ReplaceKey {
@ -199,21 +196,54 @@ pub fn run_cli() -> Fido2LuksResult<()> {
secret, secret,
luks_mod, luks_mod,
replacement: other_secret, replacement: other_secret,
remove_cred,
.. ..
} => { } => {
let pin = if authenticator.pin { let mut luks_dev = LuksDevice::load(&luks.device)?;
Some(read_pin(authenticator)?)
let luks2 = luks_dev.is_luks2()?;
let credentials = if !luks.disable_token && luks2 {
extend_creds_device(
credentials
.ids
.clone()
.map(|cs| cs.0)
.unwrap_or_default()
.as_slice(),
&mut luks_dev,
)?
} else { } else {
None credentials.ids.clone().map(|cs| cs.0).unwrap_or_default()
}; };
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> {
if interactive || secret.password_helper == PasswordHelper::Stdin { let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option<String>, [u8; 32])> {
util::read_password_hashed(q, verify) Ok(
} else { match (
secret.salt.obtain_sha256(&secret.password_helper) &secret.password_helper,
} authenticator.pin,
authenticator.pin_prefixed,
args.interactive,
) {
(Some(phelper), true, true, false) => {
read_password_pin_prefixed(|| phelper.obtain())?
}
(Some(phelper), false, false, false) => {
(None, secret.salt.obtain_sha256(&phelper)?)
}
(phelper, pin, _, _) => (
if pin { Some(read_pin()?) } else { None },
match &secret.password_helper {
None | Some(PasswordHelper::Stdin) => {
util::read_password_hashed(q, verify)
}
Some(phelper) => secret.salt.obtain_sha256(&phelper),
}?,
),
},
)
}; };
let other_secret = |salt_q: &str, let other_secret = |salt_q: &str,
verify: bool| verify: bool|
-> Fido2LuksResult<(Vec<u8>, Option<FidoCredential>)> { -> Fido2LuksResult<(Vec<u8>, Option<FidoCredential>)> {
@ -224,38 +254,53 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} => Ok((util::read_keyfile(file)?, None)), } => Ok((util::read_keyfile(file)?, None)),
OtherSecret { OtherSecret {
fido_device: true, .. fido_device: true, ..
} => Ok(derive_secret( } => {
&credentials.ids.0, let (pin, salt) = inputs(salt_q, verify)?;
&salt(salt_q, verify)?, Ok(derive_secret(
authenticator.await_time, &credentials,
pin.as_deref(), &salt,
) authenticator.await_time,
.map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?), pin.as_deref(),
)
.map(|(secret, cred)| (secret[..].to_vec(), Some(cred)))?)
}
_ => Ok(( _ => Ok((
util::read_password(salt_q, verify)?.as_bytes().to_vec(), util::read_password(salt_q, verify)?.as_bytes().to_vec(),
None, None,
)), )),
} }
}; };
let secret = |verify: bool| -> Fido2LuksResult<([u8; 32], FidoCredential)> { let secret = |verify: bool,
derive_secret( credentials: &[HexEncoded]|
&credentials.ids.0, -> Fido2LuksResult<([u8; 32], FidoCredential)> {
&salt("Password", verify)?, let (pin, salt) = inputs("Password", verify)?;
authenticator.await_time, derive_secret(credentials, &salt, authenticator.await_time, 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 { exclusive, .. } => { Command::AddKey {
exclusive,
auto_credential,
..
} => {
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) = if *auto_credential && luks2 {
let cred = make_credential_id(
Some(luks.device.display().to_string().as_str()),
None,
)?; //TODO: do ask for PIN
let creds = vec![HexEncoded(cred.id)];
secret(true, &creds)
} else {
secret(true, &credentials)
}?;
let added_slot = luks_dev.add_key( let added_slot = luks_dev.add_key(
&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(|_| !luks.disable_token || *auto_credential)
.filter(|_| luks2),
)?; )?;
if *exclusive { if *exclusive {
let destroyed = luks_dev.remove_keyslots(&[added_slot])?; let destroyed = luks_dev.remove_keyslots(&[added_slot])?;
@ -274,23 +319,37 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} }
Ok(()) Ok(())
} }
Command::ReplaceKey { add_password, .. } => { Command::ReplaceKey {
let (existing_secret, _) = secret(false)?; add_password,
remove_cred,
..
} => {
let (existing_secret, _prev_cred) = secret(false, &credentials)?;
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_dev.add_key( luks_dev.add_key(
&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(|_| !luks.disable_token)
.filter(|_| luks2)
.map(|cred| &cred.id[..]),
) )
} else { } else {
luks_dev.replace_key( let slot = luks_dev.replace_key(
&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(|_| !luks.disable_token)
.filter(|_| luks2)
.map(|cred| &cred.id[..]),
)?;
if *remove_cred && cred.is_none() {
luks_dev.remove_token_slot(slot)?;
}
Ok(slot)
}?; }?;
println!( println!(
"Added to password to device {}, slot: {}", "Added to password to device {}, slot: {}",
@ -307,48 +366,56 @@ pub fn run_cli() -> Fido2LuksResult<()> {
authenticator, authenticator,
secret, secret,
name, name,
retries, credentials,
..
}
| Command::OpenToken {
luks,
authenticator,
secret,
name,
retries, retries,
} => { } => {
let pin_string; let inputs = |q: &str, verify: bool| -> Fido2LuksResult<(Option<String>, [u8; 32])> {
let pin = if authenticator.pin { Ok(
pin_string = read_pin(authenticator)?; match (
Some(pin_string.as_ref()) &secret.password_helper,
} else { authenticator.pin,
None authenticator.pin_prefixed,
}; args.interactive,
let salt = |q: &str, verify: bool| -> Fido2LuksResult<[u8; 32]> { ) {
if interactive || secret.password_helper == PasswordHelper::Stdin { (Some(phelper), true, true, false) => {
util::read_password_hashed(q, verify) read_password_pin_prefixed(|| phelper.obtain())?
} else { }
secret.salt.obtain_sha256(&secret.password_helper) (Some(phelper), false, false, false) => {
} (None, secret.salt.obtain_sha256(&phelper)?)
}
(phelper, pin, _, _) => (
if pin { Some(read_pin()?) } else { None },
match &phelper {
None | Some(PasswordHelper::Stdin) => {
util::read_password_hashed(q, verify)
}
Some(phelper) => secret.salt.obtain_sha256(&phelper),
}?,
),
},
)
}; };
// Cow shouldn't be necessary // Cow shouldn't be necessary
let secret = |credentials: Cow<'_, Vec<HexEncoded>>| { let secret = |credentials: Cow<'_, Vec<HexEncoded>>| {
let (pin, salt) = inputs("Password", false)?;
derive_secret( derive_secret(
credentials.as_ref(), credentials.as_ref(),
&salt("Password", false)?, &salt,
authenticator.await_time, authenticator.await_time,
pin, pin.as_deref(),
) )
}; };
let mut retries = *retries; let mut retries = *retries;
let mut luks_dev = LuksDevice::load(&luks.device)?; let mut luks_dev = LuksDevice::load(&luks.device)?;
loop { loop {
let secret = match &args.command { let slot = if let Some(ref credentials) = credentials.ids {
Command::Open { credentials, .. } => secret(Cow::Borrowed(&credentials.ids.0)) secret(Cow::Borrowed(&credentials.0))
.and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot)), .and_then(|(secret, _cred)| luks_dev.activate(&name, &secret, luks.slot))
Command::OpenToken { .. } => luks_dev.activate_token( } else if luks_dev.is_luks2()? {
luks_dev.activate_token(
&name, &name,
Box::new(|credentials: Vec<String>| { Box::new(|credentials: Vec<String>| {
let creds = credentials let creds = credentials
@ -359,10 +426,11 @@ pub fn run_cli() -> Fido2LuksResult<()> {
.map(|(secret, cred)| (secret, hex::encode(&cred.id))) .map(|(secret, cred)| (secret, hex::encode(&cred.id)))
}), }),
luks.slot, luks.slot,
), )
_ => unreachable!(), } else {
return Err(Fido2LuksError::WrongSecret); // creds or luks2
}; };
match secret { match slot {
Err(e) => { Err(e) => {
match e { match e {
Fido2LuksError::WrongSecret if retries > 0 => {} Fido2LuksError::WrongSecret if retries > 0 => {}
@ -444,7 +512,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
} }
} }
let count = if tokens.is_empty() { let count = if tokens.is_empty() {
dev.add_token(&Fido2LuksToken::with_credentials(&credentials.ids.0, *slot))?; dev.add_token(&Fido2LuksToken::with_credentials(&credentials.0, *slot))?;
1 1
} else { } else {
tokens.len() tokens.len()
@ -452,7 +520,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
for (id, mut token) in tokens { for (id, mut token) in tokens {
token token
.credential .credential
.extend(credentials.ids.0.iter().map(|h| h.to_string())); .extend(credentials.0.iter().map(|h| h.to_string()));
dev.update_token(id, &token)?; dev.update_token(id, &token)?;
} }
println!("Updated {} tokens", count); println!("Updated {} tokens", count);
@ -480,7 +548,7 @@ pub fn run_cli() -> Fido2LuksResult<()> {
token.credential = token token.credential = token
.credential .credential
.into_iter() .into_iter()
.filter(|cred| !credentials.ids.0.iter().any(|h| &h.to_string() == cred)) .filter(|cred| !credentials.0.iter().any(|h| &h.to_string() == cred))
.collect(); .collect();
dev.update_token(id, &token)?; dev.update_token(id, &token)?;
} }
@ -512,11 +580,8 @@ pub fn run_cli() -> Fido2LuksResult<()> {
Command::GenerateCompletions { shell, out_dir } => { Command::GenerateCompletions { shell, out_dir } => {
Args::clap().gen_completions( Args::clap().gen_completions(
env!("CARGO_PKG_NAME"), env!("CARGO_PKG_NAME"),
match shell.as_ref() { Shell::from_str(shell.as_str())
"bash" => Shell::Bash, .expect("structopt shouldn't allow us to reach this point"),
"fish" => Shell::Fish,
_ => unreachable!("structopt shouldn't allow us to reach this point"),
},
&out_dir, &out_dir,
); );
Ok(()) Ok(())

View File

@ -1,4 +1,5 @@
use std::fmt::{Display, Error, Formatter}; use std::fmt::{Display, Error, Formatter};
use std::hash::{Hash, Hasher};
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use structopt::clap::AppSettings; use structopt::clap::AppSettings;
@ -31,6 +32,12 @@ impl FromStr for HexEncoded {
} }
} }
impl Hash for HexEncoded {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
#[derive(Debug, Eq, PartialEq, Clone)] #[derive(Debug, Eq, PartialEq, Clone)]
pub struct CommaSeparated<T: FromStr + Display>(pub Vec<T>); pub struct CommaSeparated<T: FromStr + Display>(pub Vec<T>);
@ -128,8 +135,7 @@ pub struct SecretParameters {
#[structopt( #[structopt(
name = "password-helper", name = "password-helper",
env = "FIDO2LUKS_PASSWORD_HELPER", env = "FIDO2LUKS_PASSWORD_HELPER",
long = "password-helper", long = "password-helper"
default_value = "/usr/bin/env systemd-ask-password 'Please enter second factor for LUKS disk encryption!'"
)] )]
pub password_helper: Option<PasswordHelper>, pub password_helper: Option<PasswordHelper>,
} }
@ -269,8 +275,14 @@ pub enum TokenCommand {
Add { Add {
#[structopt(env = "FIDO2LUKS_DEVICE")] #[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf, device: PathBuf,
#[structopt(flatten)] /// FIDO credential ids, separated by ',' generate using fido2luks credential
credentials: Credentials, #[structopt(
name = "credential-ids",
env = "FIDO2LUKS_CREDENTIAL_ID",
short = "c",
long = "creds"
)]
credentials: CommaSeparated<HexEncoded>,
/// Slot to which the credentials will be added /// Slot to which the credentials will be added
#[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")] #[structopt(long = "slot", env = "FIDO2LUKS_DEVICE_SLOT")]
slot: u32, slot: u32,
@ -279,8 +291,14 @@ pub enum TokenCommand {
Remove { Remove {
#[structopt(env = "FIDO2LUKS_DEVICE")] #[structopt(env = "FIDO2LUKS_DEVICE")]
device: PathBuf, device: PathBuf,
#[structopt(flatten)] /// FIDO credential ids, separated by ',' generate using fido2luks credential
credentials: Credentials, #[structopt(
name = "credential-ids",
env = "FIDO2LUKS_CREDENTIAL_ID",
short = "c",
long = "creds"
)]
credentials: CommaSeparated<HexEncoded>,
/// Token from which the credentials will be removed /// Token from which the credentials will be removed
#[structopt(long = "token")] #[structopt(long = "token")]
token_id: Option<u32>, token_id: Option<u32>,

View File

@ -29,6 +29,8 @@ pub enum Fido2LuksError {
WrongSecret, WrongSecret,
#[fail(display = "not an utf8 string")] #[fail(display = "not an utf8 string")]
StringEncodingError { cause: FromUtf8Error }, StringEncodingError { cause: FromUtf8Error },
#[fail(display = "not an hex string: {}", string)]
HexEncodingError { string: String },
} }
impl Fido2LuksError { impl Fido2LuksError {

View File

@ -103,6 +103,10 @@ impl LuksDevice {
Ok(()) Ok(())
} }
pub fn add_credential(&mut self, slot: u32, credential: Vec<u8>) -> Fido2LuksResult<()> {
self.add_token(&Fido2LuksToken::with_credentials(&[credential], slot))
}
pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> { pub fn remove_token(&mut self, token: u32) -> Fido2LuksResult<()> {
self.require_luks2()?; self.require_luks2()?;
self.device self.device
@ -111,6 +115,20 @@ impl LuksDevice {
Ok(()) Ok(())
} }
pub fn remove_token_slot(&mut self, slot: u32) -> Fido2LuksResult<()> {
let mut remove = HashSet::new();
for token in self.tokens()? {
let (id, token) = token?;
if token.keyslots.contains(&slot.to_string()) {
remove.insert(id);
}
}
for rm in remove {
self.remove_token(rm)?;
}
Ok(())
}
pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> { pub fn update_token(&mut self, token: u32, data: &Fido2LuksToken) -> Fido2LuksResult<()> {
self.require_luks2()?; self.require_luks2()?;
self.device self.device
@ -192,7 +210,9 @@ impl LuksDevice {
old_secret, old_secret,
CryptActivateFlags::empty(), CryptActivateFlags::empty(),
)?; )?;
self.device.keyslot_handle().change_by_passphrase(
// slot should stay the same but better be safe than sorry
let slot = self.device.keyslot_handle().change_by_passphrase(
Some(slot), Some(slot),
Some(slot), Some(slot),
old_secret, old_secret,