Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[Unreleased]: https://github.com/trussed-dev/ctap-types/compare/0.6.0-rc.1...HEAD

- Rename `authenticator_config` to `config`.
- Add `platform-serde` feature for additional `Serialize` and `Deserialize` implementations not required by authenticators.

## [0.6.0-rc.1] 2026-05-21

Expand Down
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ serde_test = "1.0.176"
[features]
std = []

# adds additional serde::{Deserialize, Serialize} implementations required by platforms
platform-serde = []

# implements arbitrary::Arbitrary for requests
arbitrary = ["dep:arbitrary", "std"]
# enables all fields for ctap2::get_info
Expand Down
78 changes: 78 additions & 0 deletions src/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,61 @@ pub enum AttestationStatement {
Packed(PackedAttestationStatement),
}

// Hand-rolled Deserialize avoids serde's `#[serde(untagged)]` machinery,
// which pulls in `serde::private::de::Content` (alloc-dependent) and
// would force `serde/alloc` on the whole ctap-types crate. Distinguishes
// variants by structural shape: `None` is an empty CBOR map; `Packed`
// has `alg` + `sig` (+ optional `x5c`).
#[cfg(feature = "platform-serde")]
impl<'de> Deserialize<'de> for AttestationStatement {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = AttestationStatement;
fn expecting(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str("a CBOR map for an attestation statement")
}
fn visit_map<A>(self, mut map: A) -> core::result::Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
use serde::de::Error as _;

let mut alg: Option<i32> = None;
let mut sig: Option<Bytes<ASN1_SIGNATURE_LENGTH>> = None;
let mut x5c: Option<Vec<Bytes<1024>, 1>> = None;
let mut has_values = false;
while let Some(key) = map.next_key::<&str>()? {
has_values = true;
match key {
"alg" => alg = Some(map.next_value()?),
"sig" => sig = Some(map.next_value()?),
"x5c" => x5c = Some(map.next_value()?),
_ => {
let _: serde::de::IgnoredAny = map.next_value()?;
}
}
}
if let Some((alg, sig)) = alg.zip(sig) {
Ok(AttestationStatement::Packed(PackedAttestationStatement {
alg,
sig,
x5c,
}))
} else if !has_values {
Ok(AttestationStatement::None(NoneAttestationStatement {}))
} else {
Err(A::Error::custom("unsupported attestation statement format"))
}
}
}
deserializer.deserialize_map(V)
}
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
Expand Down Expand Up @@ -303,9 +358,11 @@ impl TryFrom<&str> for AttestationStatementFormat {
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "platform-serde", derive(Deserialize))]
pub struct NoneAttestationStatement {}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "platform-serde", derive(Deserialize))]
pub struct PackedAttestationStatement {
pub alg: i32,
pub sig: Bytes<ASN1_SIGNATURE_LENGTH>,
Expand All @@ -329,6 +386,27 @@ impl AttestationFormatsPreference {
}
}

#[cfg(feature = "platform-serde")]
impl Serialize for AttestationFormatsPreference {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeSeq;
// Wire format mirrors the Deserialize impl below: a CBOR array of
// attestation-format strings. The `unknown` flag is a parse-time
// marker — Deserialize sets it when it sees a string it doesn't
// recognize. That source string isn't retained, so we can't round-
// trip it; on the way out we only emit the recognized entries.
let mut seq = serializer.serialize_seq(Some(self.known_formats.len()))?;
for format in &self.known_formats {
let s: &str = (*format).into();
seq.serialize_element(s)?;
}
seq.end()
}
}

impl<'de> Deserialize<'de> for AttestationFormatsPreference {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
Expand Down
1 change: 1 addition & 0 deletions src/ctap2/credential_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ pub struct Request<'a> {
}

#[derive(Clone, Debug, Default, Eq, PartialEq, SerializeIndexed)]
#[cfg_attr(feature = "platform-serde", derive(DeserializeIndexed))]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Response {
Expand Down
2 changes: 2 additions & 0 deletions src/ctap2/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub type AuthenticatorData<'a> =
pub type AllowList<'a> = Vec<PublicKeyCredentialDescriptorRef<'a>, MAX_CREDENTIAL_COUNT_IN_LIST>;

#[derive(Clone, Debug, Eq, PartialEq, DeserializeIndexed)]
#[cfg_attr(feature = "platform-serde", derive(SerializeIndexed))]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Request<'a> {
Expand All @@ -128,6 +129,7 @@ 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)]
#[cfg_attr(feature = "platform-serde", derive(DeserializeIndexed))]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Response {
Expand Down
3 changes: 3 additions & 0 deletions src/ctap2/make_credential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ pub struct ExtensionsOutput {
}

#[derive(Clone, Debug, Eq, PartialEq, DeserializeIndexed)]
#[cfg_attr(feature = "platform-serde", derive(SerializeIndexed))]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Request<'a> {
Expand Down Expand Up @@ -162,6 +163,7 @@ impl super::SerializeAttestedCredentialData for AttestedCredentialData<'_> {
}

#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed)]
#[cfg_attr(feature = "platform-serde", derive(DeserializeIndexed))]
#[non_exhaustive]
#[serde_indexed(offset = 1)]
pub struct Response {
Expand Down Expand Up @@ -198,6 +200,7 @@ impl ResponseBuilder {
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "platform-serde", derive(Deserialize))]
#[non_exhaustive]
pub struct UnsignedExtensionOutputs {}

Expand Down
Loading