diff --git a/Cargo.lock b/Cargo.lock index b129021..04e4dd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,7 @@ dependencies = [ "serde_derive", "serde_json", "structopt", + "sudo", ] [[package]] @@ -987,6 +988,16 @@ dependencies = [ "syn 1.0.40", ] +[[package]] +name = "sudo" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a88e74edf206f281aff2820aa2066c781331044c770626dcafe19491f214e05" +dependencies = [ + "libc", + "log", +] + [[package]] name = "syn" version = "0.15.44" diff --git a/Cargo.toml b/Cargo.toml index aa4c136..14d75cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ serde_json = "1.0.51" serde_derive = "1.0.106" serde = "1.0.106" pamsm = { version = "0.4.1", features = ["libpam"] } +sudo = "0.5.0" [build-dependencies] ctap_hmac = { version="0.4.2", features = ["request_multiple"] } diff --git a/src/lib.rs b/src/lib.rs index 238be78..c2f3a86 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ use std::collections::{HashMap, HashSet}; use std::ffi::CStr; use std::path::Path; use std::str::FromStr; +use sudo::{self, RunningAs}; pub mod cli_args; pub mod device; @@ -27,7 +28,12 @@ pub mod util; struct PamFido2Luks; impl PamFido2Luks { - fn open(&self, user: String, password: String, args: Vec) -> Fido2LuksResult<()> { + fn open( + &self, + user: String, + mut password: impl FnMut() -> PamResult, + args: Vec, + ) -> Fido2LuksResult<()> { let args: HashMap = args .into_iter() .filter_map(|arg| { @@ -55,11 +61,26 @@ impl PamFido2Luks { .get("name") .map(|name| name.replace("%user%", user.as_str())); + let attempts = args + .get("attempts") + .and_then(|a| a.parse::().ok()) + .unwrap_or(3); + if let (Some(device), Some(name)) = (device, name) { if !Path::new(&device).exists() || Path::new(&format!("/dev/mapper/{}", name)).exists() { return Ok(()); } + // root required to mount luks + match sudo::check() { + RunningAs::User => { + //err + unimplemented!("no root") + } + _ => { + sudo::escalate_if_needed().unwrap(); + } + } let mut device = LuksDevice::load(device)?; let mut additional_credentials: HashSet = HashSet::new(); if device.is_luks2()? { @@ -79,22 +100,24 @@ impl PamFido2Luks { .collect(); let credentials: Vec<&FidoCredential> = credentials.iter().collect(); if !credentials.is_empty() { - let salt = util::sha256(&[password.as_bytes()]); - let secret = util::sha256(&[ - &salt, - &perform_challenge( - &credentials[..], + for _ in 0..attempts { + let salt = util::sha256(&[password().expect("Password").as_bytes()]); + let secret = util::sha256(&[ &salt, - Duration::from_secs(15), - pin.map(AsRef::as_ref), - )? - .0[..], - ]); - device.activate(name.as_str(), &secret[..], None)?; + &perform_challenge( + &credentials[..], + &salt, + Duration::from_secs(15), + pin.map(AsRef::as_ref), + )? + .0[..], + ]); + device.activate(name.as_str(), &secret[..], None)?; + } + Ok(()) } else { unimplemented!("custom error") } - unimplemented!() } else { unimplemented!("custom error") } @@ -107,12 +130,21 @@ impl PamServiceModule for PamFido2Luks { Err(_) => return dbg!(PamError::AUTH_ERR), Ok(p) => p.map(|s| s.to_str().map(str::to_string).unwrap()), }; - let password = match pamh.get_authtok(None) { + let mut password = match pamh.get_authtok(None) { Err(_) => return dbg!(PamError::AUTH_ERR), Ok(p) => p.map(|s| s.to_str().map(str::to_string).unwrap()), }; - if let (Some(user), Some(password)) = (user, password) { - match PamFido2Luks.open(user, password, args) { + if let Some(user) = user { + match PamFido2Luks.open( + user, + move || match password.take() { + Some(pass) => Ok(pass), + None => pamh + .conv(Some("Fido2 salt: "), PamMsgStyle::PROMPT_ECHO_OFF) + .map(|s| s.map(|s| s.to_str().unwrap()).unwrap_or("").to_string()), + }, + args, + ) { Ok(_) => PamError::SUCCESS, Err(e) => match e { //TODO: output more detailed error