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 authenticator_version,
1110 protocol_family,
1111 schema: _,
1112 authentication_algorithms,
1113 public_key_alg_and_encodings,
1114 attestation_types,
1115 mut user_verification_details,
1116 key_protection,
1117 is_key_restricted,
1118 is_fresh_user_verification_required,
1119 matcher_protection: _,
1120 crypto_strength: _,
1121 attachment_hint: _,
1122 tc_display: _,
1123 tc_display_content_type: _,
1124 tc_display_png_characteristics: _,
1125 attestation_root_certificates,
1126 ecdaa_trust_anchors,
1127 icon: _,
1128 supported_extensions,
1129 mut authenticator_get_info,
1130 multi_device_credential_support,
1131 } = metadata_statement;
1132
1133 let mut status_reports: BTreeSet<_> = status_reports
1134 .into_iter()
1135 .filter_map(|sr| {
1136 sr.try_into()
1137 .map_err(|_| {
1138 warn!(
1139 "Invalid Status Report located in: {:?}, {:?}, {:?}",
1140 aaid, aaguid, attestation_certificate_key_identifiers
1141 );
1142 invalid_metadata = true;
1143 })
1144 .ok()
1145 })
1146 .collect();
1147
1148 if let Some(status_report) = patch::mds_deny_insecure_authenticators(aaguid) {
1149 status_reports.insert(status_report);
1150 }
1151
1152 let attestation_root_certificates = attestation_root_certificates.into_iter()
1153 .filter_map(|cert| {
1154 let trim_cert = cert.trim();
1155 if trim_cert != cert {
1156 warn!(
1157 "Invalid attestation root certificate - leading/trailing whitespace: {:?}, {:?}, {:?}",
1158 aaid, aaguid, attestation_certificate_key_identifiers
1159 );
1160 inconsistent_data = true;
1161 None
1162 } else {
1163 match STANDARD.decode(trim_cert) {
1164 Ok(der) => Some(der),
1165 Err(e) => {
1166 warn!(
1167 "Invalid attestation root certificate - invalid base64 {:?} : {:?}, {:?}, {:?}",
1168 e,
1169 aaid, aaguid, attestation_certificate_key_identifiers
1170 );
1171 invalid_metadata = true;
1172 None
1173 }
1174 }
1175 }
1176 })
1177 .collect();
1178
1179 if patch::mds_user_verification_method_code_accuracy_descriptor(
1180 &mut user_verification_details,
1181 ) {
1182 info!(
1183 "Device was patched for invalid code accuracy descriptior on presence: {:?}, {:?}, {:?}",
1184 aaid, aaguid, attestation_certificate_key_identifiers
1185 );
1186 inconsistent_data = true;
1187 patched_data = true;
1188 }
1189
1190 if patch::mds_user_verification_method_invalid_all_present(&mut user_verification_details) {
1191 info!(
1192 "Device was patched for uvm 'all', which violates fido's standards: {:?}, {:?}, {:?}",
1193 aaid, aaguid, attestation_certificate_key_identifiers
1194 );
1195 inconsistent_data = true;
1196 patched_data = true;
1197 }
1198
1199 let mut user_verification_details: Vec<Vec<_>> = user_verification_details.into_iter()
1202 .map(|inner| {
1203 inner.into_iter()
1204 .filter_map(|uvm| {
1205 uvm.try_into()
1206 .map_err(|_e| {
1207 warn!(
1208 "Invalid user verification details located in: {:?}, {:?}, {:?}",
1209 aaid, aaguid, attestation_certificate_key_identifiers
1210 );
1211 assert!(aaguid.is_none());
1212 invalid_metadata = true;
1213 })
1214 .ok()
1215
1216 })
1217 .collect()
1218 })
1219 .collect();
1220
1221 match patch::user_verification_method(aaguid, &user_verification_details) {
1222 Ok(None) => {
1223 }
1225 Ok(Some(mut uvm_patch)) => {
1226 inconsistent_data = true;
1228 patched_data = true;
1229 std::mem::swap(&mut uvm_patch, &mut user_verification_details)
1230 }
1231 Err(_e) => {
1232 error!("Unable to patch user verification methods. This is a bug and should be reported. https://github.com/kanidm/webauthn-rs/issues");
1233 }
1234 }
1235
1236 for uvm_and in user_verification_details.iter() {
1237 if uvm_and.contains(&UserVerificationMethod::None) && uvm_and.len() != 1 {
1238 debug!(?user_verification_details);
1239 debug!(?description);
1240 warn!(
1241 "Illogical user verification method located in - None may not exist with other UVM: {:?}, {:?}, {:?}",
1242 aaid, aaguid, attestation_certificate_key_identifiers
1243 );
1244 invalid_metadata = true;
1245 }
1246 }
1247
1248 let agi_invalid = if let Some(agi) = authenticator_get_info.as_ref() {
1253 agi.extensions.is_empty()
1254 && agi.pin_uv_auth_protocols.is_empty()
1255 && agi.transports.is_empty()
1256 && agi.algorithms.is_empty()
1257 } else {
1258 false
1259 };
1260
1261 if agi_invalid {
1262 authenticator_get_info = None;
1263 info!(
1264 "Device was patched for invalid authenticator get info that was not collected from a real device: {:?}, {:?}, {:?}",
1265 aaid, aaguid, attestation_certificate_key_identifiers
1266 );
1267 patched_data = true;
1268 inconsistent_data = true;
1269 }
1270
1271 if let Some(agi) = authenticator_get_info.as_ref() {
1272 if !supported_extensions.is_empty() {
1273 let agi_extn: BTreeSet<_> = agi.extensions.iter().map(|s| s.as_str()).collect();
1274 let sup_extn: BTreeSet<_> =
1275 supported_extensions.iter().map(|s| s.id.as_str()).collect();
1276
1277 for sup_missing in agi_extn.difference(&sup_extn) {
1278 warn!(
1279 "Inconsistent supported extension descriptor {} in - {:?}, {:?}, {:?}",
1280 sup_missing, aaid, aaguid, attestation_certificate_key_identifiers
1281 );
1282 inconsistent_data = true;
1283 }
1284
1285 for agi_missing in sup_extn.difference(&agi_extn) {
1286 warn!(
1287 "Inconsistent authenticator_get_info extension descriptor {} in - {:?}, {:?}, {:?}",
1288 agi_missing,
1289 aaid, aaguid, attestation_certificate_key_identifiers
1290 );
1291 inconsistent_data = true;
1292 }
1293 }
1294 }
1295
1296 if let Some(aaguid) = aaguid.as_ref() {
1297 if authenticator_get_info.is_none() {
1298 warn!("FIDO2 Device missing authenticator info - {:?}", aaguid);
1299 invalid_metadata = true;
1300 }
1301 }
1302
1303 if invalid_metadata {
1304 return Err(());
1305 }
1306
1307 match (
1308 protocol_family,
1309 aaid,
1310 aaguid,
1311 attestation_certificate_key_identifiers,
1312 ) {
1313 (ProtocolFamily::Uaf, Some(aaid), None, _) => Ok(FidoDevice::Uaf(UAF {
1314 aaid,
1315 description,
1316 alternative_descriptions,
1317 authenticator_version,
1318 authentication_algorithms,
1319 public_key_alg_and_encodings,
1320 attestation_types,
1321 user_verification_details,
1322 key_protection,
1323 is_key_restricted,
1324 is_fresh_user_verification_required,
1325 attestation_root_certificates,
1326 ecdaa_trust_anchors,
1327 supported_extensions,
1328 authenticator_get_info,
1329 status_reports,
1330 time_of_last_status_change,
1331 })),
1332 (ProtocolFamily::Fido2, None, Some(aaguid), _) => Ok(FidoDevice::FIDO2(FIDO2 {
1333 aaguid,
1334 description,
1335 alternative_descriptions,
1336 authenticator_version,
1337 authentication_algorithms,
1338 public_key_alg_and_encodings,
1339 attestation_types,
1340 user_verification_details,
1341 key_protection,
1342 is_key_restricted,
1343 is_fresh_user_verification_required,
1344 attestation_root_certificates,
1345 ecdaa_trust_anchors,
1346 supported_extensions,
1347 authenticator_get_info,
1348 status_reports,
1349 time_of_last_status_change,
1350 inconsistent_data,
1351 patched_data,
1352 multi_device_credential_support,
1353 })),
1354 (ProtocolFamily::U2f, None, None, Some(aki)) => Ok(FidoDevice::U2F(U2F {
1355 attestation_certificate_key_identifiers: aki,
1356 description,
1357 alternative_descriptions,
1358 authenticator_version,
1359 authentication_algorithms,
1360 public_key_alg_and_encodings,
1361 attestation_types,
1362 user_verification_details,
1363 key_protection,
1364 is_key_restricted,
1365 is_fresh_user_verification_required,
1366 attestation_root_certificates,
1367 ecdaa_trust_anchors,
1368 supported_extensions,
1369 authenticator_get_info,
1370 status_reports,
1371 time_of_last_status_change,
1372 })),
1373 r => {
1374 warn!(
1375 "Invalid device aaid/aaguid, may not be a valid metadata statement {:?}",
1376 r
1377 );
1378 Err(())
1379 }
1380 }
1381 }
1382}
1383
1384#[derive(Debug, Clone)]
1386pub struct FidoMds {
1387 pub fido2: Vec<rc::Rc<FIDO2>>,
1390 pub uaf: Vec<UAF>,
1392 pub u2f: Vec<rc::Rc<U2F>>,
1394}
1395
1396impl From<RawFidoMds> for FidoMds {
1397 fn from(rawmds: RawFidoMds) -> Self {
1398 let mut fido2 = Vec::new();
1399 let mut uaf = Vec::new();
1400 let mut u2f = Vec::new();
1401
1402 rawmds
1403 .entries
1404 .into_iter()
1405 .filter_map(|device| device.try_into().ok())
1406 .for_each(|fd| match fd {
1407 FidoDevice::Uaf(dev) => uaf.push(dev),
1408 FidoDevice::U2F(dev) => {
1409 let dev = rc::Rc::new(dev);
1411
1412 u2f.push(dev);
1413 }
1414 FidoDevice::FIDO2(dev) => {
1415 let dev = rc::Rc::new(dev);
1416 fido2.push(dev)
1417 }
1418 });
1419
1420 fido2.sort_unstable_by(|a, b| {
1422 a.aaguid.cmp(&b.aaguid)
1424 });
1425 u2f.sort_unstable_by(|a, b| a.description.cmp(&b.description));
1426 uaf.sort_unstable_by(|a, b| a.description.cmp(&b.description));
1427
1428 FidoMds { fido2, uaf, u2f }
1429 }
1430}
1431
1432impl FromStr for FidoMds {
1433 type Err = JwtError;
1434
1435 fn from_str(s: &str) -> Result<Self, Self::Err> {
1436 RawFidoMds::from_str(s).map(|rawmds| rawmds.into())
1437 }
1438}
1439
1440impl FidoMds {
1441 pub fn fido2_query(&self, query: &Query) -> Option<Vec<rc::Rc<FIDO2>>> {
1442 debug!(?query);
1443
1444 let fds = self
1446 .fido2
1447 .iter()
1448 .filter(|fd| fd.query_match(query))
1449 .cloned()
1451 .collect::<Vec<rc::Rc<FIDO2>>>();
1452
1453 if fds.is_empty() {
1455 None
1456 } else {
1457 Some(fds)
1458 }
1459 }
1460
1461 pub fn fido2_to_attestation_ca_list(fds: &[rc::Rc<FIDO2>]) -> Option<AttestationCaList> {
1462 let mut att_ca_builder = AttestationCaListBuilder::new();
1463
1464 for fd in fds {
1465 for ca in fd.attestation_root_certificates.iter() {
1466 trace!(?fd);
1467
1468 att_ca_builder
1469 .insert_device_der(
1470 ca.as_slice(),
1471 fd.aaguid,
1472 fd.description.clone(),
1473 fd.alternative_descriptions.clone(),
1474 )
1475 .map_err(|err| {
1476 error!(?err, "Failed to add FIDO2 device to attestation ca list");
1477 })
1478 .ok()?;
1479 }
1480 }
1481
1482 Some(att_ca_builder.build())
1483 }
1484}