1#![deny(warnings)]
11#![warn(unused_extern_crates)]
12#![deny(clippy::todo)]
14#![deny(clippy::unimplemented)]
15#![deny(clippy::unwrap_used)]
16#![deny(clippy::expect_used)]
17#![deny(clippy::panic)]
18#![deny(clippy::unreachable)]
19#![deny(clippy::await_holding_lock)]
20#![deny(clippy::needless_pass_by_value)]
21#![deny(clippy::trivially_copy_pass_by_ref)]
22
23pub mod mds;
24pub mod patch;
25pub mod query;
26
27use crate::mds::AuthenticatorStatus;
28use crate::mds::AuthenticatorTransport;
29use crate::mds::FidoDevice as RawFidoDevice;
30use crate::mds::FidoMds as RawFidoMds;
31use crate::mds::MetadataStatement as RawMetadataStatement;
32use crate::mds::MultiDeviceCredentialSupport;
33use crate::mds::StatusReport as RawStatusReport;
34use crate::mds::UserVerificationMethod as RawUserVerificationMethod;
35use crate::mds::VerificationMethodAndCombinations;
36use crate::mds::{
37 AttestationType, AuthenticationAlgorithm, AuthenticatorGetInfo, BiometricAccuracyDescriptor,
38 CodeAccuracyDescriptor, EcdaaAnchor, ExtensionDescriptor, KeyProtection,
39 PatternAccuracyDescriptor, ProtocolFamily, PublicKeyAlg,
40};
41
42use crate::query::{AttrValueAssertion, Query};
43
44use webauthn_attestation_ca::{AttestationCaList, AttestationCaListBuilder};
45
46use base64::{engine::general_purpose::STANDARD, Engine};
47use compact_jwt::JwtError;
48use std::cmp::Ordering;
49use std::fmt;
50use std::rc;
51use std::str::FromStr;
52use tracing::{debug, error, info, trace, warn};
53
54use std::collections::{BTreeMap, BTreeSet};
55use std::hash::Hash;
56use uuid::Uuid;
57
58pub const FIDO_MDS_URL: &str = "https://mds.fidoalliance.org/";
59
60#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum StatusReport {
65 NotFidoCertified {
67 effective_date: Option<String>,
69 authenticator_version: u32,
71 url: Option<String>,
73 },
74 UserVerificationBypass {
77 effective_date: Option<String>,
79 authenticator_version: u32,
81 url: Option<String>,
83 },
84 AttestationKeyCompromise {
87 effective_date: Option<String>,
89 authenticator_version: u32,
91 certificate: Option<String>,
94 url: Option<String>,
96 },
97 UserKeyRemoteCompromise {
100 effective_date: Option<String>,
102 authenticator_version: u32,
104 url: Option<String>,
106 },
107 UserKeyPhysicalCompromise {
110 effective_date: Option<String>,
112 authenticator_version: u32,
114 url: Option<String>,
116 },
117 UpdateAvailable {
120 effective_date: Option<String>,
122 authenticator_version: u32,
124 url: Option<String>,
126 },
127 Revoked {
129 effective_date: Option<String>,
131 authenticator_version: u32,
133 url: Option<String>,
135 },
136 SelfAssertionSubmitted {
139 effective_date: Option<String>,
141 authenticator_version: u32,
143 },
144 FidoCertified {
146 effective_date: Option<String>,
148 authenticator_version: Option<u32>,
150 certification_descriptor: Option<String>,
152 certificate_number: Option<String>,
154 certification_policy_version: Option<String>,
156 certification_requirements_version: Option<String>,
158 url: Option<String>,
160 },
161 FidoCertifiedL1 {
163 effective_date: Option<String>,
165 authenticator_version: Option<u32>,
167 certification_descriptor: Option<String>,
169 certificate_number: Option<String>,
171 certification_policy_version: Option<String>,
173 certification_requirements_version: Option<String>,
175 url: Option<String>,
177 },
178 FidoCertifiedL1Plus {
180 effective_date: Option<String>,
182 authenticator_version: Option<u32>,
184 certification_descriptor: Option<String>,
186 certificate_number: Option<String>,
188 certification_policy_version: Option<String>,
190 certification_requirements_version: Option<String>,
192 url: Option<String>,
194 },
195 FidoCertifiedL2 {
197 effective_date: Option<String>,
199 authenticator_version: Option<u32>,
201 certification_descriptor: Option<String>,
203 certificate_number: Option<String>,
205 certification_policy_version: Option<String>,
207 certification_requirements_version: Option<String>,
209 url: Option<String>,
211 },
212 FidoCertifiedL2Plus {
214 effective_date: Option<String>,
216 authenticator_version: Option<u32>,
218 certification_descriptor: Option<String>,
220 certificate_number: Option<String>,
222 certification_policy_version: Option<String>,
224 certification_requirements_version: Option<String>,
226 url: Option<String>,
228 },
229 FidoCertifiedL3 {
231 effective_date: Option<String>,
233 authenticator_version: Option<u32>,
235 certification_descriptor: Option<String>,
237 certificate_number: Option<String>,
239 certification_policy_version: Option<String>,
241 certification_requirements_version: Option<String>,
243 url: Option<String>,
245 },
246 FidoCertifiedL3Plus {
248 effective_date: Option<String>,
250 authenticator_version: Option<u32>,
252 certification_descriptor: Option<String>,
254 certificate_number: Option<String>,
256 certification_policy_version: Option<String>,
258 certification_requirements_version: Option<String>,
260 url: Option<String>,
262 },
263}
264
265impl TryFrom<RawStatusReport> for StatusReport {
266 type Error = ();
267
268 fn try_from(raw_sr: RawStatusReport) -> Result<Self, Self::Error> {
269 match raw_sr {
270 RawStatusReport {
271 status: AuthenticatorStatus::NotFidoCertified,
272 effective_date,
273 authenticator_version,
274 url,
275 ..
276 } => Ok(StatusReport::NotFidoCertified {
277 effective_date,
278 authenticator_version: authenticator_version.unwrap_or(0),
279 url,
280 }),
281 RawStatusReport {
282 status: AuthenticatorStatus::SelfAssertionSubmitted,
283 effective_date,
284 authenticator_version: Some(authenticator_version),
285 ..
286 } => Ok(StatusReport::SelfAssertionSubmitted {
287 effective_date,
288 authenticator_version,
289 }),
290 RawStatusReport {
291 status: AuthenticatorStatus::UserVerificationBypass,
292 effective_date,
293 authenticator_version: Some(authenticator_version),
294 url,
295 ..
296 } => Ok(StatusReport::UserVerificationBypass {
297 effective_date,
298 authenticator_version,
299 url,
300 }),
301 RawStatusReport {
302 status: AuthenticatorStatus::AttestationKeyCompromise,
303 effective_date,
304 authenticator_version: Some(authenticator_version),
305 certificate,
306 url,
307 ..
308 } => Ok(StatusReport::AttestationKeyCompromise {
309 effective_date,
310 authenticator_version,
311 certificate,
312 url,
313 }),
314 RawStatusReport {
315 status: AuthenticatorStatus::UserKeyRemoteCompromise,
316 effective_date,
317 authenticator_version: Some(authenticator_version),
318 url,
319 ..
320 } => Ok(StatusReport::UserKeyRemoteCompromise {
321 effective_date,
322 authenticator_version,
323 url,
324 }),
325 RawStatusReport {
326 status: AuthenticatorStatus::UserKeyPhysicalCompromise,
327 effective_date,
328 authenticator_version: Some(authenticator_version),
329 url,
330 ..
331 } => Ok(StatusReport::UserKeyPhysicalCompromise {
332 effective_date,
333 authenticator_version,
334 url,
335 }),
336 RawStatusReport {
337 status: AuthenticatorStatus::Revoked,
338 effective_date,
339 authenticator_version: Some(authenticator_version),
340 url,
341 ..
342 } => Ok(StatusReport::Revoked {
343 effective_date,
344 authenticator_version,
345 url,
346 }),
347 RawStatusReport {
348 status: AuthenticatorStatus::UpdateAvailable,
349 effective_date,
350 authenticator_version: Some(authenticator_version),
351 url,
352 ..
353 } => Ok(StatusReport::UpdateAvailable {
354 effective_date,
355 authenticator_version,
356 url,
357 }),
358 RawStatusReport {
359 status: AuthenticatorStatus::FidoCertified,
360 effective_date,
361 authenticator_version,
362 certification_descriptor,
363 certificate_number,
364 certification_policy_version,
365 certification_requirements_version,
366 url,
367 ..
368 } => Ok(StatusReport::FidoCertified {
369 effective_date,
370 authenticator_version,
371 certification_descriptor,
372 certificate_number,
373 certification_policy_version,
374 certification_requirements_version,
375 url,
376 }),
377 RawStatusReport {
378 status: AuthenticatorStatus::FidoCertifiedL1,
379 effective_date,
380 authenticator_version,
381 certification_descriptor,
382 certificate_number,
383 certification_policy_version,
384 certification_requirements_version,
385 url,
386 ..
387 } => Ok(StatusReport::FidoCertifiedL1 {
388 effective_date,
389 authenticator_version,
390 certification_descriptor,
391 certificate_number,
392 certification_policy_version,
393 certification_requirements_version,
394 url,
395 }),
396 RawStatusReport {
397 status: AuthenticatorStatus::FidoCertifiedL1Plus,
398 effective_date,
399 authenticator_version,
400 certification_descriptor,
401 certificate_number,
402 certification_policy_version,
403 certification_requirements_version,
404 url,
405 ..
406 } => Ok(StatusReport::FidoCertifiedL1Plus {
407 effective_date,
408 authenticator_version,
409 certification_descriptor,
410 certificate_number,
411 certification_policy_version,
412 certification_requirements_version,
413 url,
414 }),
415 RawStatusReport {
416 status: AuthenticatorStatus::FidoCertifiedL2,
417 effective_date,
418 authenticator_version,
419 certification_descriptor,
420 certificate_number,
421 certification_policy_version,
422 certification_requirements_version,
423 url,
424 ..
425 } => Ok(StatusReport::FidoCertifiedL2 {
426 effective_date,
427 authenticator_version,
428 certification_descriptor,
429 certificate_number,
430 certification_policy_version,
431 certification_requirements_version,
432 url,
433 }),
434 RawStatusReport {
435 status: AuthenticatorStatus::FidoCertifiedL2Plus,
436 effective_date,
437 authenticator_version,
438 certification_descriptor,
439 certificate_number,
440 certification_policy_version,
441 certification_requirements_version,
442 url,
443 ..
444 } => Ok(StatusReport::FidoCertifiedL2Plus {
445 effective_date,
446 authenticator_version,
447 certification_descriptor,
448 certificate_number,
449 certification_policy_version,
450 certification_requirements_version,
451 url,
452 }),
453 RawStatusReport {
454 status: AuthenticatorStatus::FidoCertifiedL3,
455 effective_date,
456 authenticator_version,
457 certification_descriptor,
458 certificate_number,
459 certification_policy_version,
460 certification_requirements_version,
461 url,
462 ..
463 } => Ok(StatusReport::FidoCertifiedL3 {
464 effective_date,
465 authenticator_version,
466 certification_descriptor,
467 certificate_number,
468 certification_policy_version,
469 certification_requirements_version,
470 url,
471 }),
472 RawStatusReport {
473 status: AuthenticatorStatus::FidoCertifiedL3Plus,
474 effective_date,
475 authenticator_version,
476 certification_descriptor,
477 certificate_number,
478 certification_policy_version,
479 certification_requirements_version,
480 url,
481 ..
482 } => Ok(StatusReport::FidoCertifiedL3Plus {
483 effective_date,
484 authenticator_version,
485 certification_descriptor,
486 certificate_number,
487 certification_policy_version,
488 certification_requirements_version,
489 url,
490 }),
491 sr => {
492 warn!("Invalid Status Report - {:?}", sr);
493 Err(())
494 }
495 }
496 }
497}
498
499impl PartialEq<AuthenticatorStatus> for StatusReport {
500 fn eq(&self, other: &AuthenticatorStatus) -> bool {
501 #[allow(clippy::match_like_matches_macro)]
503 match (self, other) {
504 (StatusReport::NotFidoCertified { .. }, AuthenticatorStatus::NotFidoCertified)
505 | (
506 StatusReport::SelfAssertionSubmitted { .. },
507 AuthenticatorStatus::SelfAssertionSubmitted,
508 )
509 | (
510 StatusReport::UserVerificationBypass { .. },
511 AuthenticatorStatus::UserVerificationBypass,
512 )
513 | (
514 StatusReport::AttestationKeyCompromise { .. },
515 AuthenticatorStatus::AttestationKeyCompromise,
516 )
517 | (
518 StatusReport::UserKeyRemoteCompromise { .. },
519 AuthenticatorStatus::UserKeyRemoteCompromise,
520 )
521 | (
522 StatusReport::UserKeyPhysicalCompromise { .. },
523 AuthenticatorStatus::UserKeyPhysicalCompromise,
524 )
525 | (StatusReport::Revoked { .. }, AuthenticatorStatus::Revoked)
526 | (StatusReport::UpdateAvailable { .. }, AuthenticatorStatus::UpdateAvailable)
527 | (StatusReport::FidoCertified { .. }, AuthenticatorStatus::FidoCertified)
528 | (StatusReport::FidoCertifiedL1 { .. }, AuthenticatorStatus::FidoCertifiedL1)
529 | (
530 StatusReport::FidoCertifiedL1Plus { .. },
531 AuthenticatorStatus::FidoCertifiedL1Plus,
532 )
533 | (StatusReport::FidoCertifiedL2 { .. }, AuthenticatorStatus::FidoCertifiedL2)
534 | (
535 StatusReport::FidoCertifiedL2Plus { .. },
536 AuthenticatorStatus::FidoCertifiedL2Plus,
537 )
538 | (StatusReport::FidoCertifiedL3 { .. }, AuthenticatorStatus::FidoCertifiedL3)
539 | (
540 StatusReport::FidoCertifiedL3Plus { .. },
541 AuthenticatorStatus::FidoCertifiedL3Plus,
542 ) => true,
543 _ => false,
544 }
545 }
546}
547
548impl StatusReport {
549 pub fn effective_date(&self) -> Option<&str> {
551 match self {
552 StatusReport::NotFidoCertified { effective_date, .. }
553 | StatusReport::UserVerificationBypass { effective_date, .. }
554 | StatusReport::AttestationKeyCompromise { effective_date, .. }
555 | StatusReport::UserKeyRemoteCompromise { effective_date, .. }
556 | StatusReport::UserKeyPhysicalCompromise { effective_date, .. }
557 | StatusReport::UpdateAvailable { effective_date, .. }
558 | StatusReport::Revoked { effective_date, .. }
559 | StatusReport::SelfAssertionSubmitted { effective_date, .. }
560 | StatusReport::FidoCertified { effective_date, .. }
561 | StatusReport::FidoCertifiedL1 { effective_date, .. }
562 | StatusReport::FidoCertifiedL1Plus { effective_date, .. }
563 | StatusReport::FidoCertifiedL2 { effective_date, .. }
564 | StatusReport::FidoCertifiedL2Plus { effective_date, .. }
565 | StatusReport::FidoCertifiedL3 { effective_date, .. }
566 | StatusReport::FidoCertifiedL3Plus { effective_date, .. } => effective_date.as_deref(),
567 }
568 }
569
570 pub fn as_str(&self) -> &str {
571 match self {
572 StatusReport::NotFidoCertified { .. } => "Not FIDO Certified",
573 StatusReport::UserVerificationBypass { .. } => "⚠️ User Verification Bypass",
574 StatusReport::AttestationKeyCompromise { .. } => "⚠️ Attestation Key Compromise",
575 StatusReport::UserKeyRemoteCompromise { .. } => "⚠️ User Key Remote Compromise",
576 StatusReport::UserKeyPhysicalCompromise { .. } => "⚠️ User Key Physical Compromise",
577 StatusReport::UpdateAvailable { .. } => "⚠️ Update Available",
578 StatusReport::Revoked { .. } => "⚠️ Revoked",
579 StatusReport::SelfAssertionSubmitted { .. } => "Self Assertion",
580 StatusReport::FidoCertified { .. } | StatusReport::FidoCertifiedL1 { .. } => {
581 "FIDO Certified - L1"
582 }
583 StatusReport::FidoCertifiedL1Plus { .. } => "FIDO Certified - L1 Plus",
584 StatusReport::FidoCertifiedL2 { .. } => "FIDO Certified - L2",
585 StatusReport::FidoCertifiedL2Plus { .. } => "FIDO Certified - L2 Plus",
586 StatusReport::FidoCertifiedL3 { .. } => "FIDO Certified - L3",
587 StatusReport::FidoCertifiedL3Plus { .. } => "FIDO Certified - L3 Plus",
588 }
589 }
590
591 pub(crate) fn numeric(&self) -> u8 {
592 match self {
593 StatusReport::NotFidoCertified { .. }
594 | StatusReport::UserVerificationBypass { .. }
595 | StatusReport::AttestationKeyCompromise { .. }
596 | StatusReport::UserKeyRemoteCompromise { .. }
597 | StatusReport::UserKeyPhysicalCompromise { .. }
598 | StatusReport::UpdateAvailable { .. }
599 | StatusReport::Revoked { .. }
600 | StatusReport::SelfAssertionSubmitted { .. } => 0,
601 StatusReport::FidoCertified { .. } | StatusReport::FidoCertifiedL1 { .. } => 10,
602 StatusReport::FidoCertifiedL1Plus { .. } => 11,
603 StatusReport::FidoCertifiedL2 { .. } => 20,
604 StatusReport::FidoCertifiedL2Plus { .. } => 21,
605 StatusReport::FidoCertifiedL3 { .. } => 30,
606 StatusReport::FidoCertifiedL3Plus { .. } => 31,
607 }
608 }
609
610 fn gte(&self, level: &AuthenticatorStatus) -> bool {
611 self.numeric() >= level.numeric()
612 }
613}
614
615impl PartialOrd for StatusReport {
616 fn partial_cmp(&self, other: &StatusReport) -> Option<Ordering> {
617 Some(self.cmp(other))
618 }
619}
620
621impl Ord for StatusReport {
622 fn cmp(&self, other: &Self) -> Ordering {
623 match (self.effective_date(), other.effective_date()) {
624 (None, None) => Ordering::Equal,
625 (Some(_), None) => Ordering::Less,
626 (None, Some(_)) => Ordering::Greater,
627 (Some(a), Some(b)) => a.cmp(b),
628 }
629 }
630}
631
632#[derive(Debug, Clone, Hash, PartialEq)]
635pub enum UserVerificationMethod {
636 None,
638 PresenceInternal,
641 PasscodeInternal(Option<CodeAccuracyDescriptor>),
644 PasscodeExternal(Option<CodeAccuracyDescriptor>),
647 FingerprintInternal(Option<BiometricAccuracyDescriptor>),
649 HandprintInternal(Option<BiometricAccuracyDescriptor>),
651 EyeprintInternal(Option<BiometricAccuracyDescriptor>),
653 VoiceprintInternal(Option<BiometricAccuracyDescriptor>),
655 FaceprintInternal(Option<BiometricAccuracyDescriptor>),
657 LocationInternal,
659 PatternInternal(Option<PatternAccuracyDescriptor>),
662}
663
664impl FromStr for UserVerificationMethod {
665 type Err = ();
666
667 fn from_str(s: &str) -> Result<Self, Self::Err> {
668 match s {
669 "none" => Ok(UserVerificationMethod::None),
670 "presence" => Ok(UserVerificationMethod::PresenceInternal),
671 "pin_internal" | "passcode_internal" => {
672 Ok(UserVerificationMethod::PasscodeInternal(None))
673 }
674 "pin_external" | "passcode_external" => {
675 Ok(UserVerificationMethod::PasscodeExternal(None))
676 }
677 "fingerprint_internal" | "fprint_internal" | "fprint" => {
678 Ok(UserVerificationMethod::FingerprintInternal(None))
679 }
680 "handprint_internal" | "hprint_internal" | "hprint" => {
681 Ok(UserVerificationMethod::HandprintInternal(None))
682 }
683 "eyeprint_internal" | "eprint_internal" | "eprint" => {
684 Ok(UserVerificationMethod::EyeprintInternal(None))
685 }
686 "voiceprint_internal" | "vprint_internal" | "vprint" => {
687 Ok(UserVerificationMethod::VoiceprintInternal(None))
688 }
689 "faceprint_internal" | "face_internal" | "face" => {
690 Ok(UserVerificationMethod::FaceprintInternal(None))
691 }
692 "pattern_internal" | "pattern" => Ok(UserVerificationMethod::PatternInternal(None)),
693 _ => Err(()),
694 }
695 }
696}
697
698impl fmt::Display for UserVerificationMethod {
699 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
700 match self {
701 UserVerificationMethod::None => write!(f, "None"),
702 UserVerificationMethod::PresenceInternal => write!(f, "PresenceInternal"),
703 UserVerificationMethod::PasscodeInternal(Some(cad)) => write!(
704 f,
705 "PasscodeInternal ( base: {}, min_length: {}, max_retries: {:?}, slowdown: {:?} )",
706 cad.base, cad.min_length, cad.max_retries, cad.block_slowdown
707 ),
708 UserVerificationMethod::PasscodeInternal(None) => {
709 write!(f, "PasscodeInternal (unknown limits)")
710 }
711 UserVerificationMethod::PasscodeExternal(Some(cad)) => write!(
712 f,
713 "PasscodeExternal ( base: {}, min_length: {}, max_retries: {:?}, slowdown: {:?} )",
714 cad.base, cad.min_length, cad.max_retries, cad.block_slowdown
715 ),
716 UserVerificationMethod::PasscodeExternal(None) => {
717 write!(f, "PasscodeExternal (unknown limits)")
718 }
719 UserVerificationMethod::FingerprintInternal(_) => write!(f, "FingerprintInternal"),
720 UserVerificationMethod::HandprintInternal(_) => write!(f, "HandprintInternal"),
721 UserVerificationMethod::EyeprintInternal(_) => write!(f, "EyeprintInternal"),
722 UserVerificationMethod::VoiceprintInternal(_) => write!(f, "VoiceprintInternal"),
723 UserVerificationMethod::FaceprintInternal(_) => write!(f, "FaceprintInternal"),
724 UserVerificationMethod::LocationInternal => write!(f, "LocationInternal"),
725 UserVerificationMethod::PatternInternal(_) => write!(f, "PatternInternal"),
726 }
727 }
728}
729
730impl TryFrom<VerificationMethodAndCombinations> for UserVerificationMethod {
731 type Error = ();
732
733 fn try_from(uvmac: VerificationMethodAndCombinations) -> Result<Self, Self::Error> {
734 let VerificationMethodAndCombinations {
735 user_verification_method,
736 ca_desc,
737 ba_desc,
738 pa_desc,
739 } = uvmac;
740
741 match (user_verification_method, ca_desc, ba_desc, pa_desc) {
742 (RawUserVerificationMethod::None, None, None, None) => Ok(UserVerificationMethod::None),
743 (RawUserVerificationMethod::PresenceInternal, None, None, None) => {
744 Ok(UserVerificationMethod::PresenceInternal)
745 }
746 (RawUserVerificationMethod::PasscodeInternal, ca_desc, None, None) => {
747 Ok(UserVerificationMethod::PasscodeInternal(ca_desc))
748 }
749 (RawUserVerificationMethod::PasscodeExternal, ca_desc, None, None) => {
750 Ok(UserVerificationMethod::PasscodeExternal(ca_desc))
751 }
752 (RawUserVerificationMethod::FingerprintInternal, None, ba_desc, None) => {
753 Ok(UserVerificationMethod::FingerprintInternal(ba_desc))
754 }
755 (RawUserVerificationMethod::HandprintInternal, None, ba_desc, None) => {
756 Ok(UserVerificationMethod::HandprintInternal(ba_desc))
757 }
758 (RawUserVerificationMethod::EyeprintInternal, None, ba_desc, None) => {
759 Ok(UserVerificationMethod::EyeprintInternal(ba_desc))
760 }
761 (RawUserVerificationMethod::VoiceprintInternal, None, ba_desc, None) => {
762 Ok(UserVerificationMethod::VoiceprintInternal(ba_desc))
763 }
764 (RawUserVerificationMethod::FaceprintInternal, None, ba_desc, None) => {
765 Ok(UserVerificationMethod::FaceprintInternal(ba_desc))
766 }
767 (RawUserVerificationMethod::LocationInternal, None, None, None) => {
768 Ok(UserVerificationMethod::LocationInternal)
769 }
770 (RawUserVerificationMethod::PatternInternal, None, None, pa_desc) => {
771 Ok(UserVerificationMethod::PatternInternal(pa_desc))
772 }
773 r => {
775 warn!("Invalid UVM - {:?}", r);
776 Err(())
777 }
778 }
779 }
780}
781
782#[derive(Debug, Clone)]
783enum FidoDevice {
784 Uaf(UAF),
785 U2F(U2F),
786 FIDO2(FIDO2),
787}
788
789#[derive(Debug, Clone)]
791pub struct UAF {
792 pub aaid: String,
794 pub description: String,
796 pub alternative_descriptions: BTreeMap<String, String>,
798 pub authenticator_version: u32,
800 pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
802 pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
804 pub attestation_types: Vec<AttestationType>,
806 pub user_verification_details: Vec<Vec<UserVerificationMethod>>,
831 pub key_protection: Vec<KeyProtection>,
833 pub is_key_restricted: bool,
837 pub is_fresh_user_verification_required: bool,
841 pub attestation_root_certificates: Vec<Vec<u8>>,
843 pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
845 pub supported_extensions: Vec<ExtensionDescriptor>,
847 pub authenticator_get_info: Option<AuthenticatorGetInfo>,
849 pub status_reports: BTreeSet<StatusReport>,
851 pub time_of_last_status_change: String,
853}
854
855#[derive(Debug, Clone)]
857pub struct U2F {
858 pub attestation_certificate_key_identifiers: Vec<String>,
860 pub description: String,
862 pub alternative_descriptions: BTreeMap<String, String>,
864 pub authenticator_version: u32,
866 pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
868 pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
870 pub attestation_types: Vec<AttestationType>,
872 pub user_verification_details: Vec<Vec<UserVerificationMethod>>,
897 pub key_protection: Vec<KeyProtection>,
899 pub is_key_restricted: bool,
903 pub is_fresh_user_verification_required: bool,
907 pub attestation_root_certificates: Vec<Vec<u8>>,
909 pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
911 pub supported_extensions: Vec<ExtensionDescriptor>,
913 pub authenticator_get_info: Option<AuthenticatorGetInfo>,
915 pub status_reports: BTreeSet<StatusReport>,
917 pub time_of_last_status_change: String,
919}
920
921impl fmt::Display for U2F {
922 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
923 write!(f, "{}", self.description)
924 }
925}
926
927#[derive(Debug, Clone)]
929pub struct FIDO2 {
930 pub aaguid: Uuid,
932 pub description: String,
934 pub alternative_descriptions: BTreeMap<String, String>,
936 pub authenticator_version: u32,
938 pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
940 pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
942 pub attestation_types: Vec<AttestationType>,
944 pub user_verification_details: Vec<Vec<UserVerificationMethod>>,
969 pub key_protection: Vec<KeyProtection>,
971 pub is_key_restricted: bool,
975 pub is_fresh_user_verification_required: bool,
979 pub attestation_root_certificates: Vec<Vec<u8>>,
981 pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
983 pub supported_extensions: Vec<ExtensionDescriptor>,
985 pub authenticator_get_info: Option<AuthenticatorGetInfo>,
987 pub status_reports: BTreeSet<StatusReport>,
989 pub time_of_last_status_change: String,
991 pub inconsistent_data: bool,
995 pub patched_data: bool,
999 pub multi_device_credential_support: MultiDeviceCredentialSupport,
1001}
1002
1003impl fmt::Display for FIDO2 {
1004 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1005 write!(f, "{} - {}", self.aaguid, self.description)
1006 }
1007}
1008
1009impl FIDO2 {
1010 fn query_attr(&self, ava: &AttrValueAssertion) -> bool {
1011 match ava {
1012 AttrValueAssertion::AaguidEq(u) => self.aaguid == *u,
1013 AttrValueAssertion::DescriptionEq(s) => &self.description == s,
1014 AttrValueAssertion::DescriptionCnt(s) => self
1015 .description
1016 .to_lowercase()
1017 .contains(s.to_lowercase().as_str()),
1018 AttrValueAssertion::StatusEq(s) => self
1019 .status_reports
1020 .last()
1021 .map(|sr| sr == s)
1022 .unwrap_or(false),
1023 AttrValueAssertion::StatusGte(s) => self.status_reports.iter().any(|sr| sr.gte(s)),
1024 AttrValueAssertion::StatusLt(s) => self.status_reports.iter().any(|sr| !sr.gte(s)),
1025 AttrValueAssertion::TransportEq(t) => self
1026 .authenticator_get_info
1027 .as_ref()
1028 .map(|agi| agi.transports.contains(t))
1029 .unwrap_or(false),
1030 AttrValueAssertion::UserVerificationCnt(u) => self
1031 .user_verification_details
1032 .iter()
1033 .flat_map(|and| and.iter())
1034 .any(|uvd| std::mem::discriminant(uvd) == std::mem::discriminant(u)),
1035 }
1036 }
1037
1038 fn query_match(&self, q: &Query) -> bool {
1039 match q {
1040 Query::Op(ava) => self.query_attr(ava),
1041 Query::And(a, b) => self.query_match(a) && self.query_match(b),
1042 Query::Or(a, b) => self.query_match(a) || self.query_match(b),
1043 Query::Not(a) => !self.query_match(a),
1044 }
1045 }
1046}
1047
1048impl TryFrom<RawFidoDevice> for FidoDevice {
1049 type Error = ();
1050
1051 fn try_from(rawdevice: RawFidoDevice) -> Result<Self, Self::Error> {
1052 let RawFidoDevice {
1053 aaid,
1054 aaguid,
1055 attestation_certificate_key_identifiers,
1056 metadata_statement,
1057 biometric_status_reports,
1058 status_reports,
1059 time_of_last_status_change,
1060 rogue_list_url: _,
1061 rogue_list_hash: _,
1062 } = rawdevice;
1063
1064 if aaid != metadata_statement.aaid {
1065 warn!(
1066 "Inconsistent aaid {:?} != {:?}",
1067 aaid, metadata_statement.aaid
1068 );
1069 return Err(());
1070 }
1071
1072 if aaguid != metadata_statement.aaguid {
1073 warn!(
1074 "Inconsistent aaguid {:?} != {:?}",
1075 aaguid, metadata_statement.aaguid
1076 );
1077 return Err(());
1078 }
1079
1080 if attestation_certificate_key_identifiers
1081 != metadata_statement.attestation_certificate_key_identifiers
1082 {
1083 warn!(
1084 "Inconsistent aki {:?} != {:?}",
1085 attestation_certificate_key_identifiers,
1086 metadata_statement.attestation_certificate_key_identifiers
1087 );
1088 return Err(());
1089 }
1090
1091 if !biometric_status_reports.is_empty() {
1092 debug!(?biometric_status_reports);
1093 }
1094
1095 let mut invalid_metadata = false;
1096 let mut inconsistent_data = false;
1097 let mut patched_data = false;
1098
1099 let RawMetadataStatement {
1102 legal_header: _,
1103 upv: _,
1104 aaid: _,
1105 aaguid: _,
1106 attestation_certificate_key_identifiers: _,
1107 description,
1108 alternative_descriptions,
1109 friendly_names: _,
1110 authenticator_version,
1111 protocol_family,
1112 schema: _,
1113 authentication_algorithms,
1114 public_key_alg_and_encodings,
1115 attestation_types,
1116 mut user_verification_details,
1117 key_protection,
1118 is_key_restricted,
1119 is_fresh_user_verification_required,
1120 matcher_protection: _,
1121 crypto_strength: _,
1122 attachment_hint: _,
1123 tc_display: _,
1124 tc_display_content_type: _,
1125 tc_display_png_characteristics: _,
1126 attestation_root_certificates,
1127 ecdaa_trust_anchors,
1128 icon: _,
1129 supported_extensions,
1130 mut authenticator_get_info,
1131 multi_device_credential_support,
1132 } = metadata_statement;
1133
1134 let mut status_reports: BTreeSet<_> = status_reports
1135 .into_iter()
1136 .filter_map(|sr| {
1137 sr.try_into()
1138 .map_err(|_| {
1139 warn!(
1140 "Invalid Status Report located in: {:?}, {:?}, {:?}",
1141 aaid, aaguid, attestation_certificate_key_identifiers
1142 );
1143 invalid_metadata = true;
1144 })
1145 .ok()
1146 })
1147 .collect();
1148
1149 if let Some(status_report) = patch::mds_deny_insecure_authenticators(aaguid) {
1150 status_reports.insert(status_report);
1151 }
1152
1153 let attestation_root_certificates = attestation_root_certificates.into_iter()
1154 .filter_map(|cert| {
1155 let trim_cert = cert.trim();
1156 if trim_cert != cert {
1157 warn!(
1158 "Invalid attestation root certificate - leading/trailing whitespace: {:?}, {:?}, {:?}",
1159 aaid, aaguid, attestation_certificate_key_identifiers
1160 );
1161 inconsistent_data = true;
1162 None
1163 } else {
1164 match STANDARD.decode(trim_cert) {
1165 Ok(der) => Some(der),
1166 Err(e) => {
1167 warn!(
1168 "Invalid attestation root certificate - invalid base64 {:?} : {:?}, {:?}, {:?}",
1169 e,
1170 aaid, aaguid, attestation_certificate_key_identifiers
1171 );
1172 invalid_metadata = true;
1173 None
1174 }
1175 }
1176 }
1177 })
1178 .collect();
1179
1180 if patch::mds_user_verification_method_code_accuracy_descriptor(
1181 &mut user_verification_details,
1182 ) {
1183 info!(
1184 "Device was patched for invalid code accuracy descriptior on presence: {:?}, {:?}, {:?}",
1185 aaid, aaguid, attestation_certificate_key_identifiers
1186 );
1187 inconsistent_data = true;
1188 patched_data = true;
1189 }
1190
1191 if patch::mds_user_verification_method_invalid_all_present(&mut user_verification_details) {
1192 info!(
1193 "Device was patched for uvm 'all', which violates fido's standards: {:?}, {:?}, {:?}",
1194 aaid, aaguid, attestation_certificate_key_identifiers
1195 );
1196 inconsistent_data = true;
1197 patched_data = true;
1198 }
1199
1200 let mut user_verification_details: Vec<Vec<_>> = user_verification_details.into_iter()
1203 .map(|inner| {
1204 inner.into_iter()
1205 .filter_map(|uvm| {
1206 uvm.try_into()
1207 .map_err(|_e| {
1208 warn!(
1209 "Invalid user verification details located in: {:?}, {:?}, {:?}",
1210 aaid, aaguid, attestation_certificate_key_identifiers
1211 );
1212 assert!(aaguid.is_none());
1213 invalid_metadata = true;
1214 })
1215 .ok()
1216
1217 })
1218 .collect()
1219 })
1220 .collect();
1221
1222 match patch::user_verification_method(aaguid, &user_verification_details) {
1223 Ok(None) => {
1224 }
1226 Ok(Some(mut uvm_patch)) => {
1227 inconsistent_data = true;
1229 patched_data = true;
1230 std::mem::swap(&mut uvm_patch, &mut user_verification_details)
1231 }
1232 Err(_e) => {
1233 error!("Unable to patch user verification methods. This is a bug and should be reported. https://github.com/kanidm/webauthn-rs/issues");
1234 }
1235 }
1236
1237 for uvm_and in user_verification_details.iter() {
1238 if uvm_and.contains(&UserVerificationMethod::None) && uvm_and.len() != 1 {
1239 debug!(?user_verification_details);
1240 debug!(?description);
1241 warn!(
1242 "Illogical user verification method located in - None may not exist with other UVM: {:?}, {:?}, {:?}",
1243 aaid, aaguid, attestation_certificate_key_identifiers
1244 );
1245 invalid_metadata = true;
1246 }
1247 }
1248
1249 let agi_invalid = if let Some(agi) = authenticator_get_info.as_ref() {
1254 agi.extensions.is_empty()
1255 && agi.pin_uv_auth_protocols.is_empty()
1256 && agi.transports.is_empty()
1257 && agi.algorithms.is_empty()
1258 } else {
1259 false
1260 };
1261
1262 if agi_invalid {
1263 authenticator_get_info = None;
1264 info!(
1265 "Device was patched for invalid authenticator get info that was not collected from a real device: {:?}, {:?}, {:?}",
1266 aaid, aaguid, attestation_certificate_key_identifiers
1267 );
1268 patched_data = true;
1269 inconsistent_data = true;
1270 }
1271
1272 if let Some(agi) = authenticator_get_info.as_ref() {
1273 if !supported_extensions.is_empty() {
1274 let agi_extn: BTreeSet<_> = agi.extensions.iter().map(|s| s.as_str()).collect();
1275 let sup_extn: BTreeSet<_> =
1276 supported_extensions.iter().map(|s| s.id.as_str()).collect();
1277
1278 for sup_missing in agi_extn.difference(&sup_extn) {
1279 warn!(
1280 "Inconsistent supported extension descriptor {} in - {:?}, {:?}, {:?}",
1281 sup_missing, aaid, aaguid, attestation_certificate_key_identifiers
1282 );
1283 inconsistent_data = true;
1284 }
1285
1286 for agi_missing in sup_extn.difference(&agi_extn) {
1287 warn!(
1288 "Inconsistent authenticator_get_info extension descriptor {} in - {:?}, {:?}, {:?}",
1289 agi_missing,
1290 aaid, aaguid, attestation_certificate_key_identifiers
1291 );
1292 inconsistent_data = true;
1293 }
1294 }
1295 }
1296
1297 if let Some(aaguid) = aaguid.as_ref() {
1298 if authenticator_get_info.is_none() {
1299 warn!("FIDO2 Device missing authenticator info - {:?}", aaguid);
1300 invalid_metadata = true;
1301 }
1302 }
1303
1304 if invalid_metadata {
1305 return Err(());
1306 }
1307
1308 match (
1309 protocol_family,
1310 aaid,
1311 aaguid,
1312 attestation_certificate_key_identifiers,
1313 ) {
1314 (ProtocolFamily::Uaf, Some(aaid), None, _) => Ok(FidoDevice::Uaf(UAF {
1315 aaid,
1316 description,
1317 alternative_descriptions,
1318 authenticator_version,
1319 authentication_algorithms,
1320 public_key_alg_and_encodings,
1321 attestation_types,
1322 user_verification_details,
1323 key_protection,
1324 is_key_restricted,
1325 is_fresh_user_verification_required,
1326 attestation_root_certificates,
1327 ecdaa_trust_anchors,
1328 supported_extensions,
1329 authenticator_get_info,
1330 status_reports,
1331 time_of_last_status_change,
1332 })),
1333 (ProtocolFamily::Fido2, None, Some(aaguid), _) => Ok(FidoDevice::FIDO2(FIDO2 {
1334 aaguid,
1335 description,
1336 alternative_descriptions,
1337 authenticator_version,
1338 authentication_algorithms,
1339 public_key_alg_and_encodings,
1340 attestation_types,
1341 user_verification_details,
1342 key_protection,
1343 is_key_restricted,
1344 is_fresh_user_verification_required,
1345 attestation_root_certificates,
1346 ecdaa_trust_anchors,
1347 supported_extensions,
1348 authenticator_get_info,
1349 status_reports,
1350 time_of_last_status_change,
1351 inconsistent_data,
1352 patched_data,
1353 multi_device_credential_support,
1354 })),
1355 (ProtocolFamily::U2f, None, None, Some(aki)) => Ok(FidoDevice::U2F(U2F {
1356 attestation_certificate_key_identifiers: aki,
1357 description,
1358 alternative_descriptions,
1359 authenticator_version,
1360 authentication_algorithms,
1361 public_key_alg_and_encodings,
1362 attestation_types,
1363 user_verification_details,
1364 key_protection,
1365 is_key_restricted,
1366 is_fresh_user_verification_required,
1367 attestation_root_certificates,
1368 ecdaa_trust_anchors,
1369 supported_extensions,
1370 authenticator_get_info,
1371 status_reports,
1372 time_of_last_status_change,
1373 })),
1374 r => {
1375 warn!(
1376 "Invalid device aaid/aaguid, may not be a valid metadata statement {:?}",
1377 r
1378 );
1379 Err(())
1380 }
1381 }
1382 }
1383}
1384
1385#[derive(Debug, Clone)]
1387pub struct FidoMds {
1388 pub fido2: Vec<rc::Rc<FIDO2>>,
1391 pub uaf: Vec<UAF>,
1393 pub u2f: Vec<rc::Rc<U2F>>,
1395}
1396
1397impl From<RawFidoMds> for FidoMds {
1398 fn from(rawmds: RawFidoMds) -> Self {
1399 let mut fido2 = Vec::new();
1400 let mut uaf = Vec::new();
1401 let mut u2f = Vec::new();
1402
1403 rawmds
1404 .entries
1405 .into_iter()
1406 .filter_map(|device| device.try_into().ok())
1407 .for_each(|fd| match fd {
1408 FidoDevice::Uaf(dev) => uaf.push(dev),
1409 FidoDevice::U2F(dev) => {
1410 let dev = rc::Rc::new(dev);
1412
1413 u2f.push(dev);
1414 }
1415 FidoDevice::FIDO2(dev) => {
1416 let dev = rc::Rc::new(dev);
1417 fido2.push(dev)
1418 }
1419 });
1420
1421 fido2.sort_unstable_by(|a, b| {
1423 a.aaguid.cmp(&b.aaguid)
1425 });
1426 u2f.sort_unstable_by(|a, b| a.description.cmp(&b.description));
1427 uaf.sort_unstable_by(|a, b| a.description.cmp(&b.description));
1428
1429 FidoMds { fido2, uaf, u2f }
1430 }
1431}
1432
1433impl FromStr for FidoMds {
1434 type Err = JwtError;
1435
1436 fn from_str(s: &str) -> Result<Self, Self::Err> {
1437 RawFidoMds::from_str(s).map(|rawmds| rawmds.into())
1438 }
1439}
1440
1441impl FidoMds {
1442 pub fn fido2_query(&self, query: &Query) -> Option<Vec<rc::Rc<FIDO2>>> {
1443 debug!(?query);
1444
1445 let fds = self
1447 .fido2
1448 .iter()
1449 .filter(|fd| fd.query_match(query))
1450 .cloned()
1452 .collect::<Vec<rc::Rc<FIDO2>>>();
1453
1454 if fds.is_empty() {
1456 None
1457 } else {
1458 Some(fds)
1459 }
1460 }
1461
1462 pub fn fido2_to_attestation_ca_list(fds: &[rc::Rc<FIDO2>]) -> Option<AttestationCaList> {
1463 let mut att_ca_builder = AttestationCaListBuilder::new();
1464
1465 for fd in fds {
1466 for ca in fd.attestation_root_certificates.iter() {
1467 trace!(?fd);
1468
1469 att_ca_builder
1470 .insert_device_der(
1471 ca.as_slice(),
1472 fd.aaguid,
1473 fd.description.clone(),
1474 fd.alternative_descriptions.clone(),
1475 )
1476 .map_err(|err| {
1477 error!(?err, "Failed to add FIDO2 device to attestation ca list");
1478 })
1479 .ok()?;
1480 }
1481 }
1482
1483 Some(att_ca_builder.build())
1484 }
1485}