Skip to content

Commit 835658e

Browse files
committed
ctap2.3: add wire types for FIDO_2_3, smart-card transport, enableLongTouchForReset
1 parent 65c5b72 commit 835658e

7 files changed

Lines changed: 55 additions & 10 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ arbitrary = ["dep:arbitrary", "std"]
3434
get-info-full = []
3535
# enables support for implementing the large-blobs extension, see src/sizes.rs
3636
large-blobs = []
37+
# Bumps `MAX_PACKED_SIG_LENGTH` and the per-cert size in `x5c` so packed
38+
# attestation can carry an ML-DSA-44 (CTAP 2.3 alg -50) signature (2420 B)
39+
# alongside the now-larger AUTHENTICATOR_DATA_LENGTH for ML-DSA pubkeys.
40+
mldsa44 = []
3741
third-party-payment = []
3842

3943
log-all = []

src/ctap2.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -308,9 +308,9 @@ pub struct NoneAttestationStatement {}
308308
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
309309
pub struct PackedAttestationStatement {
310310
pub alg: i32,
311-
pub sig: Bytes<ASN1_SIGNATURE_LENGTH>,
311+
pub sig: Bytes<MAX_PACKED_SIG_LENGTH>,
312312
#[serde(skip_serializing_if = "Option::is_none")]
313-
pub x5c: Option<Vec<Bytes<1024>, 1>>,
313+
pub x5c: Option<Vec<Bytes<MAX_X5C_CERT_LENGTH>, 1>>,
314314
}
315315

316316
#[derive(Clone, Debug, Default, Eq, PartialEq)]

src/ctap2/authenticator_config.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub enum Subcommand {
1717
EnableEnterpriseAttestation = 0x01,
1818
ToggleAlwaysUv = 0x02,
1919
SetMinPINLength = 0x03,
20+
EnableLongTouchForReset = 0x04,
2021
VendorPrototype = 0xff,
2122
}
2223

