Skip to content

Commit 86f7b3d

Browse files
Use enums instead of string constants
Previously, we used String<_> to represent string constants in some responses. This has multiple drawbacks: - It is error-prone because the value is not validated. - Construction is fallible because of the fixed length of the string. - The length needs to be bumped if longer values are added. This patch introduces enums to replace these constants. As cbor_smol serializes enums using the variant index instead of the string, we need to manually implement the string conversion.
1 parent be9589d commit 86f7b3d

File tree

4 files changed

+167
-10
lines changed

4 files changed

+167
-10
lines changed

CHANGELOG.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88

99
[Unreleased]: https://github.com/trussed-dev/ctap-types/compare/0.2.0...HEAD
1010

11-
-
11+
### Breaking Changes
12+
13+
- Use enums instead of string constants
14+
- Introduce `Version`, `Extension` and `Transport` enums and use them in `ctap2::get_info`
15+
- Fix serialization of the `AttestationStatementFormat` enum and use it in `ctap2::make_credential`
1216

1317
## [0.2.0] - 2024-06-21
1418

src/ctap2/get_info.rs

+119-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::webauthn::FilteredPublicKeyCredentialParameters;
2-
use crate::{Bytes, String, Vec};
2+
use crate::{Bytes, TryFromStrError, Vec};
33
use serde::{Deserialize, Serialize};
44
use serde_indexed::{DeserializeIndexed, SerializeIndexed};
55

