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