#[macro_use] extern crate pamsm; extern crate ctap_hmac as ctap; use pamsm::{Pam, PamError, PamFlag, PamServiceModule}; use rand::Rng; use regex::Regex; use std::fs::File; use std::io::{self, prelude::*, BufReader}; use std::time::{Duration, SystemTime}; struct PamFido2; struct Settings { pub device_timeout: Duration, pub user_credentials: Vec<(String, String)>, } impl Default for Settings { fn default() -> Self { Settings { device_timeout: Duration::from_secs(15), user_credentials: vec![("091566e43802c5a29971c1e08d7865d959af862cc28af22dacf413ac26b90f6dea7d1ac491d9d3712c63f7b8d6cfadf86d057d099d382246dbe9c87f133ed167881b65030000".into(),".*".into())], } } } impl Settings { /*pub fn load(file: impl Read) -> io::Result { }*/ pub fn load() -> Settings { let mut creds = Vec::new(); for path in &["/etc/pam_fido2.conf"] { let file = match File::open(&path) { Ok(file) => file, _ => continue, }; let reader = BufReader::new(file); for line in reader.lines() { let line = line.unwrap(); let mut parts = line.split(":"); let user = parts.by_ref().next().unwrap(); creds.push(( (&parts.collect::>()[..].join(":")).to_string(), user.to_string(), )); } } Settings { user_credentials: creds, ..Default::default() } } pub fn get_credential(&self, user: &str) -> Option { for (cred, pattern) in self.user_credentials.iter() { let re = Regex::new(&pattern).expect(&["Invalid regex pattern:", &pattern].join(" ")); if re.is_match(user) { let mut parts = cred.split(":"); //TODO: use expect let id = parts.by_ref().next().unwrap(); let key = parts.by_ref().next().unwrap(); return Some(ctap::FidoCredential { id: hex::decode(id).unwrap(), public_key: hex::decode(key).unwrap(), rp_id: "pam".into(), }); } } None } } impl PamFido2 { fn settings(&self) -> Settings { Settings::load() } } impl PamServiceModule for PamFido2 { fn authenticate(self: &Self, pamh: Pam, _: PamFlag, _args: Vec) -> PamError { let settings = self.settings(); let begin = SystemTime::now(); let mut device = loop { if let Ok(mut devices) = ctap::get_devices() { if let Some(dev) = devices.next() { break ctap::FidoDevice::new(&dev).unwrap(); } } if begin.elapsed().unwrap() > settings.device_timeout { return PamError::AUTH_ERR; } }; let challenge = rand::thread_rng().gen::<[u8; 32]>(); let credential = match settings .get_credential( &pamh .get_cached_user() .ok() .map(|name| name.unwrap().to_str().unwrap().to_string()) .expect("Faied to get username"), ) { Some(cred) => cred, _ => return PamError::CRED_UNAVAIL }; match device.get_assertion(&credential, &challenge) { Ok(true) => PamError::SUCCESS, _ => PamError::AUTH_ERR, } } } pamsm_init!(Box::new(PamFido2)); #[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }