use coset::iana;
use indexmap::IndexMap;
use serde::{Deserialize, Serialize, Serializer};
use std::fmt;
#[cfg(feature = "typeshare")]
use typeshare::typeshare;
use crate::{
Bytes,
utils::serde::{
i64_to_iana, ignore_unknown, ignore_unknown_opt_vec, ignore_unknown_vec,
maybe_stringified_bool, maybe_stringified_num,
},
webauthn::{
AuthenticationExtensionsClientInputs, AuthenticatorAttachment, AuthenticatorTransport,
PublicKeyCredential, PublicKeyCredentialDescriptor, PublicKeyCredentialHints,
PublicKeyCredentialType, UserVerificationRequirement,
},
};
#[cfg(doc)]
use crate::{
ctap2::{Aaguid, AttestedCredentialData, AuthenticatorData},
webauthn::AuthenticatorAssertionResponse,
};
#[cfg_attr(feature = "typeshare", typeshare(swift = "Equatable, Hashable"))]
pub type CreatedPublicKeyCredential = PublicKeyCredential<AuthenticatorAttestationResponse>;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typeshare", typeshare)]
pub struct CredentialCreationOptions {
pub public_key: PublicKeyCredentialCreationOptions,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typeshare", typeshare)]
pub struct PublicKeyCredentialCreationOptions {
pub rp: PublicKeyCredentialRpEntity,
pub user: PublicKeyCredentialUserEntity,
pub challenge: Bytes,
#[serde(deserialize_with = "ignore_unknown_vec")]
pub pub_key_cred_params: Vec<PublicKeyCredentialParameters>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "maybe_stringified_num"
)]
pub timeout: Option<u32>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown_opt_vec"
)]
pub exclude_credentials: Option<Vec<PublicKeyCredentialDescriptor>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub authenticator_selection: Option<AuthenticatorSelectionCriteria>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown_opt_vec"
)]
pub hints: Option<Vec<PublicKeyCredentialHints>>,
#[serde(default, deserialize_with = "ignore_unknown")]
pub attestation: AttestationConveyancePreference,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown_opt_vec"
)]
pub attestation_formats: Option<Vec<AttestationStatementFormatIdentifiers>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extensions: Option<AuthenticationExtensionsClientInputs>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "typeshare", typeshare)]
pub struct PublicKeyCredentialRpEntity {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
pub name: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typeshare", typeshare)]
pub struct PublicKeyCredentialUserEntity {
pub id: Bytes,
pub display_name: String,
pub name: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "typeshare", typeshare)]
pub struct PublicKeyCredentialParameters {
#[serde(rename = "type", deserialize_with = "ignore_unknown")]
pub ty: PublicKeyCredentialType,
#[serde(with = "i64_to_iana")]
#[cfg_attr(feature = "typeshare", typeshare(serialized_as = "I54"))]
pub alg: iana::Algorithm,
}
impl PublicKeyCredentialParameters {
pub fn default_algorithms() -> Vec<Self> {
vec![
Self {
ty: PublicKeyCredentialType::PublicKey,
alg: iana::Algorithm::ES256,
},
Self {
ty: PublicKeyCredentialType::PublicKey,
alg: iana::Algorithm::RS256,
},
]
}
}
impl From<iana::Algorithm> for PublicKeyCredentialParameters {
fn from(value: iana::Algorithm) -> Self {
Self {
ty: PublicKeyCredentialType::PublicKey,
alg: value,
}
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typeshare", typeshare)]
pub struct AuthenticatorSelectionCriteria {
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown",
default
)]
pub authenticator_attachment: Option<AuthenticatorAttachment>,
#[serde(
skip_serializing_if = "Option::is_none",
deserialize_with = "ignore_unknown",
default
)]
pub resident_key: Option<ResidentKeyRequirement>,
#[serde(default, deserialize_with = "maybe_stringified_bool")]
pub require_resident_key: bool,
#[serde(default, deserialize_with = "ignore_unknown")]
pub user_verification: UserVerificationRequirement,
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "typeshare", typeshare(serialized_as = "String"))]
pub enum ResidentKeyRequirement {
Discouraged,
Preferred,
Required,
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "typeshare", typeshare(serialized_as = "String"))]
pub enum AttestationConveyancePreference {
#[default]
None,
Indirect,
Direct,
Enterprise,
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[cfg_attr(feature = "typeshare", typeshare)]
pub enum AttestationStatementFormatIdentifiers {
Packed,
Tpm,
AndroidKey,
AndroidSafetynet,
FidoU2f,
Apple,
#[default]
None,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "typeshare", typeshare(swift = "Equatable, Hashable"))]
pub struct AuthenticatorAttestationResponse {
#[serde(rename = "clientDataJSON")]
pub client_data_json: Bytes,
pub authenticator_data: Bytes,
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key: Option<Bytes>,
#[cfg_attr(feature = "typeshare", typeshare(serialized_as = "I54"))]
pub public_key_algorithm: i64,
pub attestation_object: Bytes,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transports: Option<Vec<AuthenticatorTransport>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CollectedClientData<E = ()>
where
E: Clone + Serialize,
{
#[serde(rename = "type")]
pub ty: ClientDataType,
pub challenge: String,
pub origin: String,
#[serde(default, serialize_with = "truthiness")]
pub cross_origin: Option<bool>,
#[serde(flatten)]
pub extra_data: E,
#[serde(flatten)]
pub unknown_keys: IndexMap<String, serde_json::value::Value>,
}
fn truthiness<S>(cross_origin: &Option<bool>, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
ser.serialize_bool(cross_origin.filter(|b| *b).is_some())
}
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "typeshare", typeshare)]
pub enum ClientDataType {
#[serde(rename = "webauthn.create")]
Create,
#[serde(rename = "webauthn.get")]
Get,
#[serde(rename = "payment.get")]
PaymentGet,
}
impl fmt::Display for ClientDataType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let renamed = serde_json::to_string(self).unwrap();
write!(f, "{}", renamed.trim_matches('"'))
}
}
#[cfg(test)]
mod tests;