tmp
parent
0fb2487154
commit
cf792b97cb
@ -0,0 +1,187 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use ctap2_proto::extensions;
|
||||||
|
use ctap2_proto::prelude::*;
|
||||||
|
use ctap2_proto::Ctap2_2Authenticator;
|
||||||
|
|
||||||
|
extern crate ctap2_proto;
|
||||||
|
|
||||||
|
struct FidoKey(ctap_hid_fido2::FidoKeyHid);
|
||||||
|
|
||||||
|
impl Deref for FidoKey {
|
||||||
|
type Target = ctap_hid_fido2::FidoKeyHid;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ctap2_2Authenticator for FidoKey {
|
||||||
|
fn make_credential(&mut self, request: make::Request) -> Result<make::Response, make::Error> {
|
||||||
|
let args = MakeCredentialArgsBuilder::new(
|
||||||
|
&request.relying_party.id,
|
||||||
|
request.client_data_hash.as_ref(),
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let attestation = match self.make_credential_with_args(&args) {
|
||||||
|
Ok(attestation) => attestation,
|
||||||
|
Err(e) => {
|
||||||
|
todo!("unhandled error: {e}") // anyhow::Error requires manually
|
||||||
|
// mapping
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let format = if !attestation.flags_attested_credential_data_included {
|
||||||
|
FormatIdentifier::None
|
||||||
|
} else {
|
||||||
|
unimplemented!("do not support attestation yet")
|
||||||
|
};
|
||||||
|
|
||||||
|
let authenticator_data = attestation.auth_data.as_slice().try_into().unwrap();
|
||||||
|
|
||||||
|
let unsigned_extension_outputs: BTreeMap<_, _> = attestation
|
||||||
|
.extensions
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|extension| -> Option<(extensions::Identifier, Vec<u8>)> {
|
||||||
|
match extension {
|
||||||
|
CredentialExtension::CredBlob(_) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
CredentialExtension::CredProtect(_) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
CredentialExtension::HmacSecret(_) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
CredentialExtension::LargeBlobKey(_) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
CredentialExtension::MinPinLength(_) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let unsigned_extension_outputs = if !unsigned_extension_outputs.is_empty() {
|
||||||
|
Some(unsigned_extension_outputs)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(make::Response {
|
||||||
|
format,
|
||||||
|
authenticator_data,
|
||||||
|
enterprise_attestation: None,
|
||||||
|
large_blob_key: None,
|
||||||
|
unsigned_extension_outputs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_assertion(request: get::Request) -> Result<get::Response, get::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_info(&self) -> device::Info {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_pin(request: client_pin::Request) -> Result<client_pin::Response, client_pin::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset() -> Result<(), reset::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection() -> Result<(), ctap2_proto::authenticator::selection::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use ctap_hid_fido2::fidokey::CredentialExtension;
|
||||||
|
use ctap_hid_fido2::{
|
||||||
|
fidokey::{GetAssertionArgsBuilder, MakeCredentialArgsBuilder},
|
||||||
|
verifier, Cfg, FidoKeyHidFactory,
|
||||||
|
};
|
||||||
|
use fido_common::attestation::FormatIdentifier;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let rpid = "reg-auth-example-app";
|
||||||
|
let pin = get_input_with_message("input PIN:");
|
||||||
|
|
||||||
|
println!("Register");
|
||||||
|
// create `challenge`
|
||||||
|
let challenge = verifier::create_challenge();
|
||||||
|
|
||||||
|
// create `MakeCredentialArgs`
|
||||||
|
let make_credential_args = MakeCredentialArgsBuilder::new(rpid, &challenge)
|
||||||
|
.pin(&pin)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let mut cfg = Cfg::init();
|
||||||
|
cfg.enable_log = false;
|
||||||
|
|
||||||
|
// create `FidoKeyHid`
|
||||||
|
let device = FidoKeyHidFactory::create(&cfg).unwrap();
|
||||||
|
|
||||||
|
if device.get_info().unwrap().force_pin_change {
|
||||||
|
device.set_new_pin("1234").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get `Attestation` Object
|
||||||
|
let attestation = device
|
||||||
|
.make_credential_with_args(&make_credential_args)
|
||||||
|
.unwrap();
|
||||||
|
println!("- Register Success");
|
||||||
|
|
||||||
|
// verify `Attestation` Object
|
||||||
|
let verify_result = verifier::verify_attestation(rpid, &challenge, &attestation);
|
||||||
|
if !verify_result.is_success {
|
||||||
|
println!("- ! Verify Failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// store Credential Id and Publickey
|
||||||
|
let userdata_credential_id = verify_result.credential_id;
|
||||||
|
let userdata_credential_public_key = verify_result.credential_public_key;
|
||||||
|
|
||||||
|
println!("Authenticate");
|
||||||
|
// create `challenge`
|
||||||
|
let challenge = verifier::create_challenge();
|
||||||
|
|
||||||
|
// create `GetAssertionArgs`
|
||||||
|
let get_assertion_args = GetAssertionArgsBuilder::new(rpid, &challenge)
|
||||||
|
.pin(&pin)
|
||||||
|
.credential_id(&userdata_credential_id)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// get `Assertion` Object
|
||||||
|
let assertions = device.get_assertion_with_args(&get_assertion_args).unwrap();
|
||||||
|
println!("- Authenticate Success");
|
||||||
|
|
||||||
|
// verify `Assertion` Object
|
||||||
|
if !verifier::verify_assertion(
|
||||||
|
rpid,
|
||||||
|
&userdata_credential_public_key,
|
||||||
|
&challenge,
|
||||||
|
&assertions[0],
|
||||||
|
) {
|
||||||
|
println!("- ! Verify Assertion Failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_input() -> String {
|
||||||
|
let mut word = String::new();
|
||||||
|
std::io::stdin().read_line(&mut word).ok();
|
||||||
|
return word.trim().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_input_with_message(message: &str) -> String {
|
||||||
|
println!("{}", message);
|
||||||
|
let input = get_input();
|
||||||
|
println!();
|
||||||
|
input
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "hid-ctap2-proto"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ciborium = "0.2.1"
|
||||||
|
ciborium-io = "0.2.1"
|
||||||
|
ctap2-proto = { path = "../ctap2-proto", features = ["serde"] }
|
||||||
|
ctaphid = { version = "0.3.1", default_features = false }
|
||||||
|
serde = "1.0.163"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
hidapi = { version = "^1.2.6", default-features = false, features = ["linux-shared-hidraw"] }
|
@ -0,0 +1,185 @@
|
|||||||
|
use ctap2_proto::{prelude::*, Ctap2_2Authenticator};
|
||||||
|
use ctaphid::types::Command;
|
||||||
|
|
||||||
|
pub struct Device<D: ctaphid::HidDevice>(ctaphid::Device<D>);
|
||||||
|
|
||||||
|
impl<D: ctaphid::HidDevice> Device<D> {
|
||||||
|
fn send_raw(&self, command: Command, bytes: &[u8]) -> Result<Vec<u8>, ctaphid::error::Error> {
|
||||||
|
self.0.ctap2(command.into(), bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send<Req, Res>(&self, req: Req) -> Result<Res, ctaphid::error::Error>
|
||||||
|
where
|
||||||
|
Req: serde::Serialize,
|
||||||
|
Res: for<'de> serde::Deserialize<'de>,
|
||||||
|
{
|
||||||
|
let command = Command::Cbor;
|
||||||
|
let mut data: Vec<u8> = Vec::new();
|
||||||
|
ciborium::ser::into_writer(&req, &mut data).map_err(|e| match e {
|
||||||
|
ciborium::ser::Error::Io(_) => ctaphid::error::RequestError::IncompleteWrite,
|
||||||
|
ciborium::ser::Error::Value(desc) => {
|
||||||
|
ctaphid::error::RequestError::PacketSendingFailed(desc.into())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = self.0.ctap2(command.into(), &data)?;
|
||||||
|
|
||||||
|
match ciborium::de::from_reader(response.as_slice()) {
|
||||||
|
Ok(res) => Ok(res),
|
||||||
|
Err(e) => match e {
|
||||||
|
ciborium::de::Error::Io(_) => todo!(),
|
||||||
|
ciborium::de::Error::Syntax(_) => todo!(),
|
||||||
|
ciborium::de::Error::Semantic(_, _) => todo!(),
|
||||||
|
ciborium::de::Error::RecursionLimitExceeded => todo!(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Ctap2_2Authenticator for Device<D>
|
||||||
|
where
|
||||||
|
D: ctaphid::HidDevice,
|
||||||
|
{
|
||||||
|
fn make_credential(&mut self, request: make::Request) -> Result<make::Response, make::Error> {
|
||||||
|
// TODO: How the heck am i supposed to handle errors???
|
||||||
|
self.send(&request).map_err(|e| match e {
|
||||||
|
ctaphid::error::Error::CommandError(e) => match e {
|
||||||
|
ctaphid::error::CommandError::CborError(_) => todo!(),
|
||||||
|
ctaphid::error::CommandError::InvalidPingData => todo!(),
|
||||||
|
ctaphid::error::CommandError::NotSupported(_) => todo!(),
|
||||||
|
},
|
||||||
|
ctaphid::error::Error::RequestError(e) => match e {
|
||||||
|
ctaphid::error::RequestError::IncompleteWrite => todo!(),
|
||||||
|
ctaphid::error::RequestError::MessageFragmentationFailed(_) => todo!(),
|
||||||
|
ctaphid::error::RequestError::PacketSendingFailed(_) => todo!(),
|
||||||
|
ctaphid::error::RequestError::PacketSerializationFailed(_) => todo!(),
|
||||||
|
},
|
||||||
|
ctaphid::error::Error::ResponseError(e) => match e {
|
||||||
|
ctaphid::error::ResponseError::CommandFailed(_) => todo!(),
|
||||||
|
ctaphid::error::ResponseError::MessageDefragmentationFailed(_) => todo!(),
|
||||||
|
ctaphid::error::ResponseError::PacketParsingFailed(_) => todo!(),
|
||||||
|
ctaphid::error::ResponseError::PacketReceivingFailed(_) => todo!(),
|
||||||
|
ctaphid::error::ResponseError::Timeout => todo!(),
|
||||||
|
ctaphid::error::ResponseError::MissingErrorCode => todo!(),
|
||||||
|
ctaphid::error::ResponseError::UnexpectedCommand { expected, actual } => todo!(),
|
||||||
|
ctaphid::error::ResponseError::UnexpectedKeepAlive(_) => todo!(),
|
||||||
|
ctaphid::error::ResponseError::UnexpectedResponseData(_) => todo!(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_assertion(request: get::Request) -> Result<get::Response, get::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_info(&self) -> device::Info {
|
||||||
|
let info = self.send_raw(Command::Cbor, &[1u8, 4]).unwrap();
|
||||||
|
println!("info: {info:#?}");
|
||||||
|
let info: device::Info =
|
||||||
|
ciborium::de::from_reader::<device::Info, _>(info.as_slice()).unwrap();
|
||||||
|
info
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_pin(request: client_pin::Request) -> Result<client_pin::Response, client_pin::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset() -> Result<(), reset::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection() -> Result<(), ctap2_proto::authenticator::selection::Error> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::ffi::CStr;
|
||||||
|
|
||||||
|
use ctap2_proto::Ctap2_2Authenticator;
|
||||||
|
use ctaphid::{
|
||||||
|
error::{RequestError, ResponseError},
|
||||||
|
types::Command,
|
||||||
|
};
|
||||||
|
struct HidDevice(hidapi::HidDevice);
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct HidDeviceInfoMy(hidapi::DeviceInfo);
|
||||||
|
|
||||||
|
impl ctaphid::HidDeviceInfo for HidDeviceInfoMy {
|
||||||
|
fn vendor_id(&self) -> u16 {
|
||||||
|
hidapi::DeviceInfo::vendor_id(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn product_id(&self) -> u16 {
|
||||||
|
hidapi::DeviceInfo::product_id(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> std::borrow::Cow<'_, str> {
|
||||||
|
let cstr: &CStr = hidapi::DeviceInfo::path(&self.0);
|
||||||
|
let s = cstr.to_str().unwrap();
|
||||||
|
std::borrow::Cow::from(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ctaphid::HidDevice for HidDevice {
|
||||||
|
type Info = HidDeviceInfoMy;
|
||||||
|
|
||||||
|
fn send(&self, data: &[u8]) -> Result<(), ctaphid::error::RequestError> {
|
||||||
|
println!("sending bytes: {data:#?}");
|
||||||
|
hidapi::HidDevice::write(&self.0, data)
|
||||||
|
.map_err(|e| RequestError::PacketSendingFailed(e.into()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive<'a>(
|
||||||
|
&self,
|
||||||
|
buffer: &'a mut [u8],
|
||||||
|
timeout: Option<std::time::Duration>,
|
||||||
|
) -> Result<&'a [u8], ctaphid::error::ResponseError> {
|
||||||
|
println!("reading bytes");
|
||||||
|
let duration = if let Some(timeout) = timeout {
|
||||||
|
i32::try_from(timeout.as_millis())
|
||||||
|
.map_err(|err| ResponseError::PacketReceivingFailed(err.into()))?
|
||||||
|
} else {
|
||||||
|
-1
|
||||||
|
};
|
||||||
|
let n = self
|
||||||
|
.0
|
||||||
|
.read_timeout(buffer, duration)
|
||||||
|
.map_err(|err| ResponseError::PacketReceivingFailed(err.into()))?;
|
||||||
|
if n == buffer.len() {
|
||||||
|
Ok(&buffer[1..n])
|
||||||
|
} else if n == 0 {
|
||||||
|
Err(ResponseError::Timeout)
|
||||||
|
} else {
|
||||||
|
Ok(&buffer[..n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_info() {
|
||||||
|
let hidapi = hidapi::HidApi::new().unwrap();
|
||||||
|
let devices = hidapi.device_list();
|
||||||
|
for device_info in devices {
|
||||||
|
let hid_device = hidapi::DeviceInfo::open_device(&device_info, &hidapi);
|
||||||
|
let hid_device = match hid_device {
|
||||||
|
Ok(hid_device) => hid_device,
|
||||||
|
Err(e) => {
|
||||||
|
println!("error: {e:#?}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let device = HidDevice(hid_device);
|
||||||
|
let device =
|
||||||
|
ctaphid::Device::new(device, HidDeviceInfoMy(device_info.to_owned())).unwrap();
|
||||||
|
let device = super::Device(device);
|
||||||
|
println!("info: {:#?}", device.0.ctap2(Command::Cbor.into(), &[]));
|
||||||
|
}
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn quickstart() {}
|
||||||
|
}
|
Loading…
Reference in New Issue