You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
4.3 KiB
Rust
153 lines
4.3 KiB
Rust
#![cfg_attr(test, feature(split_array, lazy_cell))]
|
|
|
|
use ctap2_proto::{prelude::*, Ctap2_2Authenticator};
|
|
use hid::HidAuthenticator;
|
|
|
|
pub mod hid;
|
|
|
|
impl<D: ctaphid::HidDevice> Ctap2_2Authenticator for HidAuthenticator<D> {
|
|
fn make_credential(&mut self, request: make::Request) -> Result<make::Response, make::Error> {
|
|
Ok(self
|
|
.send(Command::AuthenticatorMakeCredential, request)
|
|
.unwrap()) // TODO: Properly parse/convert errors
|
|
}
|
|
|
|
fn get_assertion(&mut self, request: get::Request) -> Result<get::Response, get::Error> {
|
|
Ok(self
|
|
.send(Command::AuthenticatorGetAssertion, request)
|
|
.unwrap())
|
|
}
|
|
|
|
fn get_info(&self) -> device::Info {
|
|
self.send(Command::AuthenticatorGetInfo, ()).unwrap()
|
|
}
|
|
|
|
fn client_pin(
|
|
_request: client_pin::Request,
|
|
) -> Result<client_pin::Response, client_pin::Error> {
|
|
todo!()
|
|
}
|
|
|
|
fn reset(&mut self) -> Result<(), reset::Error> {
|
|
let _: () = self.send(Command::AuthenticatorReset, ()).unwrap();
|
|
Ok(())
|
|
}
|
|
|
|
fn selection() -> Result<(), ctap2_proto::authenticator::selection::Error> {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
extern crate hidapi;
|
|
|
|
use std::sync::{LazyLock, Mutex};
|
|
|
|
use crate::hid::HidAuthenticator;
|
|
use ctap2_proto::prelude::{credential::public_key, *};
|
|
use rand::{distributions, Rng};
|
|
|
|
static AUTHENTICATOR: LazyLock<Mutex<Option<HidAuthenticator>>> =
|
|
LazyLock::new(|| Mutex::new(get_authenticator()));
|
|
|
|
fn init() {
|
|
let _ = env_logger::builder().is_test(true).try_init();
|
|
}
|
|
|
|
fn get_authenticator() -> Option<HidAuthenticator> {
|
|
let hidapi = hidapi::HidApi::new().unwrap();
|
|
for info in hidapi.device_list() {
|
|
let Ok(device) = info.open_device(&hidapi) else {
|
|
continue;
|
|
};
|
|
let Ok(authenticator) = device.try_into() else { continue };
|
|
return Some(authenticator);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
// #[test]
|
|
fn reset() {
|
|
init();
|
|
|
|
let mut guard = AUTHENTICATOR.lock().unwrap();
|
|
let authenticator = guard.as_mut().unwrap();
|
|
|
|
authenticator.reset().unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_info() {
|
|
init();
|
|
|
|
let guard = AUTHENTICATOR.lock().unwrap();
|
|
let authenticator = guard.as_ref().unwrap();
|
|
|
|
let info = authenticator.get_info();
|
|
println!("deserialized: {:#?}", info);
|
|
}
|
|
|
|
#[test]
|
|
fn make_credential() {
|
|
init();
|
|
|
|
let mut guard = AUTHENTICATOR.lock().unwrap();
|
|
let authenticator = guard.as_mut().unwrap();
|
|
|
|
let info = authenticator.get_info();
|
|
|
|
let client_data_hash: Vec<u8> = rand::thread_rng()
|
|
.sample_iter(&distributions::Standard)
|
|
.take(32)
|
|
.collect();
|
|
|
|
let user_id = rand::thread_rng()
|
|
.sample_iter(&distributions::Standard)
|
|
.take(32)
|
|
.collect();
|
|
|
|
let rp = public_key::RelyingPartyEntity {
|
|
id: "com.example".to_string(),
|
|
name: Some("Example Inc.".into()),
|
|
};
|
|
|
|
let user = public_key::UserEntity {
|
|
id: user_id,
|
|
name: Some("example_user".to_string()),
|
|
display_name: Some("Example User".to_string()),
|
|
};
|
|
|
|
let pub_key_params = info.algorithms.unwrap();
|
|
|
|
let options = [(make::OptionKey::Discoverable, true)].into();
|
|
|
|
let req = make::Request::builder()
|
|
.client_data_hash(&client_data_hash.split_array_ref::<32>().0)
|
|
.relying_party(&rp)
|
|
.user(&user)
|
|
.public_key_credential_params(pub_key_params.as_slice())
|
|
.options(&options)
|
|
.build();
|
|
|
|
println!("request: {req:#?}");
|
|
let response = authenticator.make_credential(req);
|
|
println!("response: {response:#?}");
|
|
|
|
let req = get::Request {
|
|
relying_party_id: &rp.id,
|
|
client_data_hash: client_data_hash.as_slice().try_into().unwrap(),
|
|
allow_list: None,
|
|
extensions: None,
|
|
options: None,
|
|
pin_uv_auth_param: None,
|
|
pin_uv_auth_protocol_version: None,
|
|
};
|
|
|
|
println!("request: {req:#?}");
|
|
let response = authenticator.get_assertion(req);
|
|
println!("response: {response:#?}");
|
|
}
|
|
}
|