1use crate::webauthn::FilteredPublicKeyCredentialParameters;
2use crate::{Bytes, TryFromStrError, Vec};
3use serde::{Deserialize, Serialize};
4use serde_indexed::{DeserializeIndexed, SerializeIndexed};
5
6pub type AuthenticatorInfo = Response;
7
8#[derive(Clone, Debug, Eq, PartialEq, SerializeIndexed, DeserializeIndexed)]
9#[non_exhaustive]
10#[serde_indexed(offset = 1)]
11pub struct Response {
12 pub versions: Vec<Version, 4>,
14
15 #[serde(skip_serializing_if = "Option::is_none")]
17 pub extensions: Option<Vec<Extension, 4>>,
18
19 pub aaguid: Bytes<16>,
21
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub options: Option<CtapOptions>,
25
26 #[serde(skip_serializing_if = "Option::is_none")]
28 pub max_msg_size: Option<usize>,
29
30 #[serde(skip_serializing_if = "Option::is_none")]
32 pub pin_protocols: Option<Vec<u8, 2>>,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
37 pub max_creds_in_list: Option<usize>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
42 pub max_cred_id_length: Option<usize>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
47 pub transports: Option<Vec<Transport, 4>>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
52 pub algorithms: Option<FilteredPublicKeyCredentialParameters>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
57 pub max_serialized_large_blob_array: Option<usize>,
58
59 #[cfg(feature = "get-info-full")]
62 #[serde(skip_serializing_if = "Option::is_none")]
63 pub force_pin_change: Option<bool>,
64
65 #[cfg(feature = "get-info-full")]
68 #[serde(skip_serializing_if = "Option::is_none")]
69 pub min_pin_length: Option<usize>,
70
71 #[cfg(feature = "get-info-full")]
74 #[serde(skip_serializing_if = "Option::is_none")]
75 pub firmware_version: Option<usize>,
76
77 #[cfg(feature = "get-info-full")]
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub max_cred_blob_length: Option<usize>,
82
83 #[cfg(feature = "get-info-full")]
86 #[serde(skip_serializing_if = "Option::is_none")]
87 pub max_rpids_for_set_min_pin_length: Option<usize>,
88
89 #[cfg(feature = "get-info-full")]
92 #[serde(skip_serializing_if = "Option::is_none")]
93 pub preferred_platform_uv_attempts: Option<usize>,
94
95 #[cfg(feature = "get-info-full")]
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub uv_modality: Option<usize>,
100
101 #[cfg(feature = "get-info-full")]
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub certifications: Option<Certifications>,
106
107 #[cfg(feature = "get-info-full")]
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub remaining_discoverable_credentials: Option<usize>,
112
113 #[cfg(feature = "get-info-full")]
116 #[serde(skip_serializing_if = "Option::is_none")]
117 pub vendor_prototype_config_commands: Option<usize>,
118
119 #[cfg(feature = "get-info-full")]
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub attestation_formats: Option<Vec<super::AttestationStatementFormat, 2>>,
124
125 #[cfg(feature = "get-info-full")]
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub uv_count_since_last_pin_entry: Option<usize>,
130
131 #[cfg(feature = "get-info-full")]
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub long_touch_for_reset: Option<bool>,
136}
137
138impl Default for Response {
139 fn default() -> Self {
140 let mut zero_aaguid = Vec::<u8, 16>::new();
141 zero_aaguid.resize_default(16).unwrap();
142 let aaguid = Bytes::<16>::from(zero_aaguid);
143
144 let mut response = ResponseBuilder {
145 aaguid,
146 versions: Vec::new(),
147 }
148 .build();
149 response.options = Some(CtapOptions::default());
150 response
151 }
152}
153
154#[derive(Debug)]
155pub struct ResponseBuilder {
156 pub versions: Vec<Version, 4>,
157 pub aaguid: Bytes<16>,
158}
159
160impl ResponseBuilder {
161 #[inline(always)]
162 pub fn build(self) -> Response {
163 Response {
164 versions: self.versions,
165 aaguid: self.aaguid,
166 extensions: None,
167 options: None,
168 max_msg_size: None,
169 pin_protocols: None,
170 max_creds_in_list: None,
171 max_cred_id_length: None,
172 transports: None,
173 algorithms: None,
174 max_serialized_large_blob_array: None,
175 #[cfg(feature = "get-info-full")]
176 force_pin_change: None,
177 #[cfg(feature = "get-info-full")]
178 min_pin_length: None,
179 #[cfg(feature = "get-info-full")]
180 firmware_version: None,
181 #[cfg(feature = "get-info-full")]
182 max_cred_blob_length: None,
183 #[cfg(feature = "get-info-full")]
184 max_rpids_for_set_min_pin_length: None,
185 #[cfg(feature = "get-info-full")]
186 preferred_platform_uv_attempts: None,
187 #[cfg(feature = "get-info-full")]
188 uv_modality: None,
189 #[cfg(feature = "get-info-full")]
190 certifications: None,
191 #[cfg(feature = "get-info-full")]
192 remaining_discoverable_credentials: None,
193 #[cfg(feature = "get-info-full")]
194 vendor_prototype_config_commands: None,
195 #[cfg(feature = "get-info-full")]
196 attestation_formats: None,
197 #[cfg(feature = "get-info-full")]
198 uv_count_since_last_pin_entry: None,
199 #[cfg(feature = "get-info-full")]
200 long_touch_for_reset: None,
201 }
202 }
203}
204
205#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
206#[non_exhaustive]
207#[serde(into = "&str", try_from = "&str")]
208pub enum Version {
209 Fido2_0,
210 Fido2_1,
211 Fido2_1Pre,
212 U2fV2,
213}
214
215impl Version {
216 const FIDO_2_0: &'static str = "FIDO_2_0";
217 const FIDO_2_1: &'static str = "FIDO_2_1";
218 const FIDO_2_1_PRE: &'static str = "FIDO_2_1_PRE";
219 const U2F_V2: &'static str = "U2F_V2";
220}
221
222impl From<Version> for &str {
223 fn from(version: Version) -> Self {
224 match version {
225 Version::Fido2_0 => Version::FIDO_2_0,
226 Version::Fido2_1 => Version::FIDO_2_1,
227 Version::Fido2_1Pre => Version::FIDO_2_1_PRE,
228 Version::U2fV2 => Version::U2F_V2,
229 }
230 }
231}
232
233impl TryFrom<&str> for Version {
234 type Error = TryFromStrError;
235
236 fn try_from(s: &str) -> Result<Self, Self::Error> {
237 match s {
238 Self::FIDO_2_0 => Ok(Self::Fido2_0),
239 Self::FIDO_2_1 => Ok(Self::Fido2_1),
240 Self::FIDO_2_1_PRE => Ok(Self::Fido2_1Pre),
241 Self::U2F_V2 => Ok(Self::U2fV2),
242 _ => Err(TryFromStrError),
243 }
244 }
245}
246
247#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
248#[non_exhaustive]
249#[serde(into = "&str", try_from = "&str")]
250pub enum Extension {
251 CredProtect,
252 HmacSecret,
253 LargeBlobKey,
254 ThirdPartyPayment,
255}
256
257impl Extension {
258 const CRED_PROTECT: &'static str = "credProtect";
259 const HMAC_SECRET: &'static str = "hmac-secret";
260 const LARGE_BLOB_KEY: &'static str = "largeBlobKey";
261 const THIRD_PARTY_PAYMENT: &'static str = "thirdPartyPayment";
262}
263
264impl From<Extension> for &str {
265 fn from(extension: Extension) -> Self {
266 match extension {
267 Extension::CredProtect => Extension::CRED_PROTECT,
268 Extension::HmacSecret => Extension::HMAC_SECRET,
269 Extension::LargeBlobKey => Extension::LARGE_BLOB_KEY,
270 Extension::ThirdPartyPayment => Extension::THIRD_PARTY_PAYMENT,
271 }
272 }
273}
274
275impl TryFrom<&str> for Extension {
276 type Error = TryFromStrError;
277
278 fn try_from(s: &str) -> Result<Self, Self::Error> {
279 match s {
280 Self::CRED_PROTECT => Ok(Self::CredProtect),
281 Self::HMAC_SECRET => Ok(Self::HmacSecret),
282 Self::LARGE_BLOB_KEY => Ok(Self::LargeBlobKey),
283 Self::THIRD_PARTY_PAYMENT => Ok(Self::ThirdPartyPayment),
284 _ => Err(TryFromStrError),
285 }
286 }
287}
288
289#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
290#[non_exhaustive]
291#[serde(into = "&str", try_from = "&str")]
292pub enum Transport {
293 Nfc,
294 Usb,
295}
296
297impl Transport {
298 const NFC: &'static str = "nfc";
299 const USB: &'static str = "usb";
300}
301
302impl From<Transport> for &str {
303 fn from(transport: Transport) -> Self {
304 match transport {
305 Transport::Nfc => Transport::NFC,
306 Transport::Usb => Transport::USB,
307 }
308 }
309}
310
311impl TryFrom<&str> for Transport {
312 type Error = TryFromStrError;
313
314 fn try_from(s: &str) -> Result<Self, Self::Error> {
315 match s {
316 Self::NFC => Ok(Self::Nfc),
317 Self::USB => Ok(Self::Usb),
318 _ => Err(TryFromStrError),
319 }
320 }
321}
322
323#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
324#[non_exhaustive]
325#[serde(rename_all = "camelCase")]
326pub struct CtapOptions {
327 #[cfg(feature = "get-info-full")]
328 #[serde(skip_serializing_if = "Option::is_none")]
329 pub ep: Option<bool>, pub rk: bool,
331 pub up: bool,
332 #[serde(skip_serializing_if = "Option::is_none")]
333 pub uv: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")]
338 pub plat: Option<bool>, #[cfg(feature = "get-info-full")]
340 #[serde(skip_serializing_if = "Option::is_none")]
341 pub uv_acfg: Option<bool>, #[cfg(feature = "get-info-full")]
343 #[serde(skip_serializing_if = "Option::is_none")]
344 pub always_uv: Option<bool>,
345 #[serde(skip_serializing_if = "Option::is_none")]
346 pub cred_mgmt: Option<bool>,
347 #[cfg(feature = "get-info-full")]
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub authnr_cfg: Option<bool>,
350 #[cfg(feature = "get-info-full")]
351 #[serde(skip_serializing_if = "Option::is_none")]
352 pub bio_enroll: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")]
354 pub client_pin: Option<bool>,
355 #[serde(skip_serializing_if = "Option::is_none")]
356 pub large_blobs: Option<bool>,
357 #[cfg(feature = "get-info-full")]
358 #[serde(skip_serializing_if = "Option::is_none")]
359 pub uv_bio_enroll: Option<bool>,
360 #[cfg(feature = "get-info-full")]
361 #[serde(rename = "setMinPINLength", skip_serializing_if = "Option::is_none")]
362 pub set_min_pin_length: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")]
364 pub pin_uv_auth_token: Option<bool>,
365 #[cfg(feature = "get-info-full")]
366 #[serde(skip_serializing_if = "Option::is_none")]
367 pub make_cred_uv_not_rqd: Option<bool>,
368 #[cfg(feature = "get-info-full")]
369 #[serde(skip_serializing_if = "Option::is_none")]
370 pub credential_mgmt_preview: Option<bool>,
371 #[cfg(feature = "get-info-full")]
372 #[serde(skip_serializing_if = "Option::is_none")]
373 pub user_verification_mgmt_preview: Option<bool>,
374 #[cfg(feature = "get-info-full")]
375 #[serde(skip_serializing_if = "Option::is_none")]
376 pub no_mc_ga_permissions_with_client_pin: Option<bool>,
377}
378
379impl Default for CtapOptions {
380 fn default() -> Self {
381 Self {
382 #[cfg(feature = "get-info-full")]
383 ep: None,
384 rk: false,
385 up: true,
386 uv: None,
387 plat: None,
388 #[cfg(feature = "get-info-full")]
389 uv_acfg: None,
390 #[cfg(feature = "get-info-full")]
391 always_uv: None,
392 cred_mgmt: None,
393 #[cfg(feature = "get-info-full")]
394 authnr_cfg: None,
395 #[cfg(feature = "get-info-full")]
396 bio_enroll: None,
397 client_pin: None,
398 large_blobs: None,
399 #[cfg(feature = "get-info-full")]
400 uv_bio_enroll: None,
401 pin_uv_auth_token: None,
402 #[cfg(feature = "get-info-full")]
403 set_min_pin_length: None,
404 #[cfg(feature = "get-info-full")]
405 make_cred_uv_not_rqd: None,
406 #[cfg(feature = "get-info-full")]
407 credential_mgmt_preview: None,
408 #[cfg(feature = "get-info-full")]
409 user_verification_mgmt_preview: None,
410 #[cfg(feature = "get-info-full")]
411 no_mc_ga_permissions_with_client_pin: None,
412 }
413 }
414}
415
416#[cfg(feature = "get-info-full")]
417#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
418#[non_exhaustive]
419pub struct Certifications {
420 #[serde(rename = "FIPS-CMVP-2")]
421 #[serde(skip_serializing_if = "Option::is_none")]
422 pub fips_cmpv2: Option<u8>,
423
424 #[serde(rename = "FIPS-CMVP-3")]
425 #[serde(skip_serializing_if = "Option::is_none")]
426 pub fips_cmpv3: Option<u8>,
427
428 #[serde(rename = "FIPS-CMVP-2-PHY")]
429 #[serde(skip_serializing_if = "Option::is_none")]
430 pub fips_cmpv2_phy: Option<u8>,
431
432 #[serde(rename = "FIPS-CMVP-3-PHY")]
433 #[serde(skip_serializing_if = "Option::is_none")]
434 pub fips_cmpv3_phy: Option<u8>,
435
436 #[serde(rename = "CC-EAL")]
437 #[serde(skip_serializing_if = "Option::is_none")]
438 pub cc_eal: Option<u8>,
439
440 #[serde(rename = "FIDO")]
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub fido: Option<u8>,
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use serde_test::{assert_ser_tokens, assert_tokens, Token};
449
450 #[test]
451 fn test_serde_version() {
452 let versions = [
453 (Version::Fido2_0, "FIDO_2_0"),
454 (Version::Fido2_1, "FIDO_2_1"),
455 (Version::Fido2_1Pre, "FIDO_2_1_PRE"),
456 (Version::U2fV2, "U2F_V2"),
457 ];
458 for (version, s) in versions {
459 assert_tokens(&version, &[Token::BorrowedStr(s)]);
460 }
461 }
462
463 #[test]
464 fn test_serde_extension() {
465 let extensions = [
466 (Extension::CredProtect, "credProtect"),
467 (Extension::HmacSecret, "hmac-secret"),
468 (Extension::LargeBlobKey, "largeBlobKey"),
469 ];
470 for (extension, s) in extensions {
471 assert_tokens(&extension, &[Token::BorrowedStr(s)]);
472 }
473 }
474
475 #[test]
476 fn test_serde_transport() {
477 let transports = [(Transport::Nfc, "nfc"), (Transport::Usb, "usb")];
478 for (transport, s) in transports {
479 assert_tokens(&transport, &[Token::BorrowedStr(s)]);
480 }
481 }
482
483 #[test]
484 fn test_serde_get_info_minimal() {
485 let versions = Vec::from_slice(&[Version::Fido2_0, Version::Fido2_1]).unwrap();
486 let aaguid = Bytes::from_slice(&[0xff; 16]).unwrap();
487 let response = ResponseBuilder { versions, aaguid }.build();
488 assert_tokens(
489 &response,
490 &[
491 Token::Map { len: Some(2) },
492 Token::U64(1),
493 Token::Seq { len: Some(2) },
494 Token::BorrowedStr("FIDO_2_0"),
495 Token::BorrowedStr("FIDO_2_1"),
496 Token::SeqEnd,
497 Token::U64(3),
498 Token::BorrowedBytes(&[0xff; 16]),
499 Token::MapEnd,
500 ],
501 );
502 }
503
504 #[test]
505 fn test_serde_get_info_default() {
506 const AAGUID: &[u8] = &[
509 236, 153, 219, 25, 205, 31, 76, 6, 162, 169, 148, 15, 23, 166, 163, 11,
510 ];
511 let versions =
512 Vec::from_slice(&[Version::U2fV2, Version::Fido2_0, Version::Fido2_1]).unwrap();
513 let aaguid = Bytes::from_slice(AAGUID).unwrap();
514 let mut options = CtapOptions::default();
515 options.rk = true;
516 options.plat = Some(false);
517 options.client_pin = Some(false);
518 options.cred_mgmt = Some(true);
519 options.large_blobs = Some(false);
520 options.pin_uv_auth_token = Some(true);
521 let mut response = ResponseBuilder { versions, aaguid }.build();
522 response.extensions =
523 Some(Vec::from_slice(&[Extension::CredProtect, Extension::HmacSecret]).unwrap());
524 response.options = Some(options);
525 response.max_msg_size = Some(3072);
526 response.pin_protocols = Some(Vec::from_slice(&[1, 0]).unwrap());
527 response.max_creds_in_list = Some(10);
528 response.max_cred_id_length = Some(255);
529 response.transports = Some(Vec::from_slice(&[Transport::Nfc, Transport::Usb]).unwrap());
530 assert_ser_tokens(
531 &response,
532 &[
533 Token::Map { len: Some(9) },
534 Token::U64(0x01),
536 Token::Seq { len: Some(3) },
537 Token::BorrowedStr("U2F_V2"),
538 Token::BorrowedStr("FIDO_2_0"),
539 Token::BorrowedStr("FIDO_2_1"),
540 Token::SeqEnd,
541 Token::U64(0x02),
543 Token::Some,
544 Token::Seq { len: Some(2) },
545 Token::BorrowedStr("credProtect"),
546 Token::BorrowedStr("hmac-secret"),
547 Token::SeqEnd,
548 Token::U64(0x03),
550 Token::BorrowedBytes(AAGUID),
551 Token::U64(0x04),
553 Token::Some,
554 Token::Struct {
555 name: "CtapOptions",
556 len: 7,
557 },
558 Token::BorrowedStr("rk"),
559 Token::Bool(true),
560 Token::BorrowedStr("up"),
561 Token::Bool(true),
562 Token::BorrowedStr("plat"),
563 Token::Some,
564 Token::Bool(false),
565 Token::BorrowedStr("credMgmt"),
566 Token::Some,
567 Token::Bool(true),
568 Token::BorrowedStr("clientPin"),
569 Token::Some,
570 Token::Bool(false),
571 Token::BorrowedStr("largeBlobs"),
572 Token::Some,
573 Token::Bool(false),
574 Token::BorrowedStr("pinUvAuthToken"),
575 Token::Some,
576 Token::Bool(true),
577 Token::StructEnd,
578 Token::U64(0x05),
580 Token::Some,
581 Token::U64(3072),
582 Token::U64(0x06),
584 Token::Some,
585 Token::Seq { len: Some(2) },
586 Token::U8(1),
587 Token::U8(0),
588 Token::SeqEnd,
589 Token::U64(0x07),
591 Token::Some,
592 Token::U64(10),
593 Token::U64(0x08),
595 Token::Some,
596 Token::U64(255),
597 Token::U64(0x09),
599 Token::Some,
600 Token::Seq { len: Some(2) },
601 Token::BorrowedStr("nfc"),
602 Token::BorrowedStr("usb"),
603 Token::SeqEnd,
604 Token::MapEnd,
605 ],
606 );
607 }
608}