From 521b3d79fc9bec80c4adfdb4ab8ed8d0d284fa62 Mon Sep 17 00:00:00 2001 From: Shimun Date: Tue, 29 Oct 2019 14:24:08 +0100 Subject: [PATCH] first commit --- Cargo.toml | 24 +++++++++++ src/lib.rs | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 29 +++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..86ff245 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "fido2_pam" +version = "0.1.0" +authors = ["shimun "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "pam_fido2" +path = "src/lib.rs" +crate-type = ["cdylib"] + +[[bin]] +name = "fido2-pam-credential" +path = "src/main.rs" + + +[dependencies] +ctap = "0.1.0" +pamsm = "0.2.0" +regex = "1.3.1" +rand = "0.7.2" +hex = "0.4.0" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..665eedf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,115 @@ +#[macro_use] +extern crate pamsm; + +use 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() -> Settings { + let mut creds = Vec::new(); + for path in &["./pam_fido2.conf", "/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((user.to_string(), (&parts.collect::>()[..].join(":")).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(devices) = ctap::get_devices() { + if let Some(dev) = devices.first() { + 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 = settings + .get_credential( + &pamh + .get_cached_user() + .ok() + .map(|name| name.unwrap().to_str().unwrap().to_string()) + .expect("Faied to get username"), + ) + .expect("Couldn't find credential for user"); + 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); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e42b547 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,29 @@ +use ctap; +use hex; +use std::env::args; +use std::error::Error; +fn main() -> Result<(), ctap::FidoError> { + let user = args() + .skip(1) + .next() + .expect("Please supply username at index 0"); + let devices = ctap::get_devices()?; + let device_info = &devices[0]; + let mut device = ctap::FidoDevice::new(device_info)?; + let rp_id = "pam"; + let user_id = [0]; + let user_name = "pam"; + let client_data_hash = [0; 32]; + let cred = device.make_credential(rp_id, &user_id, user_name, &client_data_hash)?; + + println!( + "{}", + &[ + &user, + &hex::encode(&cred.id)[..], + &hex::encode(&cred.public_key)[..] + ] + .join(":") + ); + Ok(()) +}