Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ctap2 non-exhaustive #42

Merged
merged 2 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub mod make_credential;
pub type Result<T> = core::result::Result<T, Error>;

#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
#[allow(clippy::large_enum_variant)]
// clippy says...large size difference
/// Enum of all CTAP2 requests.
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<bool>,
Expand Down Expand Up @@ -247,6 +250,7 @@ impl<A: SerializeAttestedCredentialData, E: serde::Serialize> AuthenticatorData<
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum Error {
Success = 0x00,
InvalidCommand = 0x01,
Expand Down
3 changes: 3 additions & 0 deletions src/ctap2/client_pin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/ctap2/credential_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
34 changes: 30 additions & 4 deletions src/ctap2/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -18,7 +19,8 @@ pub struct HmacSecretInput {
pub pin_protocol: Option<u32>,
}

#[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")]
Expand All @@ -29,7 +31,8 @@ pub struct ExtensionsInput {
pub large_blob_key: Option<bool>,
}

#[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")]
Expand All @@ -50,6 +53,7 @@ pub type AuthenticatorData = super::AuthenticatorData<NoAttestedCredentialData,
pub type AllowList<'a> = Vec<PublicKeyCredentialDescriptorRef<'a>, 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>,
Expand All @@ -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<PublicKeyCredentialDescriptor>,
pub credential: PublicKeyCredentialDescriptor,
pub auth_data: Bytes<AUTHENTICATOR_DATA_LENGTH>,
pub signature: Bytes<ASN1_SIGNATURE_LENGTH>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -87,3 +91,25 @@ pub struct Response {
#[serde(skip_serializing_if = "Option::is_none")]
pub large_blob_key: Option<Bytes<32>>,
}

#[derive(Debug)]
pub struct ResponseBuilder {
pub credential: PublicKeyCredentialDescriptor,
pub auth_data: Bytes<AUTHENTICATOR_DATA_LENGTH>,
pub signature: Bytes<ASN1_SIGNATURE_LENGTH>,
}

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,
}
}
}
27 changes: 27 additions & 0 deletions src/ctap2/get_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -78,7 +79,33 @@ impl Default for Response {
}
}

#[derive(Debug)]
pub struct ResponseBuilder {
pub versions: Vec<String<12>, 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")]
Expand Down
2 changes: 2 additions & 0 deletions src/ctap2/large_blobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
27 changes: 26 additions & 1 deletion src/ctap2/make_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ impl TryFrom<u8> 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")]
Expand All @@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -112,7 +115,28 @@ pub struct Response {
pub large_blob_key: Option<Bytes<32>>,
}

#[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 {
Expand All @@ -121,6 +145,7 @@ pub enum AttestationStatement {
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[non_exhaustive]
#[serde(untagged)]
pub enum AttestationStatementFormat {
None,
Expand Down
Loading