From f7b7617e0e5cfe7402f4ddba74c8a1e47f339d70 Mon Sep 17 00:00:00 2001 From: shimunn Date: Mon, 16 Sep 2019 15:34:10 +0200 Subject: [PATCH] open works --- src/error.rs | 17 ++--- src/main.rs | 182 ++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 146 insertions(+), 53 deletions(-) diff --git a/src/error.rs b/src/error.rs index 631c92e..c93c4f6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,49 +1,50 @@ -use std::io; use ctap::FidoError; +use std::io; pub type Fido2LuksResult = Result; #[derive(Debug, Fail)] pub enum Fido2LuksError { #[fail(display = "unable to retrieve password: {}", cause)] - AskPassError{ cause: io::Error }, + AskPassError { cause: io::Error }, #[fail(display = "unable to read keyfile: {}", cause)] KeyfileError { cause: io::Error }, #[fail(display = "authenticator error: {}", cause)] AuthenticatorError { cause: ctap::FidoError }, #[fail(display = "no authenticator found, please ensure you device is plugged in")] NoAuthenticatorError, - #[fail(display = "no authenticator found, please ensure you device is plugged in")] + #[fail(display = "luks err")] LuksError { cause: cryptsetup_rs::device::Error }, #[fail(display = "no authenticator found, please ensure you device is plugged in")] IoError { cause: io::Error }, #[fail(display = "failed to parse config, please check formatting and contents")] ConfigurationError { cause: serde_json::error::Error }, + #[fail(display = "the submitted secret is not applicable to this luks device")] + WrongSecret, } use Fido2LuksError::*; impl From for Fido2LuksError { fn from(e: FidoError) -> Self { - AuthenticatorError { cause: e} + AuthenticatorError { cause: e } } } impl From for Fido2LuksError { - fn from(e : cryptsetup_rs::device::Error) -> Self { + fn from(e: cryptsetup_rs::device::Error) -> Self { LuksError { cause: e } } } impl From for Fido2LuksError { - fn from(e : io::Error) -> Self { + fn from(e: io::Error) -> Self { IoError { cause: e } } } impl From for Fido2LuksError { - fn from(e : serde_json::error::Error) -> Self { + fn from(e: serde_json::error::Error) -> Self { ConfigurationError { cause: e } } } - diff --git a/src/main.rs b/src/main.rs index 0fb0315..e73e06c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,25 @@ -#[macro_use] extern crate failure; -#[macro_use] extern crate serde_derive; +#[macro_use] +extern crate failure; +#[macro_use] +extern crate serde_derive; use crate::error::*; use crypto::digest::Digest; use crypto::sha2::Sha256; +use cryptsetup_rs as luks; +use cryptsetup_rs::api::{CryptDeviceHandle, CryptDeviceOpenBuilder, Luks1Params}; +use cryptsetup_rs::Luks1CryptDevice; +use ctap; +use ctap::extensions::hmac::{FidoHmacCredential, HmacExtension}; +use ctap::FidoDevice; +use luks::device::Error::CryptsetupError; +use serde_derive::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::env; use std::fs::File; use std::io::{Read, Write}; -use std::path::{PathBuf, Path}; -use ctap::extensions::hmac::{HmacExtension, FidoHmacCredential}; -use ctap; -use ctap::FidoDevice; -use cryptsetup_rs as luks; -use cryptsetup_rs::Luks1CryptDevice; -use serde_derive::{Serialize, Deserialize}; +use std::path::{Path, PathBuf}; use std::process::Command; + mod error; #[derive(Debug, Deserialize, Serialize)] @@ -21,18 +28,39 @@ pub struct Config { pub input_salt: InputSalt, pub device: PathBuf, pub mapper_name: String, - pub password_helper: PathBuf, + pub password_helper: String, +} + +impl Config { + pub fn load_default_location() -> Fido2LuksResult { + Self::load_config( + &mut File::open( + env::vars() + .collect::>() + .get("FIDO2LUKS_CONFIG") + .unwrap_or(&"/etc/fido2luks.json".to_owned()), + ) + .or(File::open("fido2luks.json"))?, + ) + } + + pub fn load_config(reader: &mut dyn Read) -> Fido2LuksResult { + let mut conf_str = String::new(); + reader.read_to_string(&mut conf_str)?; + + Ok(serde_json::from_str(&conf_str)?) + } } impl Default for Config { fn default() -> Self { - Config { - credential_id: "generate using solo key make-credential".into(), - input_salt: Default::default(), - device: "/dev/some-vg/my-volume".into(), - mapper_name: "2fa-secured-luks".into(), - password_helper: "/lib/cryptsetup/askpass".into() - } + Config { + credential_id: "generate using solo key make-credential".into(), + input_salt: Default::default(), + device: "/dev/some-vg/my-volume".into(), + mapper_name: "2fa-secured-luks".into(), + password_helper: "/usr/bin/systemd-ask-password --no-tty --no-output --id='fido2luks' --keyname='fido2luks' 'Please enter second factor for LUKS disk encryption!'".into(), + } } } @@ -45,12 +73,12 @@ pub enum InputSalt { impl Default for InputSalt { fn default() -> Self { - InputSalt::AskPassword + InputSalt::AskPassword } } impl InputSalt { - fn obtain(&self, password_helper: &PathBuf) -> Fido2LuksResult<[u8; 32]> { + fn obtain(&self, password_helper: &str) -> Fido2LuksResult<[u8; 32]> { let mut digest = Sha256::new(); match self { InputSalt::File { path } => { @@ -69,10 +97,16 @@ impl InputSalt { do_io().map_err(|cause| Fido2LuksError::KeyfileError { cause })?; } InputSalt::AskPassword => { - let password =Command::new(password_helper).arg("FIDO2 Password salt:").output().map_err(|e| Fido2LuksError::AskPassError{ cause: e })?.stdout; + let mut helper_parts = password_helper.split(" "); + + let password = Command::new((&mut helper_parts).next().unwrap()) + .args(helper_parts) + .output() + .map_err(|e| Fido2LuksError::AskPassError { cause: e })? + .stdout; digest.input(&password); } - InputSalt::Both { path} => { + InputSalt::Both { path } => { digest.input(&InputSalt::AskPassword.obtain(password_helper)?); digest.input(&InputSalt::File { path: path.clone() }.obtain(password_helper)?) } @@ -84,15 +118,15 @@ impl InputSalt { } fn open_container(device: &PathBuf, name: &str, secret: &[u8; 32]) -> Fido2LuksResult<()> { - let mut handle = luks::open(device)?.luks1()?; + let mut handle = luks::open(device.canonicalize()?)?.luks1()?; let _slot = handle.activate(name, &secret[..])?; Ok(()) } fn perform_challenge(credential_id: &str, salt: &[u8; 32]) -> Fido2LuksResult<[u8; 32]> { - let cred = FidoHmacCredential{ + let cred = FidoHmacCredential { id: hex::decode(credential_id).unwrap(), - rp_id: "hmac".to_string() + rp_id: "hmac".to_string(), }; let mut errs = Vec::new(); for di in ctap::get_devices()? { @@ -109,25 +143,33 @@ fn perform_challenge(credential_id: &str, salt: &[u8; 32]) -> Fido2LuksResult<[u Err(errs.pop().ok_or(Fido2LuksError::NoAuthenticatorError)?)? } -fn load_config(reader: &mut dyn Read) -> Fido2LuksResult { - let mut conf_str = String::new(); - reader.read_to_string(&mut conf_str)?; - - Ok(serde_json::from_str((&conf_str))?) +fn assemble_secret(hmac_result: &[u8], salt: &[u8]) -> [u8; 32] { + let mut digest = Sha256::new(); + digest.input(salt); + digest.input(hmac_result); + let mut secret = [0u8; 32]; + digest.result(&mut secret); + secret } -fn setup() { +fn setup() -> Fido2LuksResult<()> { let mut config = Config::default(); let save_config = |c: &Config| { - File::create("fido2luks.json").expect("Failed to save config").write_all(serde_json::to_string_pretty(c).unwrap().as_bytes()).expect("Failed to save config"); + File::create("fido2luks.json") + .expect("Failed to save config") + .write_all(serde_json::to_string_pretty(c).unwrap().as_bytes()) + .expect("Failed to save config"); }; fn ask_str(q: &str) -> String { let stdin = std::io::stdin(); let mut s = String::new(); print!("{}", q); - std::io::stdout().flush().ok().expect("Could not flush stdout"); + std::io::stdout() + .flush() + .ok() + .expect("Could not flush stdout"); stdin.read_line(&mut s).expect("Failed to read rom stdin"); s.trim().to_owned() } @@ -145,7 +187,7 @@ fn setup() { ccred = Some(cred); break; } - Err(e) => println!("Failed to to obtain credential trying next device(if applicable)"), + Err(_e) => println!("Failed to to obtain credential trying next device(if applicable)"), } } config.credential_id = hex::encode(ccred.expect("No credential could be obtained").id); @@ -153,8 +195,10 @@ fn setup() { loop { let device = ask_str("Path to your luks device: "); - if Path::new(&device).exists() || ask_bool(&format!("{} does not exist, save anyway?", device)) { - config.device = device.into(); + if Path::new(&device).exists() + || ask_bool(&format!("{} does not exist, save anyway?", device)) + { + config.device = device.into(); break; } } @@ -166,22 +210,70 @@ fn setup() { save_config(&config); println!("Config saved to: fido2luks.json"); - + Ok(()) } -fn main() -> Fido2LuksResult<()> { - let conf = load_config(&mut File::open("/etc/fido2luks.json").or(File::open("fido2luks.json"))?)?; +fn add_key_to_luks(conf: &Config) -> Fido2LuksResult { + fn offer_format( + _dev: CryptDeviceOpenBuilder, + ) -> Fido2LuksResult> { + unimplemented!() + } + let dev = || -> luks::device::Result { + luks::open(&conf.device.canonicalize()?) + }; + 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?, + }; + + let secret = { + let salt = conf.input_salt.obtain(&conf.password_helper)?; + + assemble_secret(&perform_challenge(&conf.credential_id, &salt)?, &salt) + }; + dbg!("Adding key"); + let slot = handle.add_keyslot(&secret, None, None)?; + Ok(slot) +} + +fn open() -> Fido2LuksResult<()> { + let conf = Config::load_default_location()?; let salt = conf.input_salt.obtain(&conf.password_helper)?; dbg!(hex::encode(&salt)); let secret = { - let mut digest = Sha256::new(); - digest.input(&salt); - digest.input(&perform_challenge(&conf.credential_id, &salt)?); - let mut secret = [0u8; 32]; - digest.result(&mut secret); - secret + let salt = conf.input_salt.obtain(&conf.password_helper)?; + + assemble_secret(&perform_challenge(&conf.credential_id, &salt)?, &salt) }; dbg!(hex::encode(&secret)); - open_container(&conf.device, &conf.mapper_name,&secret)?; + match open_container(&conf.device, &conf.mapper_name, &secret) { + Err(Fido2LuksError::LuksError { + cause: CryptsetupError(errno), + }) if errno.0 == 1 => Err(Fido2LuksError::WrongSecret)?, + e => e?, + }; Ok(()) } + +fn main() -> Fido2LuksResult<()> { + let args: Vec<_> = env::args().skip(1).collect(); //Ignore program name -> Vec + if args.is_empty() { + open() + } else { + match args.first().map(|s| s.as_ref()).unwrap() { + "addkey" => add_key_to_luks(&Config::load_default_location()?).map(|_| ()), + "setup" => setup(), + _ => { + eprintln!("Usage: setup | addkey"); + Ok(()) + } + } + } +}