You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.5 KiB
Rust
201 lines
7.5 KiB
Rust
use crate::{
|
|
attestation,
|
|
credential::{BackupEligibility, BackupState},
|
|
extensions, Sha256Hash,
|
|
};
|
|
use std::collections::BTreeMap;
|
|
|
|
#[cfg(feature = "serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[cfg(feature = "serde")]
|
|
use bitflags::bitflags;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum UserPresence {
|
|
Present,
|
|
NotPresent,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum UserVerification {
|
|
Verified,
|
|
NotVerified,
|
|
}
|
|
|
|
/// > The authenticator data structure encodes contextual bindings made by the
|
|
/// > authenticator. These bindings are controlled by the authenticator itself,
|
|
/// > and derive their trust from the `WebAuthn` Relying Party's assessment of
|
|
/// > the security properties of the authenticator. In one extreme case, the
|
|
/// > authenticator may be embedded in the client, and its bindings may be no
|
|
/// > more trustworthy than the client data. At the other extreme, the
|
|
/// > authenticator may be a discrete entity with high-security hardware and
|
|
/// > software, connected to the client over a secure channel. In both cases,
|
|
/// > the Relying Party receives the authenticator data in the same format, and
|
|
/// > uses its knowledge of the authenticator to make trust decisions.
|
|
#[derive(Debug)]
|
|
pub struct Data {
|
|
/// > SHA-256 hash of the RP ID the credential is scoped to.
|
|
pub relying_party_id_hash: Sha256Hash,
|
|
pub user_presence: UserPresence,
|
|
pub user_verification: UserVerification,
|
|
pub backup_eligibility: BackupEligibility,
|
|
pub backup_state: BackupState,
|
|
pub signature_counter: u32,
|
|
pub attested_credential_data: Option<attestation::CredentialData>,
|
|
pub extensions: Option<BTreeMap<extensions::Identifier, Vec<u8>>>,
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
bitflags! {
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
// > Flags (bit 0 is the least significant bit):
|
|
struct DataFlags: u8 {
|
|
// > Bit 0: User Present (UP) result.
|
|
// > 1 means the user is present.
|
|
const USER_PRESENCE = 0b1 << 0;
|
|
// > Bit 2: User Verified (UV) result.
|
|
// > 1 means the user is verified.
|
|
const USER_VERIFIED = 0b1 << 2;
|
|
// > Bit 3: Backup Eligibility (BE).
|
|
// > 1 means the public key credential source is backup eligible.
|
|
const BACKUP_ELIGIBLE = 0b1 << 3;
|
|
// > Bit 4: Backup State (BS).
|
|
// > 1 means the public key credential source is currently backed up.
|
|
const BACKUP_STATE = 0b1 << 4;
|
|
// > Bit 6: Attested credential data included (AT).
|
|
// > Indicates whether the authenticator added attested credential data.
|
|
const ATTESTED_CREDENTIAL_DATA = 0b1 << 6;
|
|
// > Bit 7: Extension data included (ED).
|
|
// > Indicates if the authenticator data has extensions.
|
|
const EXTENSION_DATA_INCLUDED = 0b1 << 7;
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl DataFlags {
|
|
fn user_presence(&self) -> UserPresence {
|
|
if self.contains(DataFlags::USER_PRESENCE) {
|
|
UserPresence::Present
|
|
} else {
|
|
UserPresence::NotPresent
|
|
}
|
|
}
|
|
|
|
fn user_verification(&self) -> UserVerification {
|
|
if self.contains(DataFlags::USER_VERIFIED) {
|
|
UserVerification::Verified
|
|
} else {
|
|
UserVerification::NotVerified
|
|
}
|
|
}
|
|
|
|
fn backup_eligibility(&self) -> BackupEligibility {
|
|
if self.contains(DataFlags::BACKUP_ELIGIBLE) {
|
|
BackupEligibility::Eligible
|
|
} else {
|
|
BackupEligibility::Ineligible
|
|
}
|
|
}
|
|
|
|
fn backup_state(&self) -> BackupState {
|
|
if self.contains(DataFlags::BACKUP_STATE) {
|
|
BackupState::BackedUp
|
|
} else {
|
|
BackupState::NotBackedUp
|
|
}
|
|
}
|
|
|
|
fn has_attested_credential_data(&self) -> bool {
|
|
self.contains(DataFlags::ATTESTED_CREDENTIAL_DATA)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de> Deserialize<'de> for Data {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
use serde::de;
|
|
|
|
let data = Vec::<u8>::deserialize(deserializer)?;
|
|
|
|
// The authenticator data structure is a byte array of 37 bytes or more
|
|
if data.len() < 37 {
|
|
return Err(de::Error::invalid_length(data.len(), &"at least 37 bytes"));
|
|
}
|
|
|
|
// SAFETY: split_array_ref panics if const param is out of bounds for slice.
|
|
// data.len() guard protects against out of bounds indicies.
|
|
|
|
// rpIdHash: 32 Bytes
|
|
// > SHA-256 hash of the RP ID the credential is scoped to.
|
|
let (&relying_party_id_hash, data): (&Sha256Hash, _) = data.split_array_ref::<32>();
|
|
|
|
// flags: 1 Byte
|
|
let (&[flags], data): (&[u8; 1], _) = data.split_array_ref::<1>();
|
|
let flags = DataFlags::from_bits_truncate(flags);
|
|
|
|
// signCount: 4 Bytes
|
|
// > Signature counter, 32-bit unsigned big-endian integer.
|
|
let (&counter_be_bytes, data) = data.split_array_ref::<4>();
|
|
let signature_counter = u32::from_be_bytes(counter_be_bytes);
|
|
|
|
let attested_credential_data: Option<attestation::CredentialData> =
|
|
if flags.has_attested_credential_data() {
|
|
Some(attestation::CredentialData::try_from(data).map_err(de::Error::custom)?)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
Ok(Self {
|
|
relying_party_id_hash,
|
|
user_presence: flags.user_presence(),
|
|
user_verification: flags.user_verification(),
|
|
backup_eligibility: flags.backup_eligibility(),
|
|
backup_state: flags.backup_state(),
|
|
signature_counter,
|
|
attested_credential_data,
|
|
extensions: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// > Authenticators may implement various transports for communicating with
|
|
/// > clients. This enumeration defines hints as to how clients might
|
|
/// > communicate with a particular authenticator in order to obtain an
|
|
/// > assertion for a specific credential. Note that these hints represent the
|
|
/// > `WebAuthn` Relying Party's best belief as to how an authenticator may be
|
|
/// > reached. A Relying Party will typically learn of the supported transports
|
|
/// > for a public key credential via getTransports().
|
|
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(untagged))]
|
|
pub enum Transport {
|
|
/// > Indicates the respective authenticator can be contacted over removable
|
|
/// > USB.
|
|
#[cfg_attr(feature = "serde", serde(rename = "usb"))]
|
|
Usb,
|
|
/// > Indicates the respective authenticator can be contacted over Near
|
|
/// > Field Communication (NFC).
|
|
#[cfg_attr(feature = "serde", serde(rename = "nfc"))]
|
|
Nfc,
|
|
/// > Indicates the respective authenticator can be contacted over Bluetooth
|
|
/// > Smart (Bluetooth Low Energy / BLE).
|
|
#[cfg_attr(feature = "serde", serde(rename = "ble"))]
|
|
Ble,
|
|
/// > Indicates the respective authenticator can be contacted using a
|
|
/// > combination of (often separate) data-transport and proximity
|
|
/// > mechanisms. This supports, for example, authentication on a desktop
|
|
/// > computer using a smartphone.
|
|
#[cfg_attr(feature = "serde", serde(rename = "hybrid"))]
|
|
Hybrid,
|
|
/// > Indicates the respective authenticator is contacted using a client
|
|
/// > device-specific transport, i.e., it is a platform authenticator. These
|
|
/// > authenticators are not removable from the client device.
|
|
#[cfg_attr(feature = "serde", serde(rename = "internal"))]
|
|
Internal,
|
|
// TODO: Serialize as contents of string
|
|
Unknown(String),
|
|
}
|