fido_mds/
lib.rs

1//! This library implements support to cryptographically verify, parse, validate and post-process
2//! the content of the FIDO Metadata Service. The FIDO Metadata Service acts like a "certificate
3//! transparency" registry, defining the certification state of hardware authenticators (such as
4//! Yubikeys, Windows Hello, Feitan and more). These Metadata describe the features, certification
5//! state, signing CA's and more about these devices.
6//!
7//! 2022-08-12 - FIDO's Metadata currently has a number of data entry errors - due to this, certain
8//! authenticator models will NOT be presented or listed when these errors are severe enough.
9
10#![deny(warnings)]
11#![warn(unused_extern_crates)]
12// #![warn(missing_docs)]
13#![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/// A status report for an authenticator. This describes the specific state of this device and
61/// it's FIDO certification status. The effective date acts as a publishing time, where if the
62/// effective date is `None` it is considered 'the latest report'.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum StatusReport {
65    /// This device is NOT certified by FIDO.
66    NotFidoCertified {
67        /// Date of the report. If `None` it is considered "up to date".
68        effective_date: Option<String>,
69        /// The exact or greater firmware version this relates to.
70        authenticator_version: u32,
71        /// A url describing the report.
72        url: Option<String>,
73    },
74    /// This device at the firmware version or lower can have user verification bypassed allowing
75    /// malware to access the device.
76    UserVerificationBypass {
77        /// Date of the report. If `None` it is considered "up to date".
78        effective_date: Option<String>,
79        /// The exact or lower firmware version this relates to.
80        authenticator_version: u32,
81        /// A url describing the report.
82        url: Option<String>,
83    },
84    /// The attestation key of this device has been compromised, allowing anyone to impersonate these
85    /// devices.
86    AttestationKeyCompromise {
87        /// Date of the report. If `None` it is considered "up to date".
88        effective_date: Option<String>,
89        /// The exact or greater firmware version this relates to.
90        authenticator_version: u32,
91        /// The Base64 DER encoded certificate that has been compromised. If `None` assume
92        /// all related certificates are compromised, and the device is untrustworthy.
93        certificate: Option<String>,
94        /// A url describing the report.
95        url: Option<String>,
96    },
97    /// The private keys of this device can be compromised by malware / software on a machine that
98    /// interacts with the device.
99    UserKeyRemoteCompromise {
100        /// Date of the report. If `None` it is considered "up to date".
101        effective_date: Option<String>,
102        /// The exact or greater firmware version this relates to.
103        authenticator_version: u32,
104        /// A url describing the report.
105        url: Option<String>,
106    },
107    /// The private keys of this device can be compromised and extracted by someone with physical
108    /// possession. This may or may not be a destructive process.
109    UserKeyPhysicalCompromise {
110        /// Date of the report. If `None` it is considered "up to date".
111        effective_date: Option<String>,
112        /// The exact or greater firmware version this relates to.
113        authenticator_version: u32,
114        /// A url describing the report.
115        url: Option<String>,
116    },
117    /// An update exists for this device. You *should* recommend that users update this device
118    /// before proceeding.
119    UpdateAvailable {
120        /// Date of the report. If `None` it is considered "up to date".
121        effective_date: Option<String>,
122        /// The exact or greater firmware version this relates to.
123        authenticator_version: u32,
124        /// A url describing the report.
125        url: Option<String>,
126    },
127    /// This device has had it's certification revoked and can not be trusted.
128    Revoked {
129        /// Date of the report. If `None` it is considered "up to date".
130        effective_date: Option<String>,
131        /// The exact or greater firmware version this relates to.
132        authenticator_version: u32,
133        /// A url describing the report.
134        url: Option<String>,
135    },
136    /// The vendor have submitted a self-completed compliance checklist, but FIDO have not
137    /// performed the certification themself. If in doubt, do not trust this device.
138    SelfAssertionSubmitted {
139        /// Date of the report. If `None` it is considered "up to date".
140        effective_date: Option<String>,
141        /// The exact or greater firmware version this relates to.
142        authenticator_version: u32,
143    },
144    /// This device has been certified by FIDO at Level 1
145    FidoCertified {
146        /// Date of the report. If `None` it is considered "up to date".
147        effective_date: Option<String>,
148        /// The exact or greater firmware version this relates to.
149        authenticator_version: Option<u32>,
150        /// A description of the authenticator
151        certification_descriptor: Option<String>,
152        /// FIDO Alliance Certificate Number
153        certificate_number: Option<String>,
154        /// Authenticator Certification Policy
155        certification_policy_version: Option<String>,
156        /// Security Requirements Version
157        certification_requirements_version: Option<String>,
158        /// A url describing the report.
159        url: Option<String>,
160    },
161    /// This device has been certified by FIDO at Level 1
162    FidoCertifiedL1 {
163        /// Date of the report. If `None` it is considered "up to date".
164        effective_date: Option<String>,
165        /// The exact or greater firmware version this relates to.
166        authenticator_version: Option<u32>,
167        /// A description of the authenticator
168        certification_descriptor: Option<String>,
169        /// FIDO Alliance Certificate Number
170        certificate_number: Option<String>,
171        /// Authenticator Certification Policy
172        certification_policy_version: Option<String>,
173        /// Security Requirements Version
174        certification_requirements_version: Option<String>,
175        /// A url describing the report.
176        url: Option<String>,
177    },
178    /// This device has been certified by FIDO at Level 1 Plus
179    FidoCertifiedL1Plus {
180        /// Date of the report. If `None` it is considered "up to date".
181        effective_date: Option<String>,
182        /// The exact or greater firmware version this relates to.
183        authenticator_version: Option<u32>,
184        /// A description of the authenticator
185        certification_descriptor: Option<String>,
186        /// FIDO Alliance Certificate Number
187        certificate_number: Option<String>,
188        /// Authenticator Certification Policy
189        certification_policy_version: Option<String>,
190        /// Security Requirements Version
191        certification_requirements_version: Option<String>,
192        /// A url describing the report.
193        url: Option<String>,
194    },
195    /// This device has been certified by FIDO at Level 2
196    FidoCertifiedL2 {
197        /// Date of the report. If `None` it is considered "up to date".
198        effective_date: Option<String>,
199        /// The exact or greater firmware version this relates to.
200        authenticator_version: Option<u32>,
201        /// A description of the authenticator
202        certification_descriptor: Option<String>,
203        /// FIDO Alliance Certificate Number
204        certificate_number: Option<String>,
205        /// Authenticator Certification Policy
206        certification_policy_version: Option<String>,
207        /// Security Requirements Version
208        certification_requirements_version: Option<String>,
209        /// A url describing the report.
210        url: Option<String>,
211    },
212    /// This device has been certified by FIDO at Level 2 Plus
213    FidoCertifiedL2Plus {
214        /// Date of the report. If `None` it is considered "up to date".
215        effective_date: Option<String>,
216        /// The exact or greater firmware version this relates to.
217        authenticator_version: Option<u32>,
218        /// A description of the authenticator
219        certification_descriptor: Option<String>,
220        /// FIDO Alliance Certificate Number
221        certificate_number: Option<String>,
222        /// Authenticator Certification Policy
223        certification_policy_version: Option<String>,
224        /// Security Requirements Version
225        certification_requirements_version: Option<String>,
226        /// A url describing the report.
227        url: Option<String>,
228    },
229    /// This device has been certified by FIDO at Level 3
230    FidoCertifiedL3 {
231        /// Date of the report. If `None` it is considered "up to date".
232        effective_date: Option<String>,
233        /// The exact or greater firmware version this relates to.
234        authenticator_version: Option<u32>,
235        /// A description of the authenticator
236        certification_descriptor: Option<String>,
237        /// FIDO Alliance Certificate Number
238        certificate_number: Option<String>,
239        /// Authenticator Certification Policy
240        certification_policy_version: Option<String>,
241        /// Security Requirements Version
242        certification_requirements_version: Option<String>,
243        /// A url describing the report.
244        url: Option<String>,
245    },
246    /// This device has been certified by FIDO at Level 3 Plus
247    FidoCertifiedL3Plus {
248        /// Date of the report. If `None` it is considered "up to date".
249        effective_date: Option<String>,
250        /// The exact or greater firmware version this relates to.
251        authenticator_version: Option<u32>,
252        /// A description of the authenticator
253        certification_descriptor: Option<String>,
254        /// FIDO Alliance Certificate Number
255        certificate_number: Option<String>,
256        /// Authenticator Certification Policy
257        certification_policy_version: Option<String>,
258        /// Security Requirements Version
259        certification_requirements_version: Option<String>,
260        /// A url describing the report.
261        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        // Looks nicer code style wise this way.
502        #[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    /// Retrieve the effective date of this report
550    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/// An identifier of a user verification method. Some methods may contain an internal descriptor
633/// which provides information about certification or details of the user verification method.
634#[derive(Debug, Clone, Hash, PartialEq)]
635pub enum UserVerificationMethod {
636    /// No user verification is required
637    None,
638    /// Physical interaction is required i.e. touching the device. The identity of whom touched
639    /// the device is NOT asserted, only that *someone* touched it.
640    PresenceInternal,
641    /// A passcode was entered internally to the device, i.e. a self contained PIN entry pad embedded
642    /// into the device.
643    PasscodeInternal(Option<CodeAccuracyDescriptor>),
644    /// A password was supplied to the device from an external source, i.e. a PIN entry dialog in
645    /// a browser, that then supplied the PIN to the device.
646    PasscodeExternal(Option<CodeAccuracyDescriptor>),
647    /// A fingerprint reader that is built into the device.
648    FingerprintInternal(Option<BiometricAccuracyDescriptor>),
649    /// A Handprint reader that is built into the device.
650    HandprintInternal(Option<BiometricAccuracyDescriptor>),
651    /// A Handprint reader that is built into the device.
652    EyeprintInternal(Option<BiometricAccuracyDescriptor>),
653    /// A Voiceprint reader that is built into the device.
654    VoiceprintInternal(Option<BiometricAccuracyDescriptor>),
655    /// A Faceprint reader that is built into the device.
656    FaceprintInternal(Option<BiometricAccuracyDescriptor>),
657    /// Unknown - No definition is available.
658    LocationInternal,
659    /// A pattern was entered internally to the device, i.e. a 3x3 grid of dots on a display that the
660    /// user traces over on a touch screen.
661    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            // RawUserVerificationMethod::All
774            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/// A metadata statement describing a UAF device.
790#[derive(Debug, Clone)]
791pub struct UAF {
792    /// The AAID that uniquely identifies this device.
793    pub aaid: String,
794    /// A description of the device in English
795    pub description: String,
796    /// Descriptions of the device, mapped from language to description.
797    pub alternative_descriptions: BTreeMap<String, String>,
798    /// The latest firmware version of the device.
799    pub authenticator_version: u32,
800    /// The supported cryptographic algorithms this device supports.
801    pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
802    /// The encoding of the devices public key when registered
803    pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
804    /// The types of attestation format that device may provide
805    pub attestation_types: Vec<AttestationType>,
806    /// A matrix of user verification methods this device supports. The outer matrix is
807    /// a list of `OR` methods, the inner list is `AND` methods. For example, consider:
808    ///
809    ///
810    /// [
811    ///     [
812    ///         { uvm: None }
813    ///     ],
814    ///     // OR
815    ///     [
816    ///         { uvm: PresenceInternal }
817    ///     ],
818    ///     // OR
819    ///     [
820    ///         { uvm: PresenceInternal },
821    ///         { uvm: PasscodeExternal },
822    ///     ],
823    /// ]
824    ///
825    ///
826    /// This is a common configuration found on many devices where it supports signatures with
827    /// no verification, signatures with touch-only, and signatures with touch and a passcode. These
828    /// bits are represented via the User Presence and User Verification booleans inside of the
829    /// attested credential data. Webauthn for example will always require at least presence.
830    pub user_verification_details: Vec<Vec<UserVerificationMethod>>,
831    /// The methods of supported private key protection this device supports.
832    pub key_protection: Vec<KeyProtection>,
833    /// If this device is restricted to only sign FIDO signature assertions. If `false` the device
834    /// may be used to sign any arbitrary data. If `true` the device may only be used with FIDO
835    /// (Webauthn) requests.
836    pub is_key_restricted: bool,
837    /// If `true` the device requires user verification for each operation it performs. If `false`
838    /// the device may cache the user verification for a short time. Consider a token that requires
839    /// a PIN - it may cache this for a small amount of time so that the user only requires presence.
840    pub is_fresh_user_verification_required: bool,
841    /// A list of DER root certificates that may have signed this model of authenticators attestation.
842    pub attestation_root_certificates: Vec<Vec<u8>>,
843    /// A list of ECDAA root anchors that may have signed this model of authenticators attestation.
844    pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
845    /// A list of extensions that this device supports.
846    pub supported_extensions: Vec<ExtensionDescriptor>,
847    /// If supported, the output of CTAP2.0+ authenticatorGetInfo command from a "factory new" device.
848    pub authenticator_get_info: Option<AuthenticatorGetInfo>,
849    /// A list of status reports about this device.
850    pub status_reports: BTreeSet<StatusReport>,
851    /// The time this device was last updated.
852    pub time_of_last_status_change: String,
853}
854
855/// A metadata statement describing a U2F device.
856#[derive(Debug, Clone)]
857pub struct U2F {
858    /// A list of attestation certificate keys that identify sub-models of this device.
859    pub attestation_certificate_key_identifiers: Vec<String>,
860    /// A description of the device in English
861    pub description: String,
862    /// Descriptions of the device, mapped from language to description.
863    pub alternative_descriptions: BTreeMap<String, String>,
864    /// The latest firmware version of the device.
865    pub authenticator_version: u32,
866    /// The supported cryptographic algorithms this device supports.
867    pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
868    /// The encoding of the devices public key when registered
869    pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
870    /// The types of attestation format that device may provide
871    pub attestation_types: Vec<AttestationType>,
872    /// A matrix of user verification methods this device supports. The outer matrix is
873    /// a list of `OR` methods, the inner list is `AND` methods. For example, consider:
874    ///
875    ///
876    /// [
877    ///     [
878    ///         { uvm: None }
879    ///     ],
880    ///     // OR
881    ///     [
882    ///         { uvm: PresenceInternal }
883    ///     ],
884    ///     // OR
885    ///     [
886    ///         { uvm: PresenceInternal },
887    ///         { uvm: PasscodeExternal },
888    ///     ],
889    /// ]
890    ///
891    ///
892    /// This is a common configuration found on many devices where it supports signatures with
893    /// no verification, signatures with touch-only, and signatures with touch and a passcode. These
894    /// bits are represented via the User Presence and User Verification booleans inside of the
895    /// attested credential data. Webauthn for example will always require at least presence.
896    pub user_verification_details: Vec<Vec<UserVerificationMethod>>,
897    /// The methods of supported private key protection this device supports.
898    pub key_protection: Vec<KeyProtection>,
899    /// If this device is restricted to only sign FIDO signature assertions. If `false` the device
900    /// may be used to sign any arbitrary data. If `true` the device may only be used with FIDO
901    /// (Webauthn) requests.
902    pub is_key_restricted: bool,
903    /// If `true` the device requires user verification for each operation it performs. If `false`
904    /// the device may cache the user verification for a short time. Consider a token that requires
905    /// a PIN - it may cache this for a small amount of time so that the user only requires presence.
906    pub is_fresh_user_verification_required: bool,
907    /// A list of DER root certificates that may have signed this model of authenticators attestation.
908    pub attestation_root_certificates: Vec<Vec<u8>>,
909    /// A list of ECDAA root anchors that may have signed this model of authenticators attestation.
910    pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
911    /// A list of extensions that this device supports.
912    pub supported_extensions: Vec<ExtensionDescriptor>,
913    /// If supported, the output of CTAP2.0+ authenticatorGetInfo command from a "factory new" device.
914    pub authenticator_get_info: Option<AuthenticatorGetInfo>,
915    /// A list of status reports about this device.
916    pub status_reports: BTreeSet<StatusReport>,
917    /// The time this device was last updated.
918    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/// A metadata statement describing a FIDO2 device.
928#[derive(Debug, Clone)]
929pub struct FIDO2 {
930    /// The AAGUID (UUID, Universally Unique IDentifier) that identifies this device.
931    pub aaguid: Uuid,
932    /// A description of the device in English
933    pub description: String,
934    /// Descriptions of the device, mapped from language to description.
935    pub alternative_descriptions: BTreeMap<String, String>,
936    /// The latest firmware version of the device.
937    pub authenticator_version: u32,
938    /// The supported cryptographic algorithms this device supports.
939    pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
940    /// The encoding of the devices public key when registered
941    pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
942    /// The types of attestation format that device may provide
943    pub attestation_types: Vec<AttestationType>,
944    /// A matrix of user verification methods this device supports. The outer matrix is
945    /// a list of `OR` methods, the inner list is `AND` methods. For example, consider:
946    ///
947    ///
948    /// [
949    ///     [
950    ///         { uvm: None }
951    ///     ],
952    ///     // OR
953    ///     [
954    ///         { uvm: PresenceInternal }
955    ///     ],
956    ///     // OR
957    ///     [
958    ///         { uvm: PresenceInternal },
959    ///         { uvm: PasscodeExternal },
960    ///     ],
961    /// ]
962    ///
963    ///
964    /// This is a common configuration found on many devices where it supports signatures with
965    /// no verification, signatures with touch-only, and signatures with touch and a passcode. These
966    /// bits are represented via the User Presence and User Verification booleans inside of the
967    /// attested credential data. Webauthn for example will always require at least presence.
968    pub user_verification_details: Vec<Vec<UserVerificationMethod>>,
969    /// The methods of supported private key protection this device supports.
970    pub key_protection: Vec<KeyProtection>,
971    /// If this device is restricted to only sign FIDO signature assertions. If `false` the device
972    /// may be used to sign any arbitrary data. If `true` the device may only be used with FIDO
973    /// (Webauthn) requests.
974    pub is_key_restricted: bool,
975    /// If `true` the device requires user verification for each operation it performs. If `false`
976    /// the device may cache the user verification for a short time. Consider a token that requires
977    /// a PIN - it may cache this for a small amount of time so that the user only requires presence.
978    pub is_fresh_user_verification_required: bool,
979    /// A list of DER root certificates that may have signed this model of authenticators attestation.
980    pub attestation_root_certificates: Vec<Vec<u8>>,
981    /// A list of ECDAA root anchors that may have signed this model of authenticators attestation.
982    pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
983    /// A list of extensions that this device supports.
984    pub supported_extensions: Vec<ExtensionDescriptor>,
985    /// If supported, the output of CTAP2.0+ authenticatorGetInfo command from a "factory new" device.
986    pub authenticator_get_info: Option<AuthenticatorGetInfo>,
987    /// A list of status reports about this device.
988    pub status_reports: BTreeSet<StatusReport>,
989    /// The time this device was last updated.
990    pub time_of_last_status_change: String,
991    /// These data as supplied from FIDO is inconsistent for this device, and may contain omissions
992    /// or errors. In some cases the webauthn-rs project has patched these data to correct these
993    /// which is indicated by the "patched" flag.
994    pub inconsistent_data: bool,
995    /// These data have been patched by the webauthn-rs project to repair flaws in the MDS that
996    /// are provided by FIDO. These patches are created by the project observing the device and
997    /// providing this.
998    pub patched_data: bool,
999    /// If the device supports multiple credentials
1000    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        // We deconstruct the MDS because there are a bunch of duplicate
1100        // types / values that we want to expose.
1101        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        // debug!("{:#?}", user_verification_details);
1200
1201        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                // No patching needed.
1224            }
1225            Ok(Some(mut uvm_patch)) => {
1226                // Patch provided
1227                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        // There are multiple devices that have no authenticator get info, and instead rely on
1249        // other fields in the metadata to do the work for them. In these cases, we should actually
1250        // make the AGI is None since it's only populated by the fido MDS and not a true mds.
1251
1252        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/// The set of parsed and validated FIDO Metadata
1385#[derive(Debug, Clone)]
1386pub struct FidoMds {
1387    /// The set of FIDO2 device metadata that exists within the Metadata Statement, indexed by their
1388    /// aaguid / uuid.
1389    pub fido2: Vec<rc::Rc<FIDO2>>,
1390    /// The set of (legacy) UAF device metadata that exists within the Metadata Statement.
1391    pub uaf: Vec<UAF>,
1392    /// The set of (legacy) U2f device metadata that exists within the Metadata Statement.
1393    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 akis = dev.attestation_certificate_key_identifiers.clone();
1410                    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        // Sort
1421        fido2.sort_unstable_by(|a, b| {
1422            // a.description.cmp(&b.description)
1423            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        // Iterate over the set of metadata.
1445        let fds = self
1446            .fido2
1447            .iter()
1448            .filter(|fd| fd.query_match(query))
1449            // This is cheap due to Rc,
1450            .cloned()
1451            .collect::<Vec<rc::Rc<FIDO2>>>();
1452
1453        // If != empty.
1454        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}