use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::client_data::ClientDataType;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PasskiError {
#[error("user_id must be at least 16 bytes")]
UserIdTooShort,
#[error("Base64 decode error: {0}")]
Base64Decode(#[from] base64ct::Error),
#[error("Invalid client data JSON: {0}")]
InvalidClientDataJson(#[from] serde_json::Error),
#[error("Failed to parse CBOR: {0}")]
CborDecode(#[from] ciborium::de::Error<std::io::Error>),
#[error("Failed to serialize CBOR: {0}")]
CborEncode(#[from] ciborium::ser::Error<std::io::Error>),
#[error("Missing {0} in client data")]
MissingClientDataField(String),
#[error("Invalid type in client data: {0}")]
InvalidClientDataType(String),
#[error("Invalid type: expected {expected}, got {got}")]
ClientDataTypeMismatch {
expected: ClientDataType,
got: ClientDataType,
},
#[error("Challenge mismatch")]
ChallengeMismatch,
#[error("Invalid origin: expected one of {expected:?}, got {got}")]
OriginMismatch { expected: Vec<String>, got: String },
#[error("Cross-origin requests are not allowed")]
CrossOriginNotAllowed,
#[error("Invalid authenticator data")]
InvalidAuthenticatorData,
#[error("rpId hash mismatch")]
RpIdHashMismatch,
#[error("User not present (UP flag not set)")]
UserNotPresent,
#[error("User verification required but UV flag not set")]
UserVerificationRequired,
#[error("BS flag set without BE flag")]
InvalidBackupFlags,
#[error("Invalid counter (possible replay attack)")]
CounterRegression,
#[error("No attested credential data present")]
NoAttestedCredentialData,
#[error("Credential not allowed")]
CredentialNotAllowed,
#[error("Credential ID mismatch between client and attested credential data")]
CredentialIdMismatch,
#[error("Unsupported algorithm: {0}")]
UnsupportedAlgorithm(i32),
#[error("Signature verification failed")]
SignatureVerificationFailed,
#[error("Invalid COSE key: {0}")]
InvalidCoseKey(String),
#[error("Invalid attestation object: {0}")]
InvalidAttestationObject(String),
#[error("Unsupported attestation format: {0}")]
UnsupportedAttestationFormat(String),
#[error("Missing {0} in attStmt")]
MissingAttStmtField(String),
#[error("Invalid attestation: {0}")]
InvalidAttestation(String),
#[error("Invalid attestation certificate: {0}")]
InvalidCertificate(String),
#[error("Invalid attestation certificate chain: {0}")]
InvalidCertificateChain(String),
#[error("Attestation certificate chain does not reach a trust anchor")]
UntrustedAttestation,
#[error("Attestation is required but the statement carried no certificate chain")]
MissingAttestationChain,
}
pub type Result<T> = std::result::Result<T, PasskiError>;
pub(crate) const FLAG_UP: u8 = 0x01;
pub(crate) const FLAG_UV: u8 = 0x04;
pub(crate) const FLAG_BE: u8 = 0x08;
pub(crate) const FLAG_BS: u8 = 0x10;
pub(crate) const FLAG_AT: u8 = 0x40;
pub(crate) const ALG_EDDSA: i32 = -8;
pub(crate) const ALG_ES256: i32 = -7;
pub(crate) const ALG_ES384: i32 = -35;
pub(crate) const ALG_RS256: i32 = -257;
pub(crate) const ALG_RS384: i32 = -258;
pub(crate) const KTY_OKP: i64 = 1;
pub(crate) const KTY_EC2: i64 = 2;
pub(crate) const KTY_RSA: i64 = 3;
pub(crate) const CRV_P256: i64 = 1;
pub(crate) const CRV_P384: i64 = 2;
pub(crate) const CRV_ED25519: i64 = 6;
#[derive(Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum AttestationConveyancePreference {
None,
Indirect,
Direct,
Enterprise,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug, Default)]
#[serde(rename_all = "kebab-case")]
pub enum AttestationType {
#[default]
None,
SelfAttested,
Unverified,
Basic,
AttCa,
}
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum AttestationTrustPolicy {
#[default]
Ignore,
VerifyWhenPresent,
Required,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum AuthenticatorAttachment {
Platform,
CrossPlatform,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)]
#[serde(rename_all = "kebab-case")]
pub enum AuthenticatorTransport {
Usb,
Nfc,
Ble,
SmartCard,
Hybrid,
Internal,
}
impl AuthenticatorTransport {
fn parse(value: &str) -> Option<Self> {
match value {
"usb" => Some(Self::Usb),
"nfc" => Some(Self::Nfc),
"ble" => Some(Self::Ble),
"smart-card" => Some(Self::SmartCard),
"hybrid" | "cable" => Some(Self::Hybrid),
"internal" => Some(Self::Internal),
_ => None,
}
}
}
pub(crate) fn deserialize_transports<'de, D>(
deserializer: D,
) -> std::result::Result<Vec<AuthenticatorTransport>, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = Option::<Vec<String>>::deserialize(deserializer)?.unwrap_or_default();
let mut transports = Vec::with_capacity(raw.len());
for value in raw {
if let Some(transport) = AuthenticatorTransport::parse(&value)
&& !transports.contains(&transport)
{
transports.push(transport);
}
}
Ok(transports)
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum ResidentKeyRequirement {
Discouraged,
Preferred,
Required,
}
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)]
#[serde(rename_all = "lowercase")]
pub enum UserVerificationRequirement {
Required,
Preferred,
Discouraged,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct StoredPasskey {
pub credential_id: Vec<u8>,
pub public_key: Vec<u8>,
pub counter: u32,
pub algorithm: i32,
#[serde(default)]
pub aaguid: [u8; 16],
#[serde(default)]
pub attestation_type: AttestationType,
#[serde(default, deserialize_with = "deserialize_transports")]
pub transports: Vec<AuthenticatorTransport>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rk: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub large_blob_supported: Option<bool>,
#[serde(default)]
pub be: bool,
#[serde(default)]
pub bs: bool,
}
#[derive(Serialize, Debug)]
pub struct RelyingParty {
pub name: String,
pub id: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct UserInfo {
pub id: String,
pub name: String,
#[serde(rename = "displayName")]
pub display_name: String,
}
#[derive(Serialize, Debug)]
pub struct PubKeyCredParam {
pub alg: i32,
#[serde(rename = "type")]
pub type_: &'static str,
}
#[derive(Serialize, Debug)]
#[non_exhaustive]
pub struct AuthenticatorSelection {
#[serde(
rename = "authenticatorAttachment",
skip_serializing_if = "Option::is_none"
)]
pub authenticator_attachment: Option<AuthenticatorAttachment>,
#[serde(rename = "residentKey")]
pub resident_key: ResidentKeyRequirement,
#[serde(rename = "userVerification")]
pub user_verification: UserVerificationRequirement,
}
#[derive(Serialize, Debug)]
#[non_exhaustive]
pub struct ExcludeCredential {
pub id: String,
#[serde(rename = "type")]
pub type_: &'static str,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub transports: Vec<AuthenticatorTransport>,
}
#[derive(Serialize, Debug)]
#[non_exhaustive]
pub struct AllowCredential {
pub id: String,
#[serde(rename = "type")]
pub type_: &'static str,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub transports: Vec<AuthenticatorTransport>,
}
#[derive(Serialize, Debug, Default)]
#[non_exhaustive]
pub struct RegistrationExtensions {
#[serde(rename = "credProps", skip_serializing_if = "Option::is_none")]
pub cred_props: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prf: Option<PrfInput>,
#[serde(rename = "largeBlob", skip_serializing_if = "Option::is_none")]
pub large_blob: Option<LargeBlobRegistrationInput>,
}
#[derive(Serialize, Debug, Default)]
#[non_exhaustive]
pub struct AuthenticationExtensions {
#[serde(skip_serializing_if = "Option::is_none")]
pub prf: Option<PrfInput>,
#[serde(rename = "largeBlob", skip_serializing_if = "Option::is_none")]
pub large_blob: Option<LargeBlobAuthenticationInput>,
}
#[derive(Serialize, Debug, Default)]
pub struct PrfInput {
#[serde(skip_serializing_if = "Option::is_none")]
pub eval: Option<PrfEval>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PrfEval {
pub first: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub second: Option<String>,
}
#[derive(Serialize, Debug)]
pub struct LargeBlobRegistrationInput {
pub support: LargeBlobSupport,
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum LargeBlobSupport {
Required,
Preferred,
}
#[derive(Debug)]
pub enum LargeBlobAuthenticationInput {
Read,
Write(String),
}
impl Serialize for LargeBlobAuthenticationInput {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeMap;
let mut map = serializer.serialize_map(Some(1))?;
match self {
Self::Read => map.serialize_entry("read", &true)?,
Self::Write(blob) => map.serialize_entry("write", blob)?,
}
map.end()
}
}
#[derive(Deserialize, Debug, Default)]
#[non_exhaustive]
pub struct ClientExtensionResults {
#[serde(default, rename = "credProps")]
pub cred_props: Option<CredPropsResult>,
#[serde(default)]
pub prf: Option<PrfExtensionResult>,
#[serde(default, rename = "largeBlob")]
pub large_blob: Option<LargeBlobResult>,
}
#[derive(Deserialize, Debug)]
pub struct CredPropsResult {
pub rk: Option<bool>,
}
#[derive(Deserialize, Debug)]
pub struct PrfExtensionResult {
pub enabled: Option<bool>,
pub results: Option<PrfResults>,
}
#[derive(Deserialize, Debug)]
pub struct PrfResults {
pub first: Option<String>,
pub second: Option<String>,
}
#[derive(Deserialize, Debug)]
pub struct LargeBlobResult {
pub supported: Option<bool>,
pub blob: Option<String>,
pub written: Option<bool>,
}