diff --git a/CHANGELOG.md b/CHANGELOG.md index dd591df..ccc9c86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update for compatibility with PIN protocol 2 - Add support for permissions in `ctap2::client_pin` - 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 [#8]: https://github.com/trussed-dev/ctap-types/pull/8 [#9]: https://github.com/solokeys/ctap-types/issues/9 diff --git a/src/ctap2.rs b/src/ctap2.rs index 784609b..2237c1f 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -20,6 +20,7 @@ pub mod make_credential; pub type Result = core::result::Result; #[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] #[allow(clippy::large_enum_variant)] // clippy says...large size difference /// Enum of all CTAP2 requests. @@ -127,6 +128,7 @@ impl<'a> Request<'a> { } #[derive(Clone, Debug, PartialEq)] +#[non_exhaustive] /// Enum of all CTAP2 responses. #[allow(clippy::large_enum_variant)] pub enum Response { @@ -176,6 +178,7 @@ impl Response { } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct AuthenticatorOptions { #[serde(skip_serializing_if = "Option::is_none")] pub rk: Option, @@ -247,6 +250,7 @@ impl AuthenticatorData< } #[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[non_exhaustive] pub enum Error { Success = 0x00, InvalidCommand = 0x01, diff --git a/src/ctap2/client_pin.rs b/src/ctap2/client_pin.rs index e35a77f..76c27c9 100644 --- a/src/ctap2/client_pin.rs +++ b/src/ctap2/client_pin.rs @@ -5,6 +5,7 @@ use serde_indexed::{DeserializeIndexed, SerializeIndexed}; use serde_repr::{Deserialize_repr, Serialize_repr}; #[derive(Clone, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)] +#[non_exhaustive] #[repr(u8)] pub enum PinV1Subcommand { GetRetries = 0x01, @@ -34,6 +35,7 @@ bitflags! { // maximum consecutive incorrect PIN attempts: 8 #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { // 0x01 @@ -88,6 +90,7 @@ pub struct Request<'a> { } #[derive(Clone, Debug, Default, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { // 0x01, like ClientPinParameters::key_agreement diff --git a/src/ctap2/credential_management.rs b/src/ctap2/credential_management.rs index fc8b049..08cc767 100644 --- a/src/ctap2/credential_management.rs +++ b/src/ctap2/credential_management.rs @@ -22,6 +22,7 @@ pub enum CredentialProtectionPolicy { } #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize_repr, Deserialize_repr)] +#[non_exhaustive] #[repr(u8)] pub enum Subcommand { GetCredsMetadata = 0x01, @@ -34,6 +35,7 @@ pub enum Subcommand { } #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct SubcommandParameters<'a> { // 0x01 @@ -48,6 +50,7 @@ pub struct SubcommandParameters<'a> { } #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { // 0x01 @@ -64,6 +67,7 @@ pub struct Request<'a> { } #[derive(Clone, Debug, Default, Eq, PartialEq, SerializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { // Metadata diff --git a/src/ctap2/get_assertion.rs b/src/ctap2/get_assertion.rs index ed75b2a..4674030 100644 --- a/src/ctap2/get_assertion.rs +++ b/src/ctap2/get_assertion.rs @@ -8,6 +8,7 @@ use crate::sizes::*; use crate::webauthn::*; #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct HmacSecretInput { pub key_agreement: EcdhEsHkdf256PublicKey, @@ -18,7 +19,8 @@ pub struct HmacSecretInput { pub pin_protocol: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct ExtensionsInput { #[serde(rename = "hmac-secret")] #[serde(skip_serializing_if = "Option::is_none")] @@ -29,7 +31,8 @@ pub struct ExtensionsInput { pub large_blob_key: Option, } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct ExtensionsOutput { #[serde(rename = "hmac-secret")] #[serde(skip_serializing_if = "Option::is_none")] @@ -50,6 +53,7 @@ pub type AuthenticatorData = super::AuthenticatorData = Vec, MAX_CREDENTIAL_COUNT_IN_LIST>; #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { pub rp_id: String<64>, @@ -70,10 +74,10 @@ pub struct Request<'a> { // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential // does not coincide with what python-fido2 expects in AttestationObject.__init__ *at all* :'-) #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { - #[serde(skip_serializing_if = "Option::is_none")] - pub credential: Option, + pub credential: PublicKeyCredentialDescriptor, pub auth_data: Bytes, pub signature: Bytes, #[serde(skip_serializing_if = "Option::is_none")] @@ -87,3 +91,25 @@ pub struct Response { #[serde(skip_serializing_if = "Option::is_none")] pub large_blob_key: Option>, } + +#[derive(Debug)] +pub struct ResponseBuilder { + pub credential: PublicKeyCredentialDescriptor, + pub auth_data: Bytes, + pub signature: Bytes, +} + +impl ResponseBuilder { + #[inline(always)] + pub fn build(self) -> Response { + Response { + credential: self.credential, + auth_data: self.auth_data, + signature: self.signature, + user: None, + number_of_credentials: None, + user_selected: None, + large_blob_key: None, + } + } +} diff --git a/src/ctap2/get_info.rs b/src/ctap2/get_info.rs index 01d3869..056e33c 100644 --- a/src/ctap2/get_info.rs +++ b/src/ctap2/get_info.rs @@ -6,6 +6,7 @@ use serde_indexed::{DeserializeIndexed, SerializeIndexed}; pub type AuthenticatorInfo = Response; #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { // 0x01 @@ -78,7 +79,33 @@ impl Default for Response { } } +#[derive(Debug)] +pub struct ResponseBuilder { + pub versions: Vec, 4>, + pub aaguid: Bytes<16>, +} + +impl ResponseBuilder { + #[inline(always)] + pub fn build(self) -> Response { + Response { + versions: self.versions, + aaguid: self.aaguid, + extensions: None, + options: None, + max_msg_size: None, + pin_protocols: None, + max_creds_in_list: None, + max_cred_id_length: None, + transports: None, + algorithms: None, + max_serialized_large_blob_array: None, + } + } +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] #[serde(rename_all = "camelCase")] pub struct CtapOptions { #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/ctap2/large_blobs.rs b/src/ctap2/large_blobs.rs index 438b44e..e36ce55 100644 --- a/src/ctap2/large_blobs.rs +++ b/src/ctap2/large_blobs.rs @@ -4,6 +4,7 @@ use serde_indexed::{DeserializeIndexed, SerializeIndexed}; // See: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#largeBlobsRW #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { // 0x01 @@ -26,6 +27,7 @@ pub struct Request<'a> { } #[derive(Clone, Debug, Default, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { // 0x01 diff --git a/src/ctap2/make_credential.rs b/src/ctap2/make_credential.rs index 9f73a96..1b9f485 100644 --- a/src/ctap2/make_credential.rs +++ b/src/ctap2/make_credential.rs @@ -21,7 +21,8 @@ impl TryFrom for CredentialProtectionPolicy { } } -#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +#[non_exhaustive] pub struct Extensions { #[serde(rename = "credProtect")] #[serde(skip_serializing_if = "Option::is_none")] @@ -36,6 +37,7 @@ pub struct Extensions { } #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { pub client_data_hash: &'a serde_bytes::Bytes, @@ -101,6 +103,7 @@ impl super::SerializeAttestedCredentialData for AttestedCredentialData { } #[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed)] +#[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { pub fmt: String<32>, @@ -112,7 +115,28 @@ pub struct Response { pub large_blob_key: Option>, } +#[derive(Debug)] +pub struct ResponseBuilder { + pub fmt: String<32>, + pub auth_data: super::SerializedAuthenticatorData, + pub att_stmt: AttestationStatement, +} + +impl ResponseBuilder { + #[inline(always)] + pub fn build(self) -> Response { + Response { + fmt: self.fmt, + auth_data: self.auth_data, + att_stmt: self.att_stmt, + ep_att: None, + large_blob_key: None, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(untagged)] #[allow(clippy::large_enum_variant)] pub enum AttestationStatement { @@ -121,6 +145,7 @@ pub enum AttestationStatement { } #[derive(Clone, Debug, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(untagged)] pub enum AttestationStatementFormat { None,