@@ -10,11 +10,11 @@ pub type AuthenticatorInfo = Response;
1010
#[serde_indexed(offset = 1)]
1111
pub struct Response {
1212
// 0x01
13-
pub versions: Vec<String<12>, 4>,
13+
pub versions: Vec<Version, 4>,
1414

1515
// 0x02
1616
#[serde(skip_serializing_if = "Option::is_none")]
17-
pub extensions: Option<Vec<String<13>, 4>>,
17+
pub extensions: Option<Vec<Extension, 4>>,
1818

1919
// 0x03
2020
pub aaguid: Bytes<16>,
@@ -44,7 +44,7 @@ pub struct Response {
4444
// 0x09
4545
// FIDO_2_1
4646
#[serde(skip_serializing_if = "Option::is_none")]
47-
pub transports: Option<Vec<String<8>, 4>>,
47+
pub transports: Option<Vec<Transport, 4>>,
4848

4949
// 0x0A
5050
// FIDO_2_1
@@ -135,7 +135,7 @@ impl Default for Response {
135135

136136
#[derive(Debug)]
137137
pub struct ResponseBuilder {
138-
pub versions: Vec<String<12>, 4>,
138+
pub versions: Vec<Version, 4>,
139139
pub aaguid: Bytes<16>,
140140
}
141141

@@ -178,6 +178,120 @@ impl ResponseBuilder {
178178
}
179179
}
180180

181+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
182+
#[non_exhaustive]
183+
#[serde(into = "&str", try_from = "&str")]
184+
pub enum Version {
185+
Fido2_0,
186+
Fido2_1,
187+
Fido2_1Pre,
188+
U2fV2,
189+
}
190+
191+
impl Version {
192+
const FIDO_2_0: &'static str = "FIDO_2_0";
193+
const FIDO_2_1: &'static str = "FIDO_2_1";
194+
const FIDO_2_1_PRE: &'static str = "FIDO_2_1_PRE";
195+
const U2F_V2: &'static str = "U2F_V2";
196+
}
197+
198+
impl From<Version> for &str {
199+
fn from(version: Version) -> Self {
200+
match version {
201+
Version::Fido2_0 => Version::FIDO_2_0,
202+
Version::Fido2_1 => Version::FIDO_2_1,
203+
Version::Fido2_1Pre => Version::FIDO_2_1_PRE,
204+
Version::U2fV2 => Version::U2F_V2,
205+
}
206+
}
207+
}
208+
209+
impl TryFrom<&str> for Version {
210+
type Error = TryFromStrError;
211+
212+
fn try_from(s: &str) -> Result<Self, Self::Error> {
213+
match s {
214+
Self::FIDO_2_0 => Ok(Self::Fido2_0),
215+
Self::FIDO_2_1 => Ok(Self::Fido2_1),
216+
Self::FIDO_2_1_PRE => Ok(Self::Fido2_1Pre),
217+
Self::U2F_V2 => Ok(Self::U2fV2),
218+
_ => Err(TryFromStrError),
219+
}
220+
}
221+
}
222+
223+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
224+
#[non_exhaustive]
225+
#[serde(into = "&str", try_from = "&str")]
226+
pub enum Extension {
227+
CredProtect,
228+
HmacSecret,
229+
LargeBlobKey,
230+
}
231+
232+
impl Extension {
233+
const CRED_PROTECT: &'static str = "credProtect";
234+
const HMAC_SECRET: &'static str = "hmac-secret";
235+
const LARGE_BLOB_KEY: &'static str = "largeBlobKey";
236+
}
237+
238+
impl From<Extension> for &str {
239+
fn from(extension: Extension) -> Self {
240+
match extension {
241+
Extension::CredProtect => Extension::CRED_PROTECT,
242+
Extension::HmacSecret => Extension::HMAC_SECRET,
243+
Extension::LargeBlobKey => Extension::LARGE_BLOB_KEY,
244+
}
245+
}
246+
}
247+
248+
impl TryFrom<&str> for Extension {
249+
type Error = TryFromStrError;
250+
251+
fn try_from(s: &str) -> Result<Self, Self::Error> {
252+
match s {
253+
Self::CRED_PROTECT => Ok(Self::CredProtect),
254+
Self::HMAC_SECRET => Ok(Self::HmacSecret),
255+
Self::LARGE_BLOB_KEY => Ok(Self::LargeBlobKey),
256+
_ => Err(TryFromStrError),
257+
}
258+
}
259+
}
260+
261+
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
262+
#[non_exhaustive]
263+
#[serde(into = "&str", try_from = "&str")]
264+
pub enum Transport {
265+
Nfc,
266+
Usb,
267+
}
268+
269+
impl Transport {
270+
const NFC: &'static str = "nfc";
271+
const USB: &'static str = "usb";
272+
}
273+
274+
impl From<Transport> for &str {
275+
fn from(transport: Transport) -> Self {
276+
match transport {
277+
Transport::Nfc => Transport::NFC,
278+
Transport::Usb => Transport::USB,
279+
}
280+
}
281+
}
282+
283+
impl TryFrom<&str> for Transport {
284+
type Error = TryFromStrError;
285+
286+
fn try_from(s: &str) -> Result<Self, Self::Error> {
287+
match s {
288+
Self::NFC => Ok(Self::Nfc),
289+
Self::USB => Ok(Self::Usb),
290+
_ => Err(TryFromStrError),
291+
}
292+
}
293+
}
294+
181295
#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
182296
#[non_exhaustive]
183297
#[serde(rename_all = "camelCase")]

src/ctap2/make_credential.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{Bytes, String, Vec};
1+
use crate::{Bytes, TryFromStrError, Vec};
22

33
use serde::{Deserialize, Serialize};
44
use serde_bytes::ByteArray;
@@ -103,7 +103,7 @@ impl<'a> super::SerializeAttestedCredentialData for AttestedCredentialData<'a> {
103103
#[non_exhaustive]
104104
#[serde_indexed(offset = 1)]
105105
pub struct Response {
106-
pub fmt: String<32>,
106+
pub fmt: AttestationStatementFormat,
107107
pub auth_data: super::SerializedAuthenticatorData,
108108
#[serde(skip_serializing_if = "Option::is_none")]
109109
pub att_stmt: Option<AttestationStatement>,
@@ -115,7 +115,7 @@ pub struct Response {
115115

116116
#[derive(Debug)]
117117
pub struct ResponseBuilder {
118-
pub fmt: String<32>,
118+
pub fmt: AttestationStatementFormat,
119119
pub auth_data: super::SerializedAuthenticatorData,
120120
}
121121

@@ -143,12 +143,38 @@ pub enum AttestationStatement {
143143

144144
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
145145
#[non_exhaustive]
146-
#[serde(untagged)]
146+
#[serde(into = "&str", try_from = "&str")]
147147
pub enum AttestationStatementFormat {
148148
None,
149149
Packed,
150150
}
151151

152+
impl AttestationStatementFormat {
153+
const NONE: &'static str = "none";
154+
const PACKED: &'static str = "packed";
155+
}
156+
157+
impl From<AttestationStatementFormat> for &str {
158+
fn from(format: AttestationStatementFormat) -> Self {
159+
match format {
160+
AttestationStatementFormat::None => AttestationStatementFormat::NONE,
161+
AttestationStatementFormat::Packed => AttestationStatementFormat::PACKED,
162+
}
163+
}
164+
}
165+
166+
impl TryFrom<&str> for AttestationStatementFormat {
167+
type Error = TryFromStrError;
168+
169+
fn try_from(s: &str) -> Result<Self, Self::Error> {
170+
match s {
171+
Self::NONE => Ok(Self::None),
172+
Self::PACKED => Ok(Self::Packed),
173+
_ => Err(TryFromStrError),
174+
}
175+
}
176+
}
177+
152178
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
153179
pub struct NoneAttestationStatement {}
154180

src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ pub mod webauthn;
3535

3636
pub use ctap2::{Error, Result};
3737

38+
use core::fmt::{self, Display, Formatter};
39+
40+
/// An error returned by the `TryFrom<&str>` implementation for enums if an invalid value is
41+
/// provided.
42+
#[derive(Debug)]
43+
pub struct TryFromStrError;
44+
45+
impl Display for TryFromStrError {
46+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
47+
"invalid enum value".fmt(f)
48+
}
49+
}
50+
3851
#[cfg(test)]
3952
mod tests {}
4053

0 commit comments

Comments
 (0)