use compact_jwt::{
crypto::{Certificate, DecodePem, JwsX509VerifierBuilder},
JwsCompact, JwsVerifier, JwtError,
};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use std::time::SystemTime;
use uuid::Uuid;
static GLOBAL_SIGN_ROOT_CA_R3: &str = r#"
-----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
WD9f
-----END CERTIFICATE-----
"#;
fn assume_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct Upv {
pub major: u16,
pub minor: u16,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Hash)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct CodeAccuracyDescriptor {
pub base: u16,
pub min_length: u16,
pub max_retries: Option<u16>,
pub block_slowdown: Option<u16>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct BiometricAccuracyDescriptor {
#[serde(rename = "selfAttestedFRR")]
pub self_attested_frr: Option<f32>,
#[serde(rename = "selfAttestedFAR")]
pub self_attested_far: Option<f32>,
pub max_templates: Option<u16>,
pub max_retries: Option<u16>,
pub block_slowdown: Option<u16>,
#[serde(rename = "iAPARThreshold")]
pub iapar_threshold: Option<serde_json::Value>,
}
impl Hash for BiometricAccuracyDescriptor {
fn hash<H: Hasher>(&self, state: &mut H) {
self.self_attested_frr
.map(|f| f.to_le_bytes())
.unwrap_or([0; 4])
.hash(state);
self.self_attested_far
.map(|f| f.to_le_bytes())
.unwrap_or([0; 4])
.hash(state);
self.max_templates.hash(state);
self.max_retries.hash(state);
self.block_slowdown.hash(state);
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct PatternAccuracyDescriptor {
pub min_complexity: serde_json::Value,
pub max_retries: Option<u16>,
pub block_slowdown: Option<u16>,
}
impl Hash for PatternAccuracyDescriptor {
fn hash<H: Hasher>(&self, state: &mut H) {
self.max_retries
.map(|f| f.to_le_bytes())
.unwrap_or([0; 2])
.hash(state);
self.block_slowdown
.map(|f| f.to_le_bytes())
.unwrap_or([0; 2])
.hash(state);
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum UserVerificationMethod {
#[serde(rename = "none")]
None,
#[serde(rename = "all")]
All,
#[serde(rename = "presence_internal")]
PresenceInternal,
#[serde(rename = "passcode_internal")]
PasscodeInternal,
#[serde(rename = "passcode_external")]
PasscodeExternal,
#[serde(rename = "fingerprint_internal")]
FingerprintInternal,
#[serde(rename = "handprint_internal")]
HandprintInternal,
#[serde(rename = "eyeprint_internal")]
EyeprintInternal,
#[serde(rename = "pattern_internal")]
PatternInternal,
#[serde(rename = "voiceprint_internal")]
VoiceprintInternal,
#[serde(rename = "location_internal")]
LocationInternal,
#[serde(rename = "faceprint_internal")]
FaceprintInternal,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct VerificationMethodAndCombinations {
pub user_verification_method: UserVerificationMethod,
pub ca_desc: Option<CodeAccuracyDescriptor>,
pub ba_desc: Option<BiometricAccuracyDescriptor>,
pub pa_desc: Option<PatternAccuracyDescriptor>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct EcdaaAnchor {
#[serde(rename = "X")]
pub x: String,
#[serde(rename = "Y")]
pub y: String,
pub c: String,
pub sx: String,
pub sy: String,
#[serde(rename = "G1Curve")]
pub g1curve: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExtensionDescriptor {
pub id: String,
pub tag: Option<u16>,
pub data: Option<String>,
pub fail_if_unknown: bool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AssertionScheme {
#[default]
Unknown,
#[serde(rename = "FIDOV2")]
FidoV2,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub enum ProtocolFamily {
#[default]
Unknown,
#[serde(rename = "uaf")]
Uaf,
#[serde(rename = "u2f")]
U2f,
#[serde(rename = "fido2")]
Fido2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AuthenticationAlgorithm {
#[serde(rename = "secp256r1_ecdsa_sha256_raw")]
Secp256r1EcdsaSha256Raw,
#[serde(rename = "secp256r1_ecdsa_sha256_der")]
Secp256r1EcdsaSha256Der,
#[serde(rename = "secp256k1_ecdsa_sha256_raw")]
Secp256K1EcdsaSha256Raw,
#[serde(rename = "rsa_emsa_pkcs1_sha256_raw")]
RsaEmsaPkcs1Sha256Raw,
#[serde(rename = "ed25519_eddsa_sha512_raw")]
Ed25519EddsaSha512Raw,
#[serde(rename = "secp384r1_ecdsa_sha384_raw")]
Secp384r1EcdsaSha384Raw,
#[serde(rename = "secp521r1_ecdsa_sha512_raw")]
Secp521r1EcdsaSha512Raw,
#[serde(rename = "rsassa_pkcsv15_sha256_raw")]
RsassaPkcsv15Sha256Raw,
#[serde(rename = "rsassa_pkcsv15_sha1_raw")]
RsassaPkcsv15Sha1Raw,
#[serde(rename = "rsassa_pss_sha256_raw")]
RsassaPssSha256Raw,
}
impl fmt::Display for AuthenticationAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthenticationAlgorithm::Secp256r1EcdsaSha256Raw => {
write!(f, "secp256r1_ecdsa_sha256_raw")
}
AuthenticationAlgorithm::Secp256r1EcdsaSha256Der => {
write!(f, "secp256r1_ecdsa_sha256_der")
}
AuthenticationAlgorithm::Secp256K1EcdsaSha256Raw => {
write!(f, "secp256k1_ecdsa_sha256_raw")
}
AuthenticationAlgorithm::RsaEmsaPkcs1Sha256Raw => {
write!(f, "rsa_emsa_pkcs1_sha256_raw")
}
AuthenticationAlgorithm::Ed25519EddsaSha512Raw => write!(f, "ed25519_eddsa_sha512_raw"),
AuthenticationAlgorithm::Secp384r1EcdsaSha384Raw => {
write!(f, "secp384r1_ecdsa_sha384_raw")
}
AuthenticationAlgorithm::Secp521r1EcdsaSha512Raw => {
write!(f, "secp521r1_ecdsa_sha512_raw")
}
AuthenticationAlgorithm::RsassaPkcsv15Sha256Raw => {
write!(f, "rsassa_pkcsv15_sha256_raw")
}
AuthenticationAlgorithm::RsassaPkcsv15Sha1Raw => {
write!(f, "rsassa_pkcsv15_sha1_raw")
}
AuthenticationAlgorithm::RsassaPssSha256Raw => {
write!(f, "rsassa_pss_sha256_raw")
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum PublicKeyAlg {
#[serde(rename = "ecc_x962_raw")]
EccX962Raw,
#[serde(rename = "ecc_x962_der")]
EccX962Der,
#[serde(rename = "rsa_2048_raw")]
Rsa2048Raw,
#[serde(rename = "cose")]
Cose,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AttestationType {
#[serde(rename = "basic_full")]
BasicFull,
#[serde(rename = "basic_surrogate")]
BasicSurrogate,
#[serde(rename = "attca")]
AttCa,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum KeyProtection {
#[serde(rename = "hardware")]
Hardware,
#[serde(rename = "secure_element")]
SecureElement,
#[serde(rename = "remote_handle")]
RemoteHandle,
#[serde(rename = "tee")]
Tee,
#[serde(rename = "software")]
Software,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum MatcherProtection {
#[serde(rename = "on_chip")]
OnChip,
#[serde(rename = "tee")]
Tee,
#[serde(rename = "software")]
Software,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum AttachmentHint {
#[serde(rename = "external")]
External,
#[serde(rename = "wired")]
Wired,
#[serde(rename = "wireless")]
Wireless,
#[serde(rename = "nfc")]
Nfc,
#[serde(rename = "internal")]
Internal,
#[serde(rename = "bluetooth")]
Bluetooth,
#[serde(rename = "network")]
Network,
#[serde(rename = "wifi_direct")]
WifiDirect,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AuthenticatorVersion {
#[serde(rename = "U2F_V2")]
U2fV2,
#[serde(rename = "FIDO_2_0")]
Fido2_0,
#[serde(rename = "FIDO_2_1_PRE")]
Fido2_1Pre,
#[serde(rename = "FIDO_2_1")]
Fido2_1,
}
impl fmt::Display for AuthenticatorVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthenticatorVersion::U2fV2 => write!(f, "U2F V2"),
AuthenticatorVersion::Fido2_0 => write!(f, "FIDO 2.0"),
AuthenticatorVersion::Fido2_1Pre => write!(f, "FIDO 2.1 PRE"),
AuthenticatorVersion::Fido2_1 => write!(f, "FIDO 2.1"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AuthenticatorTransport {
#[serde(rename = "usb")]
Usb,
#[serde(rename = "nfc")]
Nfc,
#[serde(rename = "lightning")]
Lightning,
#[serde(rename = "ble")]
Ble,
#[serde(rename = "internal")]
Internal,
#[serde(rename = "wireless")]
Wireless,
#[serde(rename = "hybrid")]
Hybrid,
}
impl fmt::Display for AuthenticatorTransport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthenticatorTransport::Usb => write!(f, "usb"),
AuthenticatorTransport::Nfc => write!(f, "nfc"),
AuthenticatorTransport::Lightning => write!(f, "lightning"),
AuthenticatorTransport::Ble => write!(f, "ble"),
AuthenticatorTransport::Internal => write!(f, "internal"),
AuthenticatorTransport::Wireless => write!(f, "wireless"),
AuthenticatorTransport::Hybrid => write!(f, "hybrid (caBLE)"),
}
}
}
impl FromStr for AuthenticatorTransport {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"usb" => Ok(AuthenticatorTransport::Usb),
"nfc" => Ok(AuthenticatorTransport::Nfc),
"lightning" => Ok(AuthenticatorTransport::Lightning),
"ble" => Ok(AuthenticatorTransport::Ble),
"internal" => Ok(AuthenticatorTransport::Internal),
"wireless" => Ok(AuthenticatorTransport::Wireless),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum MultiDeviceCredentialSupport {
#[default]
Unsupported,
Explicit,
Implicit,
}
impl fmt::Display for MultiDeviceCredentialSupport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unsupported => write!(f, "unsupported (no)"),
Self::Explicit => write!(f, "explicit (see backup_eligible flag)"),
Self::Implicit => write!(f, "implicit (yes)"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct AuthenticatorGetInfo {
pub versions: Vec<AuthenticatorVersion>,
#[serde(default)]
pub extensions: Vec<String>,
pub aaguid: Uuid,
#[serde(default)]
pub options: BTreeMap<String, bool>,
pub max_msg_size: Option<u32>,
#[serde(default)]
pub pin_uv_auth_protocols: Vec<u32>,
pub max_credential_count_in_list: Option<u32>,
pub max_credential_id_length: Option<u32>,
#[serde(default)]
pub transports: Vec<AuthenticatorTransport>,
#[serde(default)]
pub(crate) algorithms: Vec<serde_json::Value>,
pub max_serialized_large_blob_array: Option<u32>,
#[serde(rename = "forcePINChange")]
force_pin_change: Option<bool>,
#[serde(rename = "minPINLength")]
pub min_pin_length: Option<u32>,
firmware_version: Option<u32>,
pub max_cred_blob_length: Option<u32>,
#[serde(rename = "maxRPIDsForSetMinPINLength")]
pub max_rpids_for_set_min_pin_length: Option<u32>,
pub preferred_platform_uv_attempts: Option<u32>,
uv_modality: Option<u32>,
#[serde(default)]
pub certifications: BTreeMap<String, u32>,
pub remaining_discoverable_credentials: Option<u32>,
#[serde(default)]
pub vendor_prototype_config_commands: Vec<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct MetadataStatement {
pub legal_header: Option<String>,
pub aaid: Option<String>,
pub aaguid: Option<Uuid>,
pub attestation_certificate_key_identifiers: Option<Vec<String>>,
pub description: String,
#[serde(default)]
pub alternative_descriptions: BTreeMap<String, String>,
pub authenticator_version: u32,
#[serde(default)]
pub protocol_family: ProtocolFamily,
pub schema: u16,
pub upv: Vec<Upv>,
#[serde(default)]
pub authentication_algorithms: Vec<AuthenticationAlgorithm>,
#[serde(default)]
pub public_key_alg_and_encodings: Vec<PublicKeyAlg>,
pub attestation_types: Vec<AttestationType>,
pub user_verification_details: Vec<Vec<VerificationMethodAndCombinations>>,
pub key_protection: Vec<KeyProtection>,
#[serde(default = "assume_true")]
pub is_key_restricted: bool,
#[serde(default = "assume_true")]
pub is_fresh_user_verification_required: bool,
pub matcher_protection: Vec<MatcherProtection>,
pub crypto_strength: Option<u16>,
pub attachment_hint: Vec<AttachmentHint>,
pub tc_display: Vec<String>,
pub tc_display_content_type: Option<String>,
#[serde(rename = "tcDisplayPNGCharacteristics")]
pub tc_display_png_characteristics: Option<serde_json::Value>,
pub attestation_root_certificates: Vec<String>,
#[serde(default)]
pub ecdaa_trust_anchors: Vec<EcdaaAnchor>,
pub icon: Option<serde_json::Value>,
#[serde(default)]
pub supported_extensions: Vec<ExtensionDescriptor>,
pub authenticator_get_info: Option<AuthenticatorGetInfo>,
#[serde(default)]
pub multi_device_credential_support: MultiDeviceCredentialSupport,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum BiometricModality {
#[default]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct BiometricsStatusReport {
pub cert_level: u16,
#[serde(default)]
pub modality: BiometricModality,
pub effective_date: Option<String>,
pub certification_descriptor: Option<String>,
pub certificate_number: Option<String>,
pub certification_policy_version: Option<String>,
pub certification_requirements_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum AuthenticatorStatus {
#[serde(rename = "NOT_FIDO_CERTIFIED")]
NotFidoCertified,
#[serde(rename = "FIDO_CERTIFIED")]
FidoCertified,
#[serde(rename = "USER_VERIFICATION_BYPASS")]
UserVerificationBypass,
#[serde(rename = "ATTESTATION_KEY_COMPROMISE")]
AttestationKeyCompromise,
#[serde(rename = "USER_KEY_REMOTE_COMPROMISE")]
UserKeyRemoteCompromise,
#[serde(rename = "USER_KEY_PHYSICAL_COMPROMISE")]
UserKeyPhysicalCompromise,
#[serde(rename = "UPDATE_AVAILABLE")]
UpdateAvailable,
#[serde(rename = "REVOKED")]
Revoked,
#[serde(rename = "SELF_ASSERTION_SUBMITTED")]
SelfAssertionSubmitted,
#[serde(rename = "FIDO_CERTIFIED_L1")]
FidoCertifiedL1,
#[serde(rename = "FIDO_CERTIFIED_L1plus")]
FidoCertifiedL1Plus,
#[serde(rename = "FIDO_CERTIFIED_L2")]
FidoCertifiedL2,
#[serde(rename = "FIDO_CERTIFIED_L2plus")]
FidoCertifiedL2Plus,
#[serde(rename = "FIDO_CERTIFIED_L3")]
FidoCertifiedL3,
#[serde(rename = "FIDO_CERTIFIED_L3plus")]
FidoCertifiedL3Plus,
}
impl FromStr for AuthenticatorStatus {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"not-certified" => Ok(AuthenticatorStatus::NotFidoCertified),
"uv-bypass" => Ok(AuthenticatorStatus::UserVerificationBypass),
"key-compromise" => Ok(AuthenticatorStatus::AttestationKeyCompromise),
"remote-exploit" => Ok(AuthenticatorStatus::UserKeyRemoteCompromise),
"physical-compromise" => Ok(AuthenticatorStatus::UserKeyPhysicalCompromise),
"update-available" => Ok(AuthenticatorStatus::UpdateAvailable),
"revoked" => Ok(AuthenticatorStatus::Revoked),
"self-asserted" => Ok(AuthenticatorStatus::SelfAssertionSubmitted),
"valid" | "l1" => Ok(AuthenticatorStatus::FidoCertifiedL1),
"l1+" => Ok(AuthenticatorStatus::FidoCertifiedL1Plus),
"l2" => Ok(AuthenticatorStatus::FidoCertifiedL2),
"l2+" => Ok(AuthenticatorStatus::FidoCertifiedL2Plus),
"l3" => Ok(AuthenticatorStatus::FidoCertifiedL3),
"l3+" => Ok(AuthenticatorStatus::FidoCertifiedL3Plus),
_ => Err(()),
}
}
}
impl AuthenticatorStatus {
pub(crate) fn numeric(&self) -> u8 {
match self {
AuthenticatorStatus::NotFidoCertified
| AuthenticatorStatus::UserVerificationBypass
| AuthenticatorStatus::AttestationKeyCompromise
| AuthenticatorStatus::UserKeyRemoteCompromise
| AuthenticatorStatus::UserKeyPhysicalCompromise
| AuthenticatorStatus::UpdateAvailable
| AuthenticatorStatus::Revoked
| AuthenticatorStatus::SelfAssertionSubmitted => 0,
AuthenticatorStatus::FidoCertified | AuthenticatorStatus::FidoCertifiedL1 => 10,
AuthenticatorStatus::FidoCertifiedL1Plus => 11,
AuthenticatorStatus::FidoCertifiedL2 => 20,
AuthenticatorStatus::FidoCertifiedL2Plus => 21,
AuthenticatorStatus::FidoCertifiedL3 => 30,
AuthenticatorStatus::FidoCertifiedL3Plus => 31,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct StatusReport {
pub status: AuthenticatorStatus,
pub effective_date: Option<String>,
pub authenticator_version: Option<u32>,
pub certificate: Option<String>,
pub url: Option<String>,
pub certification_descriptor: Option<String>,
pub certificate_number: Option<String>,
pub certification_policy_version: Option<String>,
pub certification_requirements_version: Option<String>,
certification_profiles: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct FidoDevice {
pub aaid: Option<String>,
pub aaguid: Option<Uuid>,
pub attestation_certificate_key_identifiers: Option<Vec<String>>,
pub metadata_statement: MetadataStatement,
#[serde(default)]
pub biometric_status_reports: Vec<BiometricsStatusReport>,
pub status_reports: Vec<StatusReport>,
pub time_of_last_status_change: String, #[serde(rename = "rogueListURL")]
pub rogue_list_url: Option<String>,
pub rogue_list_hash: Option<String>,
}
impl fmt::Display for FidoDevice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = serde_json::to_string_pretty(self).map_err(|_| fmt::Error)?;
write!(f, "FidoDevice {s}")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct FidoMds {
pub entries: Vec<FidoDevice>,
pub legal_header: String,
pub next_update: String,
pub no: u32,
}
impl FromStr for FidoMds {
type Err = JwtError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let root_ca = Certificate::from_pem(GLOBAL_SIGN_ROOT_CA_R3.as_bytes())
.map_err(|_| JwtError::CryptoError)?;
let jws = JwsCompact::from_str(s)?;
let (leaf, chain) = jws
.get_x5c_chain()
.and_then(|chain| chain.ok_or(JwtError::InvalidHeaderFormat))?;
let now = SystemTime::now();
let verifier = JwsX509VerifierBuilder::new(&leaf, &chain)
.add_trust_root(root_ca)
.build(now)
.map_err(|_| JwtError::CryptoError)?;
let released = verifier.verify(&jws)?;
let metadata: FidoMds = released.from_json().map_err(|serde_err| {
tracing::error!(?serde_err);
JwtError::Serde
})?;
Ok(metadata)
}
}