#![cfg_attr(test, feature(split_array, lazy_cell))] use ctap2_proto::{prelude::*, Ctap2_2Authenticator}; use hid::HidAuthenticator; pub mod hid; impl Ctap2_2Authenticator for HidAuthenticator { fn make_credential(&mut self, request: make::Request) -> Result { Ok(self .send(Command::AuthenticatorMakeCredential, request) .unwrap()) // TODO: Properly parse/convert errors } fn get_assertion(&mut self, request: get::Request) -> Result { 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 { 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>> = LazyLock::new(|| Mutex::new(get_authenticator())); fn init() { let _ = env_logger::builder().is_test(true).try_init(); } fn get_authenticator() -> Option { 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 = 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:#?}"); } }