1use defuse_digest::{Digest, sha2::Sha256};
2use serde::{Deserialize, Serialize, de::DeserializeOwned};
3use serde_with::{
4 base64::{Base64, UrlSafe},
5 formats::Unpadded,
6 serde_as,
7};
8
9#[cfg(feature = "ed25519")]
10mod ed25519;
11#[cfg(feature = "ed25519")]
12pub use self::ed25519::*;
13
14#[cfg(feature = "p256")]
15mod p256;
16#[cfg(feature = "p256")]
17pub use self::p256::*;
18
19#[serde_as]
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[cfg_attr(feature = "abi", derive(::schemars::JsonSchema))]
22#[serde(bound(
23 serialize = "<A as Algorithm>::Signature: Serialize",
24 deserialize = "<A as Algorithm>::Signature: DeserializeOwned",
25))]
26pub struct PayloadSignature<A: Algorithm + ?Sized> {
27 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
29 pub authenticator_data: Vec<u8>,
30 pub client_data_json: String,
32
33 #[cfg_attr(feature = "abi", schemars(with = "String"))]
36 pub signature: A::Signature,
37}
38
39impl<A: Algorithm + ?Sized> PayloadSignature<A> {
40 pub fn verify(
46 &self,
47 message: impl AsRef<[u8]>,
48 public_key: &A::PublicKey,
49 user_verification: UserVerification,
50 ) -> bool {
51 if self.authenticator_data.len() < 37
53 || !Self::verify_flags(self.authenticator_data[32], user_verification)
54 {
55 return false;
56 }
57
58 let Ok(c) = serde_json::from_str::<CollectedClientData>(&self.client_data_json) else {
60 return false;
61 };
62 if c.typ != ClientDataType::Get {
63 return false;
64 }
65
66 if c.challenge != message.as_ref() {
69 return false;
70 }
71
72 let hash = Sha256::digest(self.client_data_json.as_bytes());
75
76 A::verify(
79 &[self.authenticator_data.as_slice(), hash.as_ref()].concat(),
80 public_key,
81 &self.signature,
82 )
83 }
84
85 #[allow(clippy::identity_op)]
86 const AUTH_DATA_FLAGS_UP: u8 = 1 << 0;
87 const AUTH_DATA_FLAGS_UV: u8 = 1 << 2;
88 const AUTH_DATA_FLAGS_BE: u8 = 1 << 3;
89 const AUTH_DATA_FLAGS_BS: u8 = 1 << 4;
90
91 const fn verify_flags(flags: u8, user_verification: UserVerification) -> bool {
93 if flags & Self::AUTH_DATA_FLAGS_UP != Self::AUTH_DATA_FLAGS_UP {
95 return false;
96 }
97
98 if user_verification.is_required()
102 && (flags & Self::AUTH_DATA_FLAGS_UV != Self::AUTH_DATA_FLAGS_UV)
103 {
104 return false;
105 }
106
107 if (flags & Self::AUTH_DATA_FLAGS_BE != Self::AUTH_DATA_FLAGS_BE)
110 && (flags & Self::AUTH_DATA_FLAGS_BS == Self::AUTH_DATA_FLAGS_BS)
111 {
112 return false;
113 }
114
115 true
116 }
117}
118
119#[derive(Debug, Clone, Copy)]
120pub enum UserVerification {
121 Ignore,
122 Require,
123}
124
125impl UserVerification {
126 #[inline]
127 pub const fn is_required(&self) -> bool {
128 matches!(self, Self::Require)
129 }
130}
131
132pub trait Algorithm {
134 type PublicKey;
135 type Signature;
136
137 fn verify(msg: &[u8], public_key: &Self::PublicKey, signature: &Self::Signature) -> bool;
138}
139
140#[serde_as]
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[cfg_attr(feature = "abi", derive(::schemars::JsonSchema))]
144pub struct CollectedClientData {
145 #[serde(rename = "type")]
146 pub typ: ClientDataType,
147
148 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
149 pub challenge: Vec<u8>,
150
151 pub origin: String,
152}
153
154#[serde_as]
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
156#[cfg_attr(feature = "abi", derive(::schemars::JsonSchema))]
157pub enum ClientDataType {
158 #[serde(rename = "webauthn.create")]
160 Create,
161
162 #[serde(rename = "webauthn.get")]
164 Get,
165}