support multiple credentials
All checks were successful
continuous-integration/drone/tag Build is passing

This commit is contained in:
2020-04-01 13:14:08 +02:00
parent 42a5a92e4b
commit 725c5f0f2b
5 changed files with 1099 additions and 59 deletions

View File

@@ -1,7 +1,7 @@
#[macro_use]
extern crate pamsm;
use ctap;
extern crate ctap_hmac as ctap;
use ctap::{get_assertion_devices, FidoAssertionRequestBuilder, FidoDevice};
use pamsm::{Pam, PamError, PamFlag, PamServiceModule};
use rand::Rng;
use regex::Regex;
@@ -38,11 +38,12 @@ impl Settings {
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::<Vec<_>>()[..].join(":")).to_string(),
user.to_string(),
));
if let Some(user) = parts.by_ref().next() {
creds.push((
(&parts.collect::<Vec<_>>()[..].join(":")).to_string(),
user.to_string(),
));
}
}
}
Settings {
@@ -51,7 +52,8 @@ impl Settings {
}
}
pub fn get_credential(&self, user: &str) -> Option<ctap::FidoCredential> {
pub fn get_credentials(&self, user: &str) -> Vec<ctap::FidoCredential> {
let mut creds = Vec::new();
for (cred, pattern) in self.user_credentials.iter() {
let re = Regex::new(&pattern).expect(&["Invalid regex pattern:", &pattern].join(" "));
if re.is_match(user) {
@@ -59,14 +61,13 @@ impl Settings {
//TODO: use expect
let id = parts.by_ref().next().unwrap();
let key = parts.by_ref().next().unwrap();
return Some(ctap::FidoCredential {
creds.push(ctap::FidoCredential {
id: hex::decode(id).unwrap(),
public_key: hex::decode(key).unwrap(),
rp_id: "pam".into(),
public_key: hex::decode(key).ok(),
});
}
}
None
creds
}
}
@@ -80,29 +81,38 @@ impl PamServiceModule for PamFido2 {
fn authenticate(self: &Self, pamh: Pam, _: PamFlag, _args: Vec<String>) -> 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,
let credentials = settings.get_credentials(
&pamh
.get_cached_user()
.ok()
.map(|name| name.unwrap().to_str().unwrap().to_string())
.expect("Faied to get username"),
);
if credentials.is_empty() {
return PamError::AUTH_ERR;
}
let slice = credentials.iter().collect::<Vec<_>>();
let request = FidoAssertionRequestBuilder::default()
.rp_id("fido2pam")
.client_data_hash(&challenge[..])
.credentials(&slice[..])
.build()
.unwrap();
loop {
let mut devices = match ctap::get_devices() {
Ok(devices) => devices
.filter_map(|handle| FidoDevice::new(&handle).ok())
.collect::<Vec<_>>(),
Err(_) => return PamError::AUTH_ERR,
};
match get_assertion_devices(&request, devices.iter_mut()) {
Ok(_) => return PamError::SUCCESS,
Err(_) if begin.elapsed().unwrap() > settings.device_timeout => {
return PamError::AUTH_ERR
}
Err(e) => eprintln!("{:?}", e),
}
}
}
}