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),
}
}
}
}

View File

@@ -1,29 +1,47 @@
use ctap;
extern crate ctap_hmac as ctap;
use ctap::{FidoCredentialRequestBuilder, FidoDevice};
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)?;
use std::io::stdout;
use std::io::Write;
use structopt;
use structopt::StructOpt;
println!(
"{}",
&[
&user,
&hex::encode(&cred.id)[..],
&hex::encode(&cred.public_key)[..]
]
.join(":")
);
#[derive(Debug, StructOpt)]
#[structopt(name = "fido2pam")]
struct CliOpt {
/// Whether the authenticator should promt the user for verification
#[structopt(short, long = "user-verification")]
uv: bool,
/// Username for which the credential will be requested
username: String,
}
fn main() -> Result<(), ctap::FidoError> {
let opt = CliOpt::from_args();
let device_info = ctap::get_devices()?.next().expect("no device");
let mut device = ctap::FidoDevice::new(&device_info)?;
let req = FidoCredentialRequestBuilder::default()
.rp_id("fido2pam")
.user_name(opt.username.as_ref())
.uv(opt.uv)
.build()
.unwrap();
let cred = device.make_credential(&req)?;
stdout()
.write_all(
&[
&opt.username,
&hex::encode(&cred.id)[..],
&hex::encode(&cred.public_key.unwrap())[..],
]
.join(":")
.as_bytes(),
)
.unwrap();
Ok(())
}