diff --git a/tests/basic.rs b/tests/basic.rs index df764fd..418a9d4 100644 --- a/tests/basic.rs +++ b/tests/basic.rs @@ -3,13 +3,8 @@ mod virt; mod webauthn; -use std::collections::BTreeMap; - -use ciborium::Value; -use ctap_types::ctap2::Operation; - use virt::Ctap2Error; -use webauthn::{MakeCredentialRequest, PubKeyCredParam, Rp, User}; +use webauthn::{GetInfo, MakeCredential, PubKeyCredParam, Rp, User}; #[test] fn test_ping() { @@ -21,10 +16,9 @@ fn test_ping() { #[test] fn test_get_info() { virt::run_ctap2(|device| { - let reply: BTreeMap = device.call(Operation::GetInfo, &Value::Null).unwrap(); - let versions: Vec = reply.get(&1).unwrap().deserialized().unwrap(); - assert!(versions.contains(&"FIDO_2_0".to_owned())); - assert!(versions.contains(&"FIDO_2_1".to_owned())); + let reply = device.exec(GetInfo).unwrap(); + assert!(reply.versions.contains(&"FIDO_2_0".to_owned())); + assert!(reply.versions.contains(&"FIDO_2_1".to_owned())); }); } @@ -36,13 +30,11 @@ fn test_make_credential() { .name("john.doe") .display_name("John Doe"); let pub_key_cred_params = vec![PubKeyCredParam::new("public-key", -7)]; - let request = MakeCredentialRequest::new(b"", rp, user, pub_key_cred_params); - let reply: BTreeMap = device - .call(Operation::MakeCredential, &request.into()) - .unwrap(); - assert_eq!(reply.get(&1).unwrap(), &Value::from("packed")); - assert!(reply.contains_key(&2)); - assert!(reply.contains_key(&3)); + let request = MakeCredential::new(b"", rp, user, pub_key_cred_params); + let reply = device.exec(request).unwrap(); + assert_eq!(reply.fmt, "packed"); + assert!(reply.auth_data.is_bytes()); + assert!(reply.att_stmt.is_map()); }); } @@ -54,8 +46,8 @@ fn test_make_credential_invalid_params() { .name("john.doe") .display_name("John Doe"); let pub_key_cred_params = vec![PubKeyCredParam::new("public-key", -11)]; - let request = MakeCredentialRequest::new(b"", rp, user, pub_key_cred_params); - let result = device.call::(Operation::MakeCredential, &request.into()); + let request = MakeCredential::new(b"", rp, user, pub_key_cred_params); + let result = device.exec(request); assert_eq!(result, Err(Ctap2Error(0x26))); }); } diff --git a/tests/virt/mod.rs b/tests/virt/mod.rs index 3f328db..05a4853 100644 --- a/tests/virt/mod.rs +++ b/tests/virt/mod.rs @@ -13,7 +13,6 @@ use std::{ }; use ciborium::Value; -use ctap_types::ctap2::Operation; use ctaphid::{ error::{RequestError, ResponseError}, HidDevice, HidDeviceInfo, @@ -23,9 +22,10 @@ use ctaphid_dispatch::{ types::{Channel, Requester}, }; use fido_authenticator::{Authenticator, Config, Conforming}; -use serde::de::DeserializeOwned; use trussed_staging::virt; +use crate::webauthn::Request; + use pipe::Pipe; static INIT_LOGGER: Once = Once::new(); @@ -92,23 +92,21 @@ where pub struct Ctap2<'a>(ctaphid::Device>); impl Ctap2<'_> { - pub fn call( - &self, - operation: Operation, - data: &Value, - ) -> Result { + pub fn exec(&self, request: R) -> Result { + let request = request.into(); let mut serialized = Vec::new(); - ciborium::into_writer(data, &mut serialized).unwrap(); + ciborium::into_writer(&request, &mut serialized).unwrap(); let reply = self .0 - .ctap2(operation.into(), &serialized) + .ctap2(R::COMMAND, &serialized) .map_err(|err| match err { ctaphid::error::Error::CommandError(ctaphid::error::CommandError::CborError( value, )) => Ctap2Error(value), err => panic!("failed to execute CTAP2 command: {err:?}"), })?; - Ok(ciborium::from_reader(reply.as_slice()).unwrap()) + let value: Value = ciborium::from_reader(reply.as_slice()).unwrap(); + Ok(value.into()) } } diff --git a/tests/webauthn/mod.rs b/tests/webauthn/mod.rs index 5236bb9..0639caf 100644 --- a/tests/webauthn/mod.rs +++ b/tests/webauthn/mod.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use ciborium::Value; #[derive(Default)] @@ -15,6 +17,12 @@ impl From for Value { } } +pub trait Request: Into { + const COMMAND: u8; + + type Reply: From; +} + pub struct Rp { id: String, name: Option, @@ -108,14 +116,14 @@ impl From for Value { } } -pub struct MakeCredentialRequest { +pub struct MakeCredential { client_data_hash: Vec, rp: Rp, user: User, pub_key_cred_params: Vec, } -impl MakeCredentialRequest { +impl MakeCredential { pub fn new( client_data_hash: impl Into>, rp: Rp, @@ -131,8 +139,8 @@ impl MakeCredentialRequest { } } -impl From for Value { - fn from(request: MakeCredentialRequest) -> Value { +impl From for Value { + fn from(request: MakeCredential) -> Value { let mut map = Map::default(); map.push(1, request.client_data_hash); map.push(2, request.rp); @@ -148,3 +156,54 @@ impl From for Value { map.into() } } + +impl Request for MakeCredential { + const COMMAND: u8 = 0x01; + + type Reply = MakeCredentialReply; +} + +#[derive(Debug, PartialEq)] +pub struct MakeCredentialReply { + pub fmt: String, + pub auth_data: Value, + pub att_stmt: Value, +} + +impl From for MakeCredentialReply { + fn from(value: Value) -> Self { + let mut map: BTreeMap = value.deserialized().unwrap(); + Self { + fmt: map.remove(&1).unwrap().deserialized().unwrap(), + auth_data: map.remove(&2).unwrap(), + att_stmt: map.remove(&3).unwrap(), + } + } +} + +pub struct GetInfo; + +impl From for Value { + fn from(_: GetInfo) -> Self { + Self::Null + } +} + +impl Request for GetInfo { + const COMMAND: u8 = 0x04; + + type Reply = GetInfoReply; +} + +pub struct GetInfoReply { + pub versions: Vec, +} + +impl From for GetInfoReply { + fn from(value: Value) -> Self { + let mut map: BTreeMap = value.deserialized().unwrap(); + Self { + versions: map.remove(&1).unwrap().deserialized().unwrap(), + } + } +}