From 5795bfc809e4de733482f10460102cdbf026c3a3 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 09:55:48 +0200 Subject: [PATCH 01/12] Remove unused Rpc trait --- CHANGELOG.md | 2 ++ src/ctap1.rs | 7 ------- src/ctap2.rs | 3 --- src/lib.rs | 5 ----- 4 files changed, 2 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce2a57f..045823d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ 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.4...HEAD +- Remove unused `Rpc` trait. + ## [0.6.0-rc.4] 2026-06-01 [0.6.0-rc.4]: https://github.com/trussed-dev/ctap-types/compare/0.6.0-rc.3...0.6.0-rc.4 diff --git a/src/ctap1.rs b/src/ctap1.rs index 9ce3e55..3d67dd3 100644 --- a/src/ctap1.rs +++ b/src/ctap1.rs @@ -255,13 +255,6 @@ pub trait Authenticator { } } -impl crate::Rpc, Response> for A { - /// Dispatches the enum of possible requests into the appropriate trait method. - fn call(&mut self, request: &Request<'_>) -> Result { - self.call_ctap1(request) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/ctap2.rs b/src/ctap2.rs index 9944987..4407606 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -503,9 +503,6 @@ pub enum Error { } /// CTAP2 authenticator API -/// -/// Note that all Authenticators automatically implement [`crate::Rpc`] with [`Request`] and -/// [`Response`]. pub trait Authenticator { fn get_info(&mut self) -> get_info::Response; diff --git a/src/lib.rs b/src/lib.rs index dabda54..de1fa6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,8 +51,3 @@ impl Display for TryFromStrError { "invalid enum value".fmt(f) } } - -/// Call a remote procedure with a request, receive a response, maybe. -pub trait Rpc { - fn call(&mut self, request: &Request) -> core::result::Result; -} From 53359f713810ed69fa1752dd1e64743d77993f2a Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 10:03:49 +0200 Subject: [PATCH 02/12] Remove cbor-smol re-export Renaming cbor-smol to serde is confusing and the re-export is not necessary as the cbor-smol version is an implementation detail. Fixes: https://github.com/trussed-dev/ctap-types/issues/69 --- CHANGELOG.md | 1 + src/ctap2/client_pin.rs | 2 +- src/lib.rs | 1 - tests/bennofs.rs | 2 +- tests/get_assertion.rs | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 045823d..faf3150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.4...HEAD - Remove unused `Rpc` trait. +- Remove `cbor-smol` re-export as `serde`. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/ctap2/client_pin.rs b/src/ctap2/client_pin.rs index d77e932..c21c1f0 100644 --- a/src/ctap2/client_pin.rs +++ b/src/ctap2/client_pin.rs @@ -512,7 +512,7 @@ mod tests { // The following test would then fail, as [1] != [2] let mut buf = [0u8; 64]; let example = PinV1Subcommand::GetKeyAgreement; - let ser = crate::serde::cbor_serialize(&example, &mut buf).unwrap(); + let ser = cbor_smol::cbor_serialize(&example, &mut buf).unwrap(); assert_eq!(ser, &[0x02]); } } diff --git a/src/lib.rs b/src/lib.rs index de1fa6f..a7e33bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,6 @@ pub mod authenticator; pub mod ctap1; pub mod ctap2; pub(crate) mod operation; -pub use cbor_smol as serde; pub mod sizes; #[cfg(test)] mod test; diff --git a/tests/bennofs.rs b/tests/bennofs.rs index 1034ff4..1c8bdc8 100644 --- a/tests/bennofs.rs +++ b/tests/bennofs.rs @@ -1,4 +1,4 @@ -use ctap_types::serde::{cbor_deserialize, cbor_serialize}; +use cbor_smol::{cbor_deserialize, cbor_serialize}; use serde::{Deserialize, Serialize}; #[derive(Debug, PartialEq, Serialize, Deserialize)] diff --git a/tests/get_assertion.rs b/tests/get_assertion.rs index 51a58fc..0d25b38 100644 --- a/tests/get_assertion.rs +++ b/tests/get_assertion.rs @@ -1,5 +1,5 @@ fn test<'data, T: serde::Deserialize<'data> + std::fmt::Debug>(data: &'data [u8]) { - let result = ctap_types::serde::cbor_deserialize::(data); + let result = cbor_smol::cbor_deserialize::(data); assert!(result.is_ok(), "{:?}", result); } From f8a76d3e84f2d3e4c2344e2833f8a4c335097fb0 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 10:05:24 +0200 Subject: [PATCH 03/12] Make ctap2::CtapMappingError private The type is only used internally and does not have to be part of the public API. --- CHANGELOG.md | 1 + src/ctap2.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index faf3150..714b524 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove unused `Rpc` trait. - Remove `cbor-smol` re-export as `serde`. +- Make `ctap2::CtapMappingError` private. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/ctap2.rs b/src/ctap2.rs index 4407606..972e95d 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -53,7 +53,7 @@ pub enum Request<'a> { Vendor(crate::operation::VendorOperation), } -pub enum CtapMappingError { +enum CtapMappingError { InvalidCommand(u8), ParsingError(cbor_smol::Error), } From 90d2509941a8849c497f93b61d613aed6131e68d Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 10:10:59 +0200 Subject: [PATCH 04/12] ctap2: Set repr(u8) for Error and implement From for u8 Fixes: https://github.com/trussed-dev/ctap-types/issues/63 --- CHANGELOG.md | 1 + src/ctap2.rs | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 714b524..200376a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove unused `Rpc` trait. - Remove `cbor-smol` re-export as `serde`. - Make `ctap2::CtapMappingError` private. +- Set `#[repr(u8)]` for `ctap2::Error` and implement `From` for `u8`. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/ctap2.rs b/src/ctap2.rs index 972e95d..3a07a0e 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -444,6 +444,7 @@ impl<'de> Deserialize<'de> for AttestationFormatsPreference { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[non_exhaustive] +#[repr(u8)] pub enum Error { Success = 0x00, InvalidCommand = 0x01, @@ -502,6 +503,12 @@ pub enum Error { VendorLast = 0xFF, } +impl From for u8 { + fn from(error: Error) -> u8 { + error as _ + } +} + /// CTAP2 authenticator API pub trait Authenticator { fn get_info(&mut self) -> get_info::Response; From d28b26656735682f005fe401ec9a47a8a33dddac Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 10:29:29 +0200 Subject: [PATCH 05/12] ctap2::get_info: Use ByteArray<16> for AAGUID Fixes: https://github.com/trussed-dev/ctap-types/issues/59 --- CHANGELOG.md | 1 + src/ctap2/get_info.rs | 22 +++++++++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 200376a..a6892b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove `cbor-smol` re-export as `serde`. - Make `ctap2::CtapMappingError` private. - Set `#[repr(u8)]` for `ctap2::Error` and implement `From` for `u8`. +- `ctap2::get_info`: Use `ByteArray<16>` instead of `Bytes<16>` for AAGUID. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/ctap2/get_info.rs b/src/ctap2/get_info.rs index 9bbc142..ed36c09 100644 --- a/src/ctap2/get_info.rs +++ b/src/ctap2/get_info.rs @@ -1,8 +1,9 @@ use crate::webauthn::FilteredPublicKeyCredentialParameters; #[cfg(feature = "get-info-full")] use crate::String; -use crate::{Bytes, TryFromStrError, Vec}; +use crate::{TryFromStrError, Vec}; use serde::{Deserialize, Serialize}; +use serde_bytes::ByteArray; use serde_indexed::{DeserializeIndexed, SerializeIndexed}; pub type AuthenticatorInfo = Response; @@ -19,7 +20,7 @@ pub struct Response { pub extensions: Option>, // 0x03 - pub aaguid: Bytes<16>, + pub aaguid: ByteArray<16>, // 0x04 #[serde(skip_serializing_if = "Option::is_none")] @@ -181,13 +182,8 @@ pub struct Response { impl Default for Response { fn default() -> Self { - let mut zero_aaguid = Vec::::new(); - zero_aaguid.resize_default(16).unwrap(); - let mut aaguid = Bytes::new(); - aaguid.resize_zero(16).unwrap(); - let mut response = ResponseBuilder { - aaguid, + aaguid: ByteArray::new([0; 16]), versions: Vec::new(), } .build(); @@ -199,7 +195,7 @@ impl Default for Response { #[derive(Debug)] pub struct ResponseBuilder { pub versions: Vec, - pub aaguid: Bytes<16>, + pub aaguid: ByteArray<16>, } impl ResponseBuilder { @@ -577,7 +573,7 @@ mod tests { #[test] fn test_serde_get_info_minimal() { let versions = Vec::from_slice(&[Version::Fido2_0, Version::Fido2_1]).unwrap(); - let aaguid = Bytes::try_from(&[0xff; 16]).unwrap(); + let aaguid = ByteArray::new([0xff; 16]); let response = ResponseBuilder { versions, aaguid }.build(); assert_tokens( &response, @@ -599,12 +595,12 @@ mod tests { fn test_serde_get_info_default() { // This corresponds to the response sent by the Nitrokey 3, see for example: // https://github.com/Nitrokey/nitrokey-3-firmware/blob/0d7209f1f75354878c0cf3454055defe8372ed14/utils/fido2-mds/metadata/v4/metadata-nk3xn-v4.json - const AAGUID: &[u8] = &[ + const AAGUID: [u8; 16] = [ 236, 153, 219, 25, 205, 31, 76, 6, 162, 169, 148, 15, 23, 166, 163, 11, ]; let versions = Vec::from_slice(&[Version::U2fV2, Version::Fido2_0, Version::Fido2_1]).unwrap(); - let aaguid = Bytes::try_from(AAGUID).unwrap(); + let aaguid = ByteArray::new(AAGUID); let mut options = CtapOptions::default(); options.rk = true; options.plat = Some(false); @@ -641,7 +637,7 @@ mod tests { Token::SeqEnd, // 0x03: aaguid Token::U64(0x03), - Token::BorrowedBytes(AAGUID), + Token::BorrowedBytes(&AAGUID), // 0x04: options Token::U64(0x04), Token::Some, From bd33d9b94793d466f9c5068e73e5f700e21775ac Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 10:31:24 +0200 Subject: [PATCH 06/12] Remove re-exports for heapless and heapless-bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The relevant types are already re-exported directly so we don’t have to re-export the entire crate. --- CHANGELOG.md | 1 + src/lib.rs | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6892b5..e1af898 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Make `ctap2::CtapMappingError` private. - Set `#[repr(u8)]` for `ctap2::Error` and implement `From` for `u8`. - `ctap2::get_info`: Use `ByteArray<16>` instead of `Bytes<16>` for AAGUID. +- Remove re-exports for `heapless` and `heapless-bytes` as the relevant types are already re-exported. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/lib.rs b/src/lib.rs index a7e33bc..610c55c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,9 +19,7 @@ extern crate delog; generate_macros!(); -pub use heapless; -pub use heapless::{String, Vec}; -pub use heapless_bytes; +pub use heapless::{String, Vec, VecView}; pub use heapless_bytes::Bytes; pub use serde_bytes::ByteArray; From d018f0dfe5e4cc12a4c0cdd7af53f35eb13bba69 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 10:38:59 +0200 Subject: [PATCH 07/12] ctap2::get_info: Use enum for authenticator_config_commands Fixes: https://github.com/trussed-dev/ctap-types/issues/85 --- CHANGELOG.md | 1 + src/ctap2/get_info.rs | 178 ++++++++++++++++++++++++------------------ 2 files changed, 104 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1af898..4b6f2f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Set `#[repr(u8)]` for `ctap2::Error` and implement `From` for `u8`. - `ctap2::get_info`: Use `ByteArray<16>` instead of `Bytes<16>` for AAGUID. - Remove re-exports for `heapless` and `heapless-bytes` as the relevant types are already re-exported. +- `ctap2::get_info`: Use subcommand enum for `authenticator_config_commands`. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/ctap2/get_info.rs b/src/ctap2/get_info.rs index ed36c09..90e1229 100644 --- a/src/ctap2/get_info.rs +++ b/src/ctap2/get_info.rs @@ -177,7 +177,7 @@ pub struct Response { // FIDO_2_3 #[cfg(feature = "get-info-full")] #[serde(skip_serializing_if = "Option::is_none")] - pub authenticator_config_commands: Option>, + pub authenticator_config_commands: Option>, } impl Default for Response { @@ -617,83 +617,111 @@ mod tests { response.max_creds_in_list = Some(10); response.max_cred_id_length = Some(255); response.transports = Some(Vec::from_slice(&[Transport::Nfc, Transport::Usb]).unwrap()); - assert_ser_tokens( - &response, - &[ - Token::Map { len: Some(9) }, - // 0x01: versions - Token::U64(0x01), - Token::Seq { len: Some(3) }, - Token::BorrowedStr("U2F_V2"), - Token::BorrowedStr("FIDO_2_0"), - Token::BorrowedStr("FIDO_2_1"), - Token::SeqEnd, - // 0x02: extensions - Token::U64(0x02), - Token::Some, - Token::Seq { len: Some(2) }, - Token::BorrowedStr("credProtect"), - Token::BorrowedStr("hmac-secret"), - Token::SeqEnd, - // 0x03: aaguid - Token::U64(0x03), - Token::BorrowedBytes(&AAGUID), - // 0x04: options - Token::U64(0x04), - Token::Some, - Token::Struct { - name: "CtapOptions", - len: 7, - }, - Token::BorrowedStr("rk"), - Token::Bool(true), - Token::BorrowedStr("up"), - Token::Bool(true), - Token::BorrowedStr("plat"), - Token::Some, - Token::Bool(false), - Token::BorrowedStr("credMgmt"), - Token::Some, - Token::Bool(true), - Token::BorrowedStr("clientPin"), - Token::Some, - Token::Bool(false), - Token::BorrowedStr("largeBlobs"), - Token::Some, - Token::Bool(false), - Token::BorrowedStr("pinUvAuthToken"), - Token::Some, - Token::Bool(true), - Token::StructEnd, - // 0x05: maxMsgSize - Token::U64(0x05), - Token::Some, - Token::U64(3072), - // 0x06: pinUvAuthProtocols - Token::U64(0x06), - Token::Some, - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::U8(0), - Token::SeqEnd, - // 0x07: maxCredentialCountInList - Token::U64(0x07), - Token::Some, - Token::U64(10), - // 0x08: maxCredentialIdLength - Token::U64(0x08), - Token::Some, - Token::U64(255), - // 0x09: transports - Token::U64(0x09), + + #[cfg(feature = "get-info-full")] + { + response.authenticator_config_commands = Some( + Vec::from_slice(&[ + crate::ctap2::config::Subcommand::ToggleAlwaysUv, + crate::ctap2::config::Subcommand::SetMinPINLength, + ]) + .unwrap(), + ); + } + + let len = 9 + if cfg!(feature = "get-info-full") { + 1 + } else { + 0 + }; + + let mut expected = vec![ + Token::Map { len: Some(len) }, + // 0x01: versions + Token::U64(0x01), + Token::Seq { len: Some(3) }, + Token::BorrowedStr("U2F_V2"), + Token::BorrowedStr("FIDO_2_0"), + Token::BorrowedStr("FIDO_2_1"), + Token::SeqEnd, + // 0x02: extensions + Token::U64(0x02), + Token::Some, + Token::Seq { len: Some(2) }, + Token::BorrowedStr("credProtect"), + Token::BorrowedStr("hmac-secret"), + Token::SeqEnd, + // 0x03: aaguid + Token::U64(0x03), + Token::BorrowedBytes(&AAGUID), + // 0x04: options + Token::U64(0x04), + Token::Some, + Token::Struct { + name: "CtapOptions", + len: 7, + }, + Token::BorrowedStr("rk"), + Token::Bool(true), + Token::BorrowedStr("up"), + Token::Bool(true), + Token::BorrowedStr("plat"), + Token::Some, + Token::Bool(false), + Token::BorrowedStr("credMgmt"), + Token::Some, + Token::Bool(true), + Token::BorrowedStr("clientPin"), + Token::Some, + Token::Bool(false), + Token::BorrowedStr("largeBlobs"), + Token::Some, + Token::Bool(false), + Token::BorrowedStr("pinUvAuthToken"), + Token::Some, + Token::Bool(true), + Token::StructEnd, + // 0x05: maxMsgSize + Token::U64(0x05), + Token::Some, + Token::U64(3072), + // 0x06: pinUvAuthProtocols + Token::U64(0x06), + Token::Some, + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::U8(0), + Token::SeqEnd, + // 0x07: maxCredentialCountInList + Token::U64(0x07), + Token::Some, + Token::U64(10), + // 0x08: maxCredentialIdLength + Token::U64(0x08), + Token::Some, + Token::U64(255), + // 0x09: transports + Token::U64(0x09), + Token::Some, + Token::Seq { len: Some(2) }, + Token::BorrowedStr("nfc"), + Token::BorrowedStr("usb"), + Token::SeqEnd, + ]; + if cfg!(feature = "get-info-full") { + expected.extend([ + // 0x1F: authenticatorConfigCommands + Token::U64(0x1F), Token::Some, Token::Seq { len: Some(2) }, - Token::BorrowedStr("nfc"), - Token::BorrowedStr("usb"), + Token::U8(0x02), + Token::U8(0x03), Token::SeqEnd, - Token::MapEnd, - ], - ); + ]); + } + expected.push(Token::MapEnd); + + assert_ser_tokens(&response, &expected); } #[test] From d9f065b365391be974b158210734c2134679dfef Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 11:07:57 +0200 Subject: [PATCH 08/12] webauthn: Make KnownPublicKeyCredentialParameters an enum We already validate the algorithm so we can also just use an enum instead of a struct. Fixes: https://github.com/trussed-dev/ctap-types/issues/62 --- CHANGELOG.md | 1 + src/arbitrary.rs | 8 -------- src/webauthn.rs | 37 ++++++++++++++++++++++++++----------- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b6f2f9..06d8692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ctap2::get_info`: Use `ByteArray<16>` instead of `Bytes<16>` for AAGUID. - Remove re-exports for `heapless` and `heapless-bytes` as the relevant types are already re-exported. - `ctap2::get_info`: Use subcommand enum for `authenticator_config_commands`. +- `webauthn`: Make `KnownPublicKeyCredentialParameters` an enum. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/arbitrary.rs b/src/arbitrary.rs index 9549ed7..10bb00a 100644 --- a/src/arbitrary.rs +++ b/src/arbitrary.rs @@ -235,14 +235,6 @@ impl<'a> Arbitrary<'a> for webauthn::FilteredPublicKeyCredentialParameters { } } -// cannot be derived because we want to make sure that we have valid values -impl<'a> Arbitrary<'a> for webauthn::KnownPublicKeyCredentialParameters { - fn arbitrary(u: &mut Unstructured<'a>) -> Result { - let alg = *u.choose(&webauthn::KNOWN_ALGS)?; - Ok(Self { alg }) - } -} - // cannot be derived because of missing impl for serde_bytes::Bytes impl<'a> Arbitrary<'a> for webauthn::PublicKeyCredentialDescriptorRef<'a> { fn arbitrary(u: &mut Unstructured<'a>) -> Result { diff --git a/src/webauthn.rs b/src/webauthn.rs index 7a9b80e..ec9eb37 100644 --- a/src/webauthn.rs +++ b/src/webauthn.rs @@ -135,15 +135,29 @@ impl PublicKeyCredentialUserEntity { } } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct KnownPublicKeyCredentialParameters { - pub alg: i32, +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[non_exhaustive] +pub enum KnownPublicKeyCredentialParameters { + ES256, + EdDSA, +} + +impl KnownPublicKeyCredentialParameters { + pub const ALL: [Self; COUNT_KNOWN_ALGS] = [Self::ES256, Self::EdDSA]; + + pub fn alg(&self) -> i32 { + match self { + Self::ES256 => ES256, + Self::EdDSA => ED_DSA, + } + } } impl From for PublicKeyCredentialParameters { fn from(value: KnownPublicKeyCredentialParameters) -> Self { Self { - alg: value.alg, + alg: value.alg(), key_type: String::try_from("public-key").unwrap(), } } @@ -155,12 +169,11 @@ pub enum UnknownPKCredentialParam { } /// ECDSA w/ SHA-256 -pub const ES256: i32 = -7; +const ES256: i32 = -7; /// EdDSA -pub const ED_DSA: i32 = -8; +const ED_DSA: i32 = -8; pub const COUNT_KNOWN_ALGS: usize = 2; -pub const KNOWN_ALGS: [i32; COUNT_KNOWN_ALGS] = [ES256, ED_DSA]; impl TryFrom for KnownPublicKeyCredentialParameters { type Error = UnknownPKCredentialParam; @@ -168,10 +181,12 @@ impl TryFrom for KnownPublicKeyCredentialParamete fn try_from(value: PublicKeyCredentialParameters) -> Result { if value.key_type != "public-key" { Err(UnknownPKCredentialParam::UnknownType) - } else if KNOWN_ALGS.contains(&value.alg) { - Ok(Self { alg: value.alg }) } else { - Err(UnknownPKCredentialParam::UnknownAlg) + match value.alg { + ES256 => Ok(Self::ES256), + ED_DSA => Ok(Self::EdDSA), + _ => Err(UnknownPKCredentialParam::UnknownAlg), + } } } } @@ -190,7 +205,7 @@ impl Serialize for FilteredPublicKeyCredentialParameters { use serde::ser::SerializeSeq; let mut seq = serializer.serialize_seq(Some(self.0.len()))?; for element in &self.0 { - let el: PublicKeyCredentialParameters = element.clone().into(); + let el = PublicKeyCredentialParameters::from(*element); seq.serialize_element(&el)? } seq.end() From 8956b7038c871c3cd3d92ddd877666f4d9b16294 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 11:12:21 +0200 Subject: [PATCH 09/12] Flatten authenticator module The authenticator module contains the main types of the crate so it makes sense to provide them from the top level instead of the authenticator module. --- CHANGELOG.md | 1 + src/authenticator.rs | 3 --- src/lib.rs | 3 ++- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d8692..af43dea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove re-exports for `heapless` and `heapless-bytes` as the relevant types are already re-exported. - `ctap2::get_info`: Use subcommand enum for `authenticator_config_commands`. - `webauthn`: Make `KnownPublicKeyCredentialParameters` an enum. +- `authenticator`: Move `Authenticator`, `Request` and `Response` to the crate root. ## [0.6.0-rc.4] 2026-06-01 diff --git a/src/authenticator.rs b/src/authenticator.rs index 94a6661..067b1a0 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -3,9 +3,6 @@ use crate::ctap1; use crate::ctap2; -pub use ctap1::Authenticator as Ctap1Authenticator; -pub use ctap2::Authenticator as Ctap2Authenticator; - #[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] // clippy says (2022-02-26): large size difference diff --git a/src/lib.rs b/src/lib.rs index 610c55c..11ed1db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,7 +25,7 @@ pub use serde_bytes::ByteArray; #[cfg(feature = "arbitrary")] mod arbitrary; -pub mod authenticator; +mod authenticator; pub mod ctap1; pub mod ctap2; pub(crate) mod operation; @@ -34,6 +34,7 @@ pub mod sizes; mod test; pub mod webauthn; +pub use authenticator::{Authenticator, Request, Response}; pub use ctap2::{Error, Result}; use core::fmt::{self, Display, Formatter}; From efcd23ed63f33d5581d314e1b51717f60875513f Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 11:45:09 +0200 Subject: [PATCH 10/12] Add doc_cfg feature for docs.rs --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 11ed1db..bedc01e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(all(not(test), not(feature = "std")), no_std)] +#![cfg_attr(docsrs, feature(doc_cfg))] // #![no_std] //! `ctap-types` maps the various types involved in the FIDO CTAP protocol From 5e61f0ad50c6309d10502ffa1f5783b35dc72b08 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 12:10:21 +0200 Subject: [PATCH 11/12] Add tests for enum counts and conversions --- Cargo.toml | 1 + src/ctap2/get_info.rs | 38 ++++++++++++++++++++++++++++++++++++++ src/webauthn.rs | 11 +++++++++++ 3 files changed, 50 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index ddae0a6..c6a496e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ ciborium = "0.2" hex = "0.4" hex-literal = "0.4.1" serde_test = "1.0.176" +strum = { version = "0.28", features = ["derive"] } [features] std = [] diff --git a/src/ctap2/get_info.rs b/src/ctap2/get_info.rs index 90e1229..857048a 100644 --- a/src/ctap2/get_info.rs +++ b/src/ctap2/get_info.rs @@ -258,6 +258,7 @@ impl ResponseBuilder { } #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(strum::EnumCount, strum::VariantArray))] #[non_exhaustive] #[serde(into = "&str", try_from = "&str")] pub enum Version { @@ -308,6 +309,7 @@ impl TryFrom<&str> for Version { pub const EXTENSION_COUNT: usize = 7; #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(strum::EnumCount, strum::VariantArray))] #[non_exhaustive] #[serde(into = "&str", try_from = "&str")] pub enum Extension { @@ -364,6 +366,7 @@ impl TryFrom<&str> for Extension { pub const TRANSPORT_COUNT: usize = 3; #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[cfg_attr(test, derive(strum::EnumCount, strum::VariantArray))] #[non_exhaustive] #[serde(into = "&str", try_from = "&str")] pub enum Transport { @@ -526,7 +529,42 @@ pub struct Certifications { #[cfg(test)] mod tests { use super::*; + use core::fmt::Debug; use serde_test::{assert_ser_tokens, assert_tokens, Token}; + use strum::{EnumCount, VariantArray}; + + fn test_enum(count: usize) + where + T: EnumCount + + VariantArray + + Into<&'static str> + + for<'a> TryFrom<&'a str> + + PartialEq + + Debug + + Copy, + { + assert_eq!(count, T::COUNT); + + for variant in T::VARIANTS { + let variant_str: &str = (*variant).into(); + assert_eq!(Some(*variant), T::try_from(variant_str).ok()); + } + } + + #[test] + fn test_version() { + test_enum::(VERSION_COUNT); + } + + #[test] + fn test_transport() { + test_enum::(TRANSPORT_COUNT); + } + + #[test] + fn test_extension() { + test_enum::(EXTENSION_COUNT); + } #[test] fn test_serde_version() { diff --git a/src/webauthn.rs b/src/webauthn.rs index ec9eb37..59a908c 100644 --- a/src/webauthn.rs +++ b/src/webauthn.rs @@ -137,6 +137,7 @@ impl PublicKeyCredentialUserEntity { #[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[cfg_attr(test, derive(strum::EnumCount, strum::VariantArray))] #[non_exhaustive] pub enum KnownPublicKeyCredentialParameters { ES256, @@ -289,6 +290,16 @@ pub struct PublicKeyCredentialDescriptorRef<'a> { #[cfg(test)] mod tests { use super::*; + use strum::{EnumCount as _, VariantArray as _}; + + #[test] + fn test_known_cred_params() { + assert_eq!(KnownPublicKeyCredentialParameters::COUNT, COUNT_KNOWN_ALGS); + assert_eq!( + KnownPublicKeyCredentialParameters::ALL, + KnownPublicKeyCredentialParameters::VARIANTS + ); + } #[test] fn test_truncate() { From 8c832b5ff377b553abbb4c538646a8c884349938 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Mon, 8 Jun 2026 11:45:22 +0200 Subject: [PATCH 12/12] Release v0.6.0-rc.5 --- CHANGELOG.md | 8 +++++++- Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af43dea..4ab2f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -[Unreleased]: https://github.com/trussed-dev/ctap-types/compare/0.6.0-rc.4...HEAD +[Unreleased]: https://github.com/trussed-dev/ctap-types/compare/0.6.0-rc.5...HEAD + +- + +## [0.6.0-rc.5] 2026-06-08 + +[0.6.0-rc.5]: https://github.com/trussed-dev/ctap-types/compare/0.6.0-rc.4...0.6.0-rc.5 - Remove unused `Rpc` trait. - Remove `cbor-smol` re-export as `serde`. diff --git a/Cargo.toml b/Cargo.toml index c6a496e..862e2e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ctap-types" -version = "0.6.0-rc.4" +version = "0.6.0-rc.5" authors = ["Nicolas Stalder ", "The Trussed developers"] edition = "2021" license = "Apache-2.0 OR MIT"