@@ -63,6 +64,7 @@ mod tests {
6364
(Subcommand::EnableEnterpriseAttestation, 0x01),
6465
(Subcommand::ToggleAlwaysUv, 0x02),
6566
(Subcommand::SetMinPINLength, 0x03),
67+
(Subcommand::EnableLongTouchForReset, 0x04),
6668
(Subcommand::VendorPrototype, 0xff),
6769
] {
6870
assert_tokens(&sub, &[Token::U8(byte)]);

src/ctap2/get_assertion.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ pub struct Request<'a> {
133133
pub struct Response {
134134
pub credential: PublicKeyCredentialDescriptor,
135135
pub auth_data: Bytes<AUTHENTICATOR_DATA_LENGTH>,
136-
pub signature: Bytes<ASN1_SIGNATURE_LENGTH>,
136+
pub signature: Bytes<MAX_PACKED_SIG_LENGTH>,
137137
#[serde(skip_serializing_if = "Option::is_none")]
138138
pub user: Option<PublicKeyCredentialUserEntity>,
139139
#[serde(skip_serializing_if = "Option::is_none")]
@@ -156,7 +156,7 @@ pub struct Response {
156156
pub struct ResponseBuilder {
157157
pub credential: PublicKeyCredentialDescriptor,
158158
pub auth_data: Bytes<AUTHENTICATOR_DATA_LENGTH>,
159-
pub signature: Bytes<ASN1_SIGNATURE_LENGTH>,
159+
pub signature: Bytes<MAX_PACKED_SIG_LENGTH>,
160160
}
161161

162162
impl ResponseBuilder {

src/ctap2/get_info.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub type AuthenticatorInfo = Response;
1010
#[serde_indexed(offset = 1)]
1111
pub struct Response {
1212
// 0x01
13-
pub versions: Vec<Version, 4>,
13+
pub versions: Vec<Version, 5>,
1414

1515
// 0x02
1616
#[serde(skip_serializing_if = "Option::is_none")]
@@ -154,7 +154,7 @@ impl Default for Response {
154154

155155
#[derive(Debug)]
156156
pub struct ResponseBuilder {
157-
pub versions: Vec<Version, 4>,
157+
pub versions: Vec<Version, 5>,
158158
pub aaguid: Bytes<16>,
159159
}
160160

@@ -211,6 +211,7 @@ pub enum Version {
211211
Fido2_1,
212212
Fido2_1Pre,
213213
Fido2_2,
214+
Fido2_3,
214215
U2fV2,
215216
}
216217

@@ -219,6 +220,7 @@ impl Version {
219220
const FIDO_2_1: &'static str = "FIDO_2_1";
220221
const FIDO_2_1_PRE: &'static str = "FIDO_2_1_PRE";
221222
const FIDO_2_2: &'static str = "FIDO_2_2";
223+
const FIDO_2_3: &'static str = "FIDO_2_3";
222224
const U2F_V2: &'static str = "U2F_V2";
223225
}
224226

@@ -229,6 +231,7 @@ impl From<Version> for &str {
229231
Version::Fido2_1 => Version::FIDO_2_1,
230232
Version::Fido2_1Pre => Version::FIDO_2_1_PRE,
231233
Version::Fido2_2 => Version::FIDO_2_2,
234+
Version::Fido2_3 => Version::FIDO_2_3,
232235
Version::U2fV2 => Version::U2F_V2,
233236
}
234237
}
@@ -243,6 +246,7 @@ impl TryFrom<&str> for Version {
243246
Self::FIDO_2_1 => Ok(Self::Fido2_1),
244247
Self::FIDO_2_1_PRE => Ok(Self::Fido2_1Pre),
245248
Self::FIDO_2_2 => Ok(Self::Fido2_2),
249+
Self::FIDO_2_3 => Ok(Self::Fido2_3),
246250
Self::U2F_V2 => Ok(Self::U2fV2),
247251
_ => Err(TryFromStrError),
248252
}
@@ -308,18 +312,21 @@ impl TryFrom<&str> for Extension {
308312
#[serde(into = "&str", try_from = "&str")]
309313
pub enum Transport {
310314
Nfc,
315+
SmartCard,
311316
Usb,
312317
}
313318

314319
impl Transport {
315320
const NFC: &'static str = "nfc";
321+
const SMART_CARD: &'static str = "smart-card";
316322
const USB: &'static str = "usb";
317323
}
318324

319325
impl From<Transport> for &str {
320326
fn from(transport: Transport) -> Self {
321327
match transport {
322328
Transport::Nfc => Transport::NFC,
329+
Transport::SmartCard => Transport::SMART_CARD,
323330
Transport::Usb => Transport::USB,
324331
}
325332
}
@@ -331,6 +338,7 @@ impl TryFrom<&str> for Transport {
331338
fn try_from(s: &str) -> Result<Self, Self::Error> {
332339
match s {
333340
Self::NFC => Ok(Self::Nfc),
341+
Self::SMART_CARD => Ok(Self::SmartCard),
334342
Self::USB => Ok(Self::Usb),
335343
_ => Err(TryFromStrError),
336344
}
@@ -479,6 +487,7 @@ mod tests {
479487
(Version::Fido2_1, "FIDO_2_1"),
480488
(Version::Fido2_1Pre, "FIDO_2_1_PRE"),
481489
(Version::Fido2_2, "FIDO_2_2"),
490+
(Version::Fido2_3, "FIDO_2_3"),
482491
(Version::U2fV2, "U2F_V2"),
483492
];
484493
for (version, s) in versions {
@@ -504,7 +513,11 @@ mod tests {
504513

505514
#[test]
506515
fn test_serde_transport() {
507-
let transports = [(Transport::Nfc, "nfc"), (Transport::Usb, "usb")];
516+
let transports = [
517+
(Transport::Nfc, "nfc"),
518+
(Transport::SmartCard, "smart-card"),
519+
(Transport::Usb, "usb"),
520+
];
508521
for (transport, s) in transports {
509522
assert_tokens(&transport, &[Token::BorrowedStr(s)]);
510523
}

src/sizes.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
1+
// Sized to hold ML-DSA-44 authData (header + 1312-byte raw public key
2+
// wrapped in a ~10-byte COSE_Key map + AAGUID + credId + extensions).
3+
// Pre-mldsa44 builds used 676 bytes which overflowed silently on alg=-50,
4+
// surfacing as `extend_from_slice` Err → CTAP `Error::Other` (0x7F).
5+
// Gated so non-mldsa44 builds keep the historical footprint.
6+
#[cfg(feature = "mldsa44")]
7+
pub const AUTHENTICATOR_DATA_LENGTH: usize = 2048;
8+
#[cfg(not(feature = "mldsa44"))]
19
pub const AUTHENTICATOR_DATA_LENGTH: usize = 676;
2-
// pub const AUTHENTICATOR_DATA_LENGTH_BYTES: usize = 512;
10+
11+
/// Max length of a packed-attestation signature. ECDSA over P-256 fits in
12+
/// `ASN1_SIGNATURE_LENGTH` (77 B). With `mldsa44`, the authenticator may
13+
/// sign with ML-DSA-44 whose signature is 2420 bytes, so we bump.
14+
#[cfg(feature = "mldsa44")]
15+
pub const MAX_PACKED_SIG_LENGTH: usize = 2432;
16+
#[cfg(not(feature = "mldsa44"))]
17+
pub const MAX_PACKED_SIG_LENGTH: usize = ASN1_SIGNATURE_LENGTH;
18+
19+
/// Max length of one x5c entry (the attestation certificate carried in
20+
/// `PackedAttestationStatement.x5c`). Matches what trussed's
21+
/// `read_certificate` Reply.der fits in (`Message`); 1024 historically,
22+
/// 2048 with `mldsa44` so larger Message buffers don't truncate.
23+
#[cfg(feature = "mldsa44")]
24+
pub const MAX_X5C_CERT_LENGTH: usize = 2048;
25+
#[cfg(not(feature = "mldsa44"))]
26+
pub const MAX_X5C_CERT_LENGTH: usize = 1024;
327

428
pub const ASN1_SIGNATURE_LENGTH: usize = 77;
529
// pub const ASN1_SIGNATURE_LENGTH_BYTES: usize = 72;

src/webauthn.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,11 @@ pub enum UnknownPKCredentialParam {
158158
pub const ES256: i32 = -7;
159159
/// EdDSA
160160
pub const ED_DSA: i32 = -8;
161+
/// ML-DSA-44 (FIPS 204, NIST level 2)
162+
pub const ML_DSA_44: i32 = -50;
161163

162-
pub const COUNT_KNOWN_ALGS: usize = 2;
163-
pub const KNOWN_ALGS: [i32; COUNT_KNOWN_ALGS] = [ES256, ED_DSA];
164+
pub const COUNT_KNOWN_ALGS: usize = 3;
165+
pub const KNOWN_ALGS: [i32; COUNT_KNOWN_ALGS] = [ES256, ED_DSA, ML_DSA_44];
164166

165167
impl TryFrom<PublicKeyCredentialParameters> for KnownPublicKeyCredentialParameters {
166168
type Error = UnknownPKCredentialParam;

0 commit comments

Comments
 (0)