use serde::{Deserialize, Serialize};
use crate::{ctap2::AuthenticatorData, webauthn, Bytes};
#[cfg(doc)]
use {
crate::webauthn::{
CollectedClientData, PublicKeyCredentialCreationOptions, PublicKeyCredentialDescriptor,
},
ciborium::value::Value,
};
use super::extensions::{AuthenticatorPrfInputs, AuthenticatorPrfMakeOutputs, HmacGetSecretInput};
serde_workaround! {
#[derive(Debug)]
pub struct Request {
#[serde(rename = 0x01)]
pub client_data_hash: Bytes,
#[serde(rename = 0x02)]
pub rp: PublicKeyCredentialRpEntity,
#[serde(rename = 0x03)]
pub user: webauthn::PublicKeyCredentialUserEntity,
#[serde(rename = 0x04)]
pub pub_key_cred_params: Vec<webauthn::PublicKeyCredentialParameters>,
#[serde(rename = 0x05, default, skip_serializing_if = Option::is_none)]
pub exclude_list: Option<Vec<webauthn::PublicKeyCredentialDescriptor>>,
#[serde(rename = 0x06, default, skip_serializing_if = Option::is_none)]
pub extensions: Option<ExtensionInputs>,
#[serde(rename = 0x07, default)]
pub options: Options,
#[serde(rename = 0x08, default, skip_serializing_if = Option::is_none)]
pub pin_auth: Option<Bytes>,
#[serde(rename = 0x09, default, skip_serializing_if = Option::is_none)]
pub pin_protocol: Option<u8>,
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PublicKeyCredentialRpEntity {
pub id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PublicKeyCredentialUserEntity {
pub id: Bytes,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub icon_url: Option<String>,
}
impl From<webauthn::PublicKeyCredentialUserEntity> for PublicKeyCredentialUserEntity {
fn from(value: webauthn::PublicKeyCredentialUserEntity) -> Self {
Self {
id: value.id,
name: Some(value.name),
display_name: Some(value.display_name),
icon_url: None,
}
}
}
impl TryFrom<PublicKeyCredentialUserEntity> for webauthn::PublicKeyCredentialUserEntity {
type Error = &'static str;
fn try_from(value: PublicKeyCredentialUserEntity) -> Result<Self, Self::Error> {
match (value.name, value.display_name) {
(Some(name), Some(display_name)) => {
Ok(Self {
id: value.id,
name,
display_name,
})
},
_ => Err("PublicKeyCredentialUserEntity is missing one or more required fields: name, display_name"),
}
}
}
#[non_exhaustive]
#[derive(Debug)]
pub struct MissingRpId {
pub rp_name: String,
}
impl TryFrom<webauthn::PublicKeyCredentialRpEntity> for PublicKeyCredentialRpEntity {
type Error = MissingRpId;
fn try_from(value: webauthn::PublicKeyCredentialRpEntity) -> Result<Self, Self::Error> {
if let Some(id) = value.id {
Ok(Self {
id,
name: Some(value.name),
})
} else {
Err(MissingRpId {
rp_name: value.name,
})
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Options {
#[serde(default)]
pub rk: bool,
#[serde(default = "default_true")]
pub up: bool,
#[serde(default)]
pub uv: bool,
}
impl Default for Options {
fn default() -> Self {
Self {
rk: false,
up: true,
uv: false,
}
}
}
const fn default_true() -> bool {
true
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct ExtensionInputs {
#[serde(
rename = "hmac-secret",
default,
skip_serializing_if = "Option::is_none"
)]
pub hmac_secret: Option<bool>,
#[serde(
rename = "hmac-secret-mc",
default,
skip_serializing_if = "Option::is_none"
)]
pub hmac_secret_mc: Option<HmacGetSecretInput>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub prf: Option<AuthenticatorPrfInputs>,
}
impl ExtensionInputs {
pub fn zip_contents(self) -> Option<Self> {
let Self {
hmac_secret,
hmac_secret_mc,
prf,
} = &self;
let has_hmac_secret = hmac_secret.is_some();
let has_hmac_secret_mc = hmac_secret_mc.is_some();
let has_prf = prf.is_some();
(has_hmac_secret || has_hmac_secret_mc || has_prf).then_some(self)
}
}
serde_workaround! {
#[derive(Debug)]
pub struct Response {
#[serde(rename = 0x01)]
pub fmt: String,
#[serde(rename = 0x02)]
pub auth_data: AuthenticatorData,
#[serde(rename = 0x03)]
pub att_stmt: ciborium::value::Value,
#[serde(rename = 0x04, default, skip_serializing_if = Option::is_none)]
pub ep_att: Option<bool>,
#[serde(rename = 0x05, default, skip_serializing_if = Option::is_none)]
pub large_blob_key: Option<Bytes>,
#[serde(rename = 0x06, default, skip_serializing_if = Option::is_none)]
pub unsigned_extension_outputs: Option<UnsignedExtensionOutputs>,
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SignedExtensionOutputs {
#[serde(
rename = "hmac-secret",
default,
skip_serializing_if = "Option::is_none"
)]
pub hmac_secret: Option<bool>,
#[serde(
rename = "hmac-secret-mc",
default,
skip_serializing_if = "Option::is_none"
)]
pub hmac_secret_mc: Option<Bytes>,
}
impl SignedExtensionOutputs {
pub fn zip_contents(self) -> Option<Self> {
let Self {
hmac_secret,
hmac_secret_mc,
} = &self;
let has_hmac_secret = hmac_secret.is_some();
let has_hmac_secret_mc = hmac_secret_mc.is_some();
(has_hmac_secret || has_hmac_secret_mc).then_some(self)
}
}
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct UnsignedExtensionOutputs {
pub prf: Option<AuthenticatorPrfMakeOutputs>,
}
impl UnsignedExtensionOutputs {
pub fn zip_contents(self) -> Option<Self> {
let Self { prf } = &self;
prf.is_some().then_some(self)
}
}