use crate::attestation::verify_attestation_ca_chain;
use crate::error::*;
pub use crate::internals::AttestationObject;
use std::fmt;
use webauthn_rs_proto::cose::*;
use webauthn_rs_proto::extensions::*;
use webauthn_rs_proto::options::*;
pub use webauthn_attestation_ca::*;
use base64urlsafedata::HumanBinaryData;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use openssl::{bn, ec, nid, pkey, 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: HumanBinaryData,
pub(crate) credential_algorithms: Vec<COSEAlgorithm>,
pub(crate) require_resident_key: bool,
pub(crate) authenticator_attachment: Option<AuthenticatorAttachment>,
pub(crate) extensions: RequestRegistrationExtensions,
pub(crate) allow_synchronised_authenticators: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthenticationState {
pub(crate) credentials: Vec<Credential>,
pub(crate) policy: UserVerificationPolicy,
pub(crate) challenge: HumanBinaryData,
pub(crate) appid: Option<String>,
pub(crate) allow_backup_eligible_upgrade: bool,
}
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,
}
impl EDDSACurve {
pub(crate) fn coordinate_size(&self) -> usize {
match self {
Self::ED25519 => 32,
Self::ED448 => 57,
}
}
}
#[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,
}
}
}
impl From<&ECDSACurve> for nid::Nid {
fn from(c: &ECDSACurve) -> Self {
use ECDSACurve::*;
match c {
SECP256R1 => nid::Nid::X9_62_PRIME256V1,
SECP384R1 => nid::Nid::SECP384R1,
SECP521R1 => nid::Nid::SECP521R1,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSEEC2Key {
pub curve: ECDSACurve,
pub x: HumanBinaryData,
pub y: HumanBinaryData,
}
impl TryFrom<&COSEEC2Key> for ec::EcKey<pkey::Public> {
type Error = openssl::error::ErrorStack;
fn try_from(k: &COSEEC2Key) -> Result<Self, Self::Error> {
let group = ec::EcGroup::from_curve_name((&k.curve).into())?;
let mut ctx = bn::BigNumContext::new()?;
let mut point = ec::EcPoint::new(&group)?;
let x = bn::BigNum::from_slice(k.x.as_slice())?;
let y = bn::BigNum::from_slice(k.y.as_slice())?;
point.set_affine_coordinates_gfp(&group, &x, &y, &mut ctx)?;
ec::EcKey::from_public_key(&group, &point)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSEOKPKey {
pub curve: EDDSACurve,
pub x: HumanBinaryData,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct COSERSAKey {
pub n: HumanBinaryData,
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,
}
pub type CredentialID = HumanBinaryData;
pub type Credential = CredentialV5;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CredentialV5 {
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 = false;
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: HumanBinaryData::from(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<HumanBinaryData>),
Self_,
AttCa(Vec<HumanBinaryData>),
AnonCa(Vec<HumanBinaryData>),
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<HumanBinaryData>,
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| HumanBinaryData::from(c.to_der().expect("Invalid DER")))
.collect(),
),
ParsedAttestationData::Self_ => SerialisableAttestationData::Self_,
ParsedAttestationData::AttCa(chain) => SerialisableAttestationData::AttCa(
chain
.into_iter()
.map(|c| HumanBinaryData::from(c.to_der().expect("Invalid DER")))
.collect(),
),
ParsedAttestationData::AnonCa(chain) => SerialisableAttestationData::AnonCa(
chain
.into_iter()
.map(|c| HumanBinaryData::from(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.as_slice()).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.as_slice()).map_err(WebauthnError::OpenSSLError)
})
.collect::<WebauthnResult<_>>()?,
),
SerialisableAttestationData::AnonCa(chain) => ParsedAttestationData::AnonCa(
chain
.into_iter()
.map(|c| {
x509::X509::from_der(c.as_slice()).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_2::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct AuthenticationSignedExtensions {
#[serde(flatten)]
pub unknown_keys: BTreeMap<String, serde_cbor_2::Value>,
}
#[derive(Debug, Clone)]
pub struct AttestedCredentialData {
pub aaguid: Aaguid,
pub credential_id: CredentialID,
pub credential_pk: serde_cbor_2::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) backup_eligible: 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 backup_eligible(&self) -> bool {
self.backup_eligible
}
pub fn counter(&self) -> Counter {
self.counter
}
pub fn extensions(&self) -> &AuthenticationExtensions {
&self.extensions
}
}