use crate::attestation::{verify_attestation_ca_chain, AttestationFormat};
use crate::constants::*;
use crate::error::*;
use std::fmt;
use webauthn_rs_proto::cose::*;
use webauthn_rs_proto::extensions::*;
use webauthn_rs_proto::options::*;
use base64urlsafedata::Base64UrlSafeData;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use openssl::x509;
use uuid::Uuid;
pub type Aaguid = [u8; 16];
pub type Counter = u32;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistrationState {
pub(crate) policy: UserVerificationPolicy,
pub(crate) exclude_credentials: Vec<CredentialID>,
pub(crate) challenge: Base64UrlSafeData,
pub(crate) credential_algorithms: Vec<COSEAlgorithm>,
pub(crate) require_resident_key: bool,
pub(crate) authenticator_attachment: Option<AuthenticatorAttachment>,
pub(crate) extensions: RequestRegistrationExtensions,
pub(crate) experimental_allow_passkeys: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthenticationState {
pub(crate) credentials: Vec<Credential>,
pub(crate) policy: UserVerificationPolicy,
pub(crate) challenge: Base64UrlSafeData,
pub(crate) appid: Option<String>,
}
impl AuthenticationState {
pub fn set_allowed_credentials(&mut self, credentials: Vec<Credential>) {
self.credentials = credentials;
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum EDDSACurve {
ED25519 = 6,
ED448 = 7,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ECDSACurve {
SECP256R1 = 1,
SECP384R1 = 2,
SECP521R1 = 3,
}
impl ECDSACurve {
pub(crate) fn coordinate_size(&self) -> usize {
match self {
Self::SECP256R1 => 32,
Self::SECP384R1 => 48,
Self::SECP521R1 => 66,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSEEC2Key {
pub curve: ECDSACurve,
pub x: Base64UrlSafeData,
pub y: Base64UrlSafeData,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSEOKPKey {
pub curve: EDDSACurve,
pub x: [u8; 32],
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSERSAKey {
pub n: Base64UrlSafeData,
pub e: [u8; 3],
}
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum COSEKeyType {
EC_OKP(COSEOKPKey),
EC_EC2(COSEEC2Key),
RSA(COSERSAKey),
}
#[allow(non_camel_case_types)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[repr(i64)]
pub enum COSEKeyTypeId {
EC_Reserved = 0,
EC_OKP = 1,
EC_EC2 = 2,
EC_RSA = 3,
EC_Symmetric = 4,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSEKey {
pub type_: COSEAlgorithm,
pub key: COSEKeyType,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Credential {
pub cred_id: CredentialID,
pub cred: COSEKey,
pub counter: Counter,
pub transports: Option<Vec<AuthenticatorTransport>>,
pub user_verified: bool,
pub backup_eligible: bool,
pub backup_state: bool,
pub registration_policy: UserVerificationPolicy,
pub extensions: RegisteredExtensions,
pub attestation: ParsedAttestation,
pub attestation_format: AttestationFormat,
}
impl Credential {
pub fn verify_attestation<'a>(
&'_ self,
ca_list: &'a AttestationCaList,
) -> Result<Option<&'a AttestationCa>, WebauthnError> {
let danger_disable_certificate_time_checks = true;
verify_attestation_ca_chain(
&self.attestation.data,
ca_list,
danger_disable_certificate_time_checks,
)
}
}
impl From<CredentialV3> for Credential {
fn from(other: CredentialV3) -> Credential {
let CredentialV3 {
cred_id,
cred,
counter,
verified,
registration_policy,
} = other;
Credential {
cred_id: Base64UrlSafeData(cred_id),
cred,
counter,
transports: None,
user_verified: verified,
backup_eligible: false,
backup_state: false,
registration_policy,
extensions: RegisteredExtensions::none(),
attestation: ParsedAttestation {
data: ParsedAttestationData::None,
metadata: AttestationMetadata::None,
},
attestation_format: AttestationFormat::None,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CredentialV3 {
pub cred_id: Vec<u8>,
pub cred: COSEKey,
pub counter: u32,
pub verified: bool,
pub registration_policy: UserVerificationPolicy,
}
#[derive(Clone, Serialize, Deserialize)]
pub enum SerialisableAttestationData {
Basic(Vec<Base64UrlSafeData>),
Self_,
AttCa(Vec<Base64UrlSafeData>),
AnonCa(Vec<Base64UrlSafeData>),
ECDAA,
None,
Uncertain,
}
impl fmt::Debug for SerialisableAttestationData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SerialisableAttestationData::Basic(_) => {
write!(f, "SerialisableAttestationData::Basic")
}
SerialisableAttestationData::Self_ => write!(f, "SerialisableAttestationData::Self_"),
SerialisableAttestationData::AttCa(_) => {
write!(f, "SerialisableAttestationData::AttCa")
}
SerialisableAttestationData::AnonCa(_) => {
write!(f, "SerialisableAttestationData::AnonCa")
}
SerialisableAttestationData::ECDAA => write!(f, "SerialisableAttestationData::ECDAA"),
SerialisableAttestationData::None => write!(f, "SerialisableAttestationData::None"),
SerialisableAttestationData::Uncertain => {
write!(f, "SerialisableAttestationData::Uncertain")
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ParsedAttestation {
pub data: ParsedAttestationData,
pub metadata: AttestationMetadata,
}
impl Default for ParsedAttestation {
fn default() -> Self {
ParsedAttestation {
data: ParsedAttestationData::None,
metadata: AttestationMetadata::None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum AttestationMetadata {
None,
Packed {
aaguid: Uuid,
},
Tpm {
aaguid: Uuid,
firmware_version: u64,
},
AndroidKey {
is_km_tee: bool,
is_attest_tee: bool,
},
AndroidSafetyNet {
apk_package_name: String,
apk_certificate_digest_sha256: Vec<Base64UrlSafeData>,
cts_profile_match: bool,
basic_integrity: bool,
evaluation_type: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(
try_from = "SerialisableAttestationData",
into = "SerialisableAttestationData"
)]
pub enum ParsedAttestationData {
Basic(Vec<x509::X509>),
Self_,
AttCa(Vec<x509::X509>),
AnonCa(Vec<x509::X509>),
ECDAA,
None,
Uncertain,
}
#[allow(clippy::from_over_into)]
impl Into<SerialisableAttestationData> for ParsedAttestationData {
fn into(self) -> SerialisableAttestationData {
match self {
ParsedAttestationData::Basic(chain) => SerialisableAttestationData::Basic(
chain
.into_iter()
.map(|c| Base64UrlSafeData(c.to_der().expect("Invalid DER")))
.collect(),
),
ParsedAttestationData::Self_ => SerialisableAttestationData::Self_,
ParsedAttestationData::AttCa(chain) => SerialisableAttestationData::AttCa(
chain
.into_iter()
.map(|c| Base64UrlSafeData(c.to_der().expect("Invalid DER")))
.collect(),
),
ParsedAttestationData::AnonCa(chain) => SerialisableAttestationData::AnonCa(
chain
.into_iter()
.map(|c| Base64UrlSafeData(c.to_der().expect("Invalid DER")))
.collect(),
),
ParsedAttestationData::ECDAA => SerialisableAttestationData::ECDAA,
ParsedAttestationData::None => SerialisableAttestationData::None,
ParsedAttestationData::Uncertain => SerialisableAttestationData::Uncertain,
}
}
}
impl TryFrom<SerialisableAttestationData> for ParsedAttestationData {
type Error = WebauthnError;
fn try_from(data: SerialisableAttestationData) -> Result<Self, Self::Error> {
Ok(match data {
SerialisableAttestationData::Basic(chain) => ParsedAttestationData::Basic(
chain
.into_iter()
.map(|c| x509::X509::from_der(&c.0).map_err(WebauthnError::OpenSSLError))
.collect::<WebauthnResult<_>>()?,
),
SerialisableAttestationData::Self_ => ParsedAttestationData::Self_,
SerialisableAttestationData::AttCa(chain) => ParsedAttestationData::AttCa(
chain
.into_iter()
.map(|c| x509::X509::from_der(&c.0).map_err(WebauthnError::OpenSSLError))
.collect::<WebauthnResult<_>>()?,
),
SerialisableAttestationData::AnonCa(chain) => ParsedAttestationData::AnonCa(
chain
.into_iter()
.map(|c| x509::X509::from_der(&c.0).map_err(WebauthnError::OpenSSLError))
.collect::<WebauthnResult<_>>()?,
),
SerialisableAttestationData::ECDAA => ParsedAttestationData::ECDAA,
SerialisableAttestationData::None => ParsedAttestationData::None,
SerialisableAttestationData::Uncertain => ParsedAttestationData::Uncertain,
})
}
}
#[derive(Debug)]
pub struct Registration;
#[derive(Debug)]
pub struct Authentication;
pub trait Ceremony {
type SignedExtensions: DeserializeOwned + std::fmt::Debug + std::default::Default;
}
impl Ceremony for Registration {
type SignedExtensions = RegistrationSignedExtensions;
}
impl Ceremony for Authentication {
type SignedExtensions = AuthenticationSignedExtensions;
}
#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(try_from = "u8", into = "u8")]
pub struct CredProtectResponse(pub CredentialProtectionPolicy);
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RegistrationSignedExtensions {
#[serde(rename = "credProtect")]
pub cred_protect: Option<CredProtectResponse>,
#[serde(rename = "hmac-secret")]
pub hmac_secret: Option<bool>,
#[serde(flatten)]
pub unknown_keys: BTreeMap<String, serde_cbor::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticationSignedExtensions {
#[serde(flatten)]
pub unknown_keys: BTreeMap<String, serde_cbor::Value>,
}
#[derive(Debug, Clone)]
pub(crate) struct AttestedCredentialData {
pub(crate) aaguid: Aaguid,
pub(crate) credential_id: CredentialID,
pub(crate) credential_pk: serde_cbor::Value,
}
#[derive(Debug, Serialize, Clone, Deserialize)]
pub struct AuthenticationResult {
pub(crate) cred_id: CredentialID,
pub(crate) needs_update: bool,
pub(crate) user_verified: bool,
pub(crate) backup_state: bool,
pub(crate) counter: Counter,
pub(crate) extensions: AuthenticationExtensions,
}
impl AuthenticationResult {
pub fn cred_id(&self) -> &CredentialID {
&self.cred_id
}
pub fn needs_update(&self) -> bool {
self.needs_update
}
pub fn user_verified(&self) -> bool {
self.user_verified
}
pub fn backup_state(&self) -> bool {
self.backup_state
}
pub fn counter(&self) -> Counter {
self.counter
}
pub fn extensions(&self) -> &AuthenticationExtensions {
&self.extensions
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SerialisableAttestationCa {
pub(crate) ca: Base64UrlSafeData,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(
try_from = "SerialisableAttestationCa",
into = "SerialisableAttestationCa"
)]
pub struct AttestationCa {
pub ca: x509::X509,
}
#[allow(clippy::from_over_into)]
impl Into<SerialisableAttestationCa> for AttestationCa {
fn into(self) -> SerialisableAttestationCa {
SerialisableAttestationCa {
ca: Base64UrlSafeData(self.ca.to_der().expect("Invalid DER")),
}
}
}
impl TryFrom<SerialisableAttestationCa> for AttestationCa {
type Error = WebauthnError;
fn try_from(data: SerialisableAttestationCa) -> Result<Self, Self::Error> {
Ok(AttestationCa {
ca: x509::X509::from_der(&data.ca.0).map_err(WebauthnError::OpenSSLError)?,
})
}
}
impl AttestationCa {
pub fn new_from_der(data: &[u8]) -> Result<Self, WebauthnError> {
Ok(AttestationCa {
ca: x509::X509::from_der(data).map_err(WebauthnError::OpenSSLError)?,
})
}
pub fn apple_webauthn_root_ca() -> Self {
AttestationCa {
ca: x509::X509::from_pem(APPLE_WEBAUTHN_ROOT_CA_PEM).expect("Invalid DER"),
}
}
pub fn yubico_u2f_root_ca_serial_457200631() -> Self {
AttestationCa {
ca: x509::X509::from_pem(YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM).expect("Invalid DER"),
}
}
pub fn microsoft_tpm_root_certificate_authority_2014() -> Self {
AttestationCa {
ca: x509::X509::from_pem(MICROSOFT_TPM_ROOT_CERTIFICATE_AUTHORITY_2014_PEM)
.expect("Invalid DER"),
}
}
pub fn nitrokey_fido2_root_ca() -> Self {
AttestationCa {
ca: x509::X509::from_pem(NITROKEY_FIDO2_ROOT_CA_PEM).expect("Invalid DER"),
}
}
pub fn nitrokey_u2f_root_ca() -> Self {
AttestationCa {
ca: x509::X509::from_pem(NITROKEY_U2F_ROOT_CA_PEM).expect("Invalid DER"),
}
}
pub fn android_root_ca_1() -> Self {
AttestationCa {
ca: x509::X509::from_pem(ANDROID_ROOT_CA_1).expect("Invalid DER"),
}
}
pub fn android_root_ca_2() -> Self {
AttestationCa {
ca: x509::X509::from_pem(ANDROID_ROOT_CA_2).expect("Invalid DER"),
}
}
pub fn android_root_ca_3() -> Self {
AttestationCa {
ca: x509::X509::from_pem(ANDROID_ROOT_CA_3).expect("Invalid DER"),
}
}
pub fn android_software_ca() -> Self {
AttestationCa {
ca: x509::X509::from_pem(ANDROID_SOFTWARE_ROOT_CA).expect("Invalid DER"),
}
}
pub fn google_safetynet_ca() -> Self {
AttestationCa {
ca: x509::X509::from_pem(GOOGLE_SAFETYNET_CA).expect("Invalid DER"),
}
}
#[allow(unused)]
pub(crate) fn google_safetynet_ca_old() -> Self {
AttestationCa {
ca: x509::X509::from_pem(GOOGLE_SAFETYNET_CA_OLD).expect("Invalid DER"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttestationCaList {
pub cas: Vec<AttestationCa>,
}
impl AttestationCaList {
pub fn is_empty(&self) -> bool {
self.cas.is_empty()
}
pub fn strict() -> Self {
AttestationCaList {
cas: vec![AttestationCa::yubico_u2f_root_ca_serial_457200631()],
}
}
pub fn apple_and_android() -> Self {
AttestationCaList {
cas: vec![
AttestationCa::apple_webauthn_root_ca(),
AttestationCa::android_root_ca_1(),
AttestationCa::android_root_ca_2(),
AttestationCa::android_root_ca_3(),
AttestationCa::google_safetynet_ca(),
AttestationCa::android_software_ca(),
],
}
}
pub fn apple() -> Self {
AttestationCaList {
cas: vec![AttestationCa::apple_webauthn_root_ca()],
}
}
pub fn all_known_cas() -> Self {
AttestationCaList {
cas: vec![
AttestationCa::apple_webauthn_root_ca(),
AttestationCa::yubico_u2f_root_ca_serial_457200631(),
AttestationCa::microsoft_tpm_root_certificate_authority_2014(),
AttestationCa::nitrokey_fido2_root_ca(),
AttestationCa::nitrokey_u2f_root_ca(),
AttestationCa::android_root_ca_1(),
AttestationCa::android_root_ca_2(),
AttestationCa::android_root_ca_3(),
AttestationCa::google_safetynet_ca(),
AttestationCa::android_software_ca(),
],
}
}
}