diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc9c86..6c2e338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replace `cose` module with `cosey` dependency ([#36][]) - Mark `get_assertion::{ExtensionsInput, ExtensionsOutput}` and `make_credential::Extensions` as non-exhaustive and implement `Default` - Mark CTAP2 request and response types as non-exhaustive where possible +- Use references where possible [#8]: https://github.com/trussed-dev/ctap-types/pull/8 [#9]: https://github.com/solokeys/ctap-types/issues/9 diff --git a/src/authenticator.rs b/src/authenticator.rs index 4e26da8..06dd272 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -12,7 +12,7 @@ pub use ctap2::Authenticator as Ctap2Authenticator; // - second is 10456 bytes #[allow(clippy::large_enum_variant)] pub enum Request<'a> { - Ctap1(ctap1::Request), + Ctap1(ctap1::Request<'a>), Ctap2(ctap2::Request<'a>), } diff --git a/src/ctap1.rs b/src/ctap1.rs index 19e7ef2..ede7de1 100644 --- a/src/ctap1.rs +++ b/src/ctap1.rs @@ -13,11 +13,11 @@ pub mod authenticate { use super::{Bytes, ControlByte}; #[derive(Clone, Debug, Eq, PartialEq)] - pub struct Request { + pub struct Request<'a> { pub control_byte: ControlByte, - pub challenge: Bytes<32>, - pub app_id: Bytes<32>, - pub key_handle: Bytes<255>, + pub challenge: &'a [u8], + pub app_id: &'a [u8], + pub key_handle: &'a [u8], } #[derive(Clone, Debug, Eq, PartialEq)] @@ -32,9 +32,9 @@ pub mod register { use super::Bytes; #[derive(Clone, Debug, Eq, PartialEq)] - pub struct Request { - pub challenge: Bytes<32>, - pub app_id: Bytes<32>, + pub struct Request<'a> { + pub challenge: &'a [u8], + pub app_id: &'a [u8], } #[derive(Clone, Debug, Eq, PartialEq)] @@ -112,9 +112,9 @@ impl TryFrom for ControlByte { pub type Result = core::result::Result; /// Type alias for convenience. -pub type Register = register::Request; +pub type Register<'a> = register::Request<'a>; /// Type alias for convenience. -pub type Authenticate = authenticate::Request; +pub type Authenticate<'a> = authenticate::Request<'a>; /// Type alias for convenience. pub type RegisterResponse = register::Response; @@ -124,9 +124,9 @@ pub type AuthenticateResponse = authenticate::Response; #[derive(Clone, Debug, Eq, PartialEq)] #[allow(clippy::large_enum_variant)] /// Enum of all CTAP1 requests. -pub enum Request { - Register(register::Request), - Authenticate(authenticate::Request), +pub enum Request<'a> { + Register(register::Request<'a>), + Authenticate(authenticate::Request<'a>), Version, } @@ -165,10 +165,10 @@ impl Response { } } -impl TryFrom<&iso7816::Command> for Request { +impl<'a, const S: usize> TryFrom<&'a iso7816::Command> for Request<'a> { type Error = Error; #[inline(never)] - fn try_from(apdu: &iso7816::Command) -> Result { + fn try_from(apdu: &'a iso7816::Command) -> Result { let cla = apdu.class().into_inner(); let ins = match apdu.instruction() { iso7816::Instruction::Unknown(ins) => ins, @@ -196,8 +196,8 @@ impl TryFrom<&iso7816::Command> for Request { return Err(Error::IncorrectDataParameter); } Ok(Request::Register(Register { - challenge: Bytes::from_slice(&request[..32]).unwrap(), - app_id: Bytes::from_slice(&request[32..]).unwrap(), + challenge: &request[..32], + app_id: &request[32..], })) } @@ -213,9 +213,9 @@ impl TryFrom<&iso7816::Command> for Request { } Ok(Request::Authenticate(Authenticate { control_byte, - challenge: Bytes::from_slice(&request[..32]).unwrap(), - app_id: Bytes::from_slice(&request[32..64]).unwrap(), - key_handle: Bytes::from_slice(&request[65..]).unwrap(), + challenge: &request[..32], + app_id: &request[32..64], + key_handle: &request[65..], })) } @@ -233,16 +233,19 @@ impl TryFrom<&iso7816::Command> for Request { /// [`Response`]. pub trait Authenticator { /// Register a U2F credential. - fn register(&mut self, request: ®ister::Request) -> Result; + fn register(&mut self, request: ®ister::Request<'_>) -> Result; /// Authenticate with a U2F credential. - fn authenticate(&mut self, request: &authenticate::Request) -> Result; + fn authenticate( + &mut self, + request: &authenticate::Request<'_>, + ) -> Result; /// Supported U2F version. fn version() -> [u8; 6] { *b"U2F_V2" } #[inline(never)] - fn call_ctap1(&mut self, request: &Request) -> Result { + fn call_ctap1(&mut self, request: &Request<'_>) -> Result { match request { Request::Register(reg) => { debug_now!("CTAP1.REG"); @@ -257,9 +260,9 @@ pub trait Authenticator { } } -impl crate::Rpc for A { +impl crate::Rpc, Response> for A { /// Dispatches the enum of possible requests into the appropriate trait method. - fn call(&mut self, request: &Request) -> Result { + fn call(&mut self, request: &Request<'_>) -> Result { self.call_ctap1(request) } } diff --git a/src/ctap2.rs b/src/ctap2.rs index 2237c1f..8c9c4dd 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -201,12 +201,12 @@ bitflags! { } pub trait SerializeAttestedCredentialData { - fn serialize(&self) -> Bytes; + fn serialize(&self, buffer: &mut SerializedAuthenticatorData) -> Result<()>; } #[derive(Clone, Debug, Eq, PartialEq)] -pub struct AuthenticatorData { - pub rp_id_hash: Bytes<32>, +pub struct AuthenticatorData<'a, A, E> { + pub rp_id_hash: &'a [u8], pub flags: AuthenticatorDataFlags, pub sign_count: u32, pub attested_credential_data: Option, @@ -217,35 +217,34 @@ pub type SerializedAuthenticatorData = Bytes; // The reason for this non-use of CBOR is for compatibility with // FIDO U2F authentication signatures. -impl AuthenticatorData { +impl<'a, A: SerializeAttestedCredentialData, E: serde::Serialize> AuthenticatorData<'a, A, E> { #[inline(never)] - pub fn serialize(&self) -> SerializedAuthenticatorData { + pub fn serialize(&self) -> Result { let mut bytes = SerializedAuthenticatorData::new(); // 32 bytes, the RP id's hash - bytes.extend_from_slice(&self.rp_id_hash).unwrap(); + bytes + .extend_from_slice(self.rp_id_hash) + .map_err(|_| Error::Other)?; // flags - bytes.push(self.flags.bits()).unwrap(); + bytes.push(self.flags.bits()).map_err(|_| Error::Other)?; // signature counts as 32-bit unsigned big-endian integer. bytes .extend_from_slice(&self.sign_count.to_be_bytes()) - .unwrap(); + .map_err(|_| Error::Other)?; // the attested credential data - if let Some(ref attested_credential_data) = &self.attested_credential_data { - bytes - .extend_from_slice(&attested_credential_data.serialize()) - .unwrap(); + if let Some(attested_credential_data) = &self.attested_credential_data { + attested_credential_data.serialize(&mut bytes)?; } // the extensions data if let Some(extensions) = self.extensions.as_ref() { - let mut extensions_buf = [0u8; 128]; - let ser = crate::serde::cbor_serialize(extensions, &mut extensions_buf).unwrap(); - bytes.extend_from_slice(ser).unwrap(); + cbor_smol::cbor_serialize_extending_bytes(extensions, &mut bytes) + .map_err(|_| Error::Other)?; } - bytes + Ok(bytes) } } diff --git a/src/ctap2/get_assertion.rs b/src/ctap2/get_assertion.rs index 4674030..5609861 100644 --- a/src/ctap2/get_assertion.rs +++ b/src/ctap2/get_assertion.rs @@ -1,9 +1,9 @@ -use crate::{Bytes, String, Vec}; +use crate::{Bytes, Vec}; use cosey::EcdhEsHkdf256PublicKey; use serde::{Deserialize, Serialize}; use serde_indexed::{DeserializeIndexed, SerializeIndexed}; -use super::AuthenticatorOptions; +use super::{AuthenticatorOptions, Result}; use crate::sizes::*; use crate::webauthn::*; @@ -40,15 +40,16 @@ pub struct ExtensionsOutput { pub hmac_secret: Option>, } -pub struct NoAttestedCredentialData(core::marker::PhantomData<()>); +pub struct NoAttestedCredentialData; impl super::SerializeAttestedCredentialData for NoAttestedCredentialData { - fn serialize(&self) -> Bytes { - Bytes::new() + fn serialize(&self, _buffer: &mut super::SerializedAuthenticatorData) -> Result<()> { + Ok(()) } } -pub type AuthenticatorData = super::AuthenticatorData; +pub type AuthenticatorData<'a> = + super::AuthenticatorData<'a, NoAttestedCredentialData, ExtensionsOutput>; pub type AllowList<'a> = Vec, MAX_CREDENTIAL_COUNT_IN_LIST>; @@ -56,8 +57,8 @@ pub type AllowList<'a> = Vec, MAX_CREDENTIA #[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { - pub rp_id: String<64>, - pub client_data_hash: Bytes<32>, + pub rp_id: &'a str, + pub client_data_hash: &'a serde_bytes::Bytes, #[serde(skip_serializing_if = "Option::is_none")] pub allow_list: Option>, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/ctap2/make_credential.rs b/src/ctap2/make_credential.rs index 1b9f485..1f9e2f1 100644 --- a/src/ctap2/make_credential.rs +++ b/src/ctap2/make_credential.rs @@ -3,7 +3,7 @@ use crate::{Bytes, String, Vec}; use serde::{Deserialize, Serialize}; use serde_indexed::{DeserializeIndexed, SerializeIndexed}; -use super::AuthenticatorOptions; +use super::{AuthenticatorOptions, Error}; use crate::ctap2::credential_management::CredentialProtectionPolicy; use crate::sizes::*; use crate::webauthn::*; @@ -60,45 +60,41 @@ pub struct Request<'a> { pub type AttestationObject = Response; -pub type AuthenticatorData = super::AuthenticatorData; +pub type AuthenticatorData<'a> = + super::AuthenticatorData<'a, AttestedCredentialData<'a>, Extensions>; // NOTE: This is not CBOR, it has a custom encoding... // https://www.w3.org/TR/webauthn/#sec-attested-credential-data #[derive(Clone, Debug, Eq, PartialEq)] -pub struct AttestedCredentialData { - pub aaguid: Bytes<16>, +pub struct AttestedCredentialData<'a> { + pub aaguid: &'a [u8], // this is where "unlimited non-resident keys" get stored // TODO: Model as actual credential ID, with ser/de to bytes (format is up to authenticator) - pub credential_id: Bytes, - pub credential_public_key: Bytes, + pub credential_id: &'a [u8], + pub credential_public_key: &'a [u8], } -impl super::SerializeAttestedCredentialData for AttestedCredentialData { - fn serialize(&self) -> Bytes { - let mut bytes = Vec::::new(); +impl<'a> super::SerializeAttestedCredentialData for AttestedCredentialData<'a> { + fn serialize(&self, buffer: &mut super::SerializedAuthenticatorData) -> Result<(), Error> { + // TODO: validate lengths of credential ID and credential public key // 16 bytes, the aaguid - bytes.extend_from_slice(&self.aaguid).unwrap(); - + buffer + .extend_from_slice(self.aaguid) + .map_err(|_| Error::Other)?; // byte length of credential ID as 16-bit unsigned big-endian integer. - bytes - .extend_from_slice(&(self.credential_id.len() as u16).to_be_bytes()) - .unwrap(); + let credential_id_len = + u16::try_from(self.credential_id.len()).map_err(|_| Error::Other)?; + buffer + .extend_from_slice(&credential_id_len.to_be_bytes()) + .map_err(|_| Error::Other)?; // raw bytes of credential ID - bytes - .extend_from_slice(&self.credential_id[..self.credential_id.len()]) - .unwrap(); - - // use existing `bytes` buffer - // let mut cbor_key = [0u8; 128]; - - // CHANGE this back if credential_public_key is not serialized again - // let l = crate::serde::cbor_serialize(&self.credential_public_key, &mut cbor_key).unwrap(); - // bytes.extend_from_slice(&cbor_key[..l]).unwrap(); - bytes - .extend_from_slice(&self.credential_public_key) - .unwrap(); - - Bytes::from(bytes) + buffer + .extend_from_slice(self.credential_id) + .map_err(|_| Error::Other)?; + buffer + .extend_from_slice(self.credential_public_key) + .map_err(|_| Error::Other)?; + Ok(()) } } diff --git a/src/sizes.rs b/src/sizes.rs index 8b440ff..3f36595 100644 --- a/src/sizes.rs +++ b/src/sizes.rs @@ -1,7 +1,3 @@ -pub const ATTESTED_CREDENTIAL_DATA_LENGTH: usize = 612; -// // not sure why i can't use `::to_usize()` here? -// pub const ATTESTED_CREDENTIAL_DATA_LENGTH_BYTES: usize = 512; - pub const AUTHENTICATOR_DATA_LENGTH: usize = 676; // pub const AUTHENTICATOR_DATA_LENGTH_BYTES: usize = 512;