From 9e70ef511771aa9ea7f7dd8fbc88bf24308bf3f9 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 21 Jun 2024 19:16:05 +0200 Subject: [PATCH] Add support for attestation format preferences --- CHANGELOG.md | 4 +++ src/ctap2/get_assertion.rs | 10 ++++-- src/ctap2/make_credential.rs | 66 ++++++++++++++++++++++++++++++++++-- 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 014eb47..a89de1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased]: https://github.com/trussed-dev/ctap-types/compare/0.2.0...HEAD +- Remove `Deserialize` implementation for `ctap2::get_assertion::Response` +- Remvoe `Serialize` implementation for `ctap2::{get_assertion, make_credential}::Request` - Add support for CTAP 2.2 - Support the `thirdPartyPayment` extension - Add new fields to `get_info` - Add unsigned extension outputs to `make_credential` and `get_assertion` - Add enterprise attestation support to `get_assertion` + - Add support for attestation statements in `get_assertion` + - Add support for attestation format preferences ## [0.2.0] - 2024-06-21 diff --git a/src/ctap2/get_assertion.rs b/src/ctap2/get_assertion.rs index f9f92c7..eefbb88 100644 --- a/src/ctap2/get_assertion.rs +++ b/src/ctap2/get_assertion.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_bytes::ByteArray; use serde_indexed::{DeserializeIndexed, SerializeIndexed}; +use super::make_credential::{AttestationFormatsPreference, AttestationStatement}; use super::{AuthenticatorOptions, Result}; use crate::sizes::*; use crate::webauthn::*; @@ -65,7 +66,7 @@ pub type AuthenticatorData<'a> = pub type AllowList<'a> = Vec, MAX_CREDENTIAL_COUNT_IN_LIST>; -#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[derive(Clone, Debug, Eq, PartialEq, DeserializeIndexed)] #[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { @@ -83,12 +84,14 @@ pub struct Request<'a> { pub pin_protocol: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enterprise_attestation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub attestation_formats_preference: Option, } // NB: attn object definition / order at end of // 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)] +#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed)] #[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Response { @@ -109,6 +112,8 @@ pub struct Response { pub unsigned_extension_outputs: Option, #[serde(skip_serializing_if = "Option::is_none")] pub ep_att: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub att_stmt: Option, } #[derive(Debug)] @@ -131,6 +136,7 @@ impl ResponseBuilder { large_blob_key: None, unsigned_extension_outputs: None, ep_att: None, + att_stmt: None, } } } diff --git a/src/ctap2/make_credential.rs b/src/ctap2/make_credential.rs index 4ef1441..1c6777b 100644 --- a/src/ctap2/make_credential.rs +++ b/src/ctap2/make_credential.rs @@ -44,7 +44,7 @@ pub struct Extensions { pub third_party_payment: Option, } -#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)] +#[derive(Clone, Debug, Eq, PartialEq, DeserializeIndexed)] #[non_exhaustive] #[serde_indexed(offset = 1)] pub struct Request<'a> { @@ -64,6 +64,8 @@ pub struct Request<'a> { pub pin_protocol: Option, #[serde(skip_serializing_if = "Option::is_none")] pub enterprise_attestation: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub attestation_formats_preference: Option, } pub type AttestationObject = Response; @@ -151,7 +153,7 @@ pub enum AttestationStatement { Packed(PackedAttestationStatement), } -#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[non_exhaustive] #[serde(rename_all = "lowercase")] pub enum AttestationStatementFormat { @@ -159,6 +161,16 @@ pub enum AttestationStatementFormat { Packed, } +impl AttestationStatementFormat { + fn from_str(s: &str) -> Option { + match s { + "none" => Some(Self::None), + "packed" => Some(Self::Packed), + _ => None, + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct NoneAttestationStatement {} @@ -170,6 +182,56 @@ pub struct PackedAttestationStatement { pub x5c: Option, 1>>, } +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AttestationFormatsPreference { + known_formats: Vec, + unknown: bool, +} + +impl AttestationFormatsPreference { + pub fn known_formats(&self) -> &[AttestationStatementFormat] { + &self.known_formats + } + + pub fn includes_unknown_formats(&self) -> bool { + self.unknown + } +} + +impl<'de> Deserialize<'de> for AttestationFormatsPreference { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct ValueVisitor; + + impl<'de> serde::de::Visitor<'de> for ValueVisitor { + type Value = AttestationFormatsPreference; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut preference = AttestationFormatsPreference::default(); + while let Some(value) = seq.next_element::<&str>()? { + if let Some(format) = AttestationStatementFormat::from_str(value) { + preference.known_formats.push(format).ok(); + } else { + preference.unknown = true; + } + } + Ok(preference) + } + } + + deserializer.deserialize_seq(ValueVisitor) + } +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] #[non_exhaustive] pub struct UnsignedExtensionOutputs {}