1use serde::{Deserialize, Serialize, de::DeserializeOwned};
2use serde_with::serde_as;
3
4use defuse_serde_utils::base64::{Base64, Unpadded, UrlSafe};
5
6#[cfg(feature = "ed25519")]
7mod ed25519;
8#[cfg(feature = "ed25519")]
9pub use self::ed25519::*;
10
11#[cfg(feature = "p256")]
12mod p256;
13#[cfg(feature = "p256")]
14pub use self::p256::*;
15
16#[serde_as]
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[cfg_attr(feature = "abi", derive(::schemars::JsonSchema))]
19#[serde(bound(
20 serialize = "<A as Algorithm>::Signature: Serialize",
21 deserialize = "<A as Algorithm>::Signature: DeserializeOwned",
22))]
23pub struct PayloadSignature<A: Algorithm + ?Sized> {
24 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
26 #[cfg_attr(feature = "abi", schemars(with = "String"))]
27 pub authenticator_data: Vec<u8>,
28 pub client_data_json: String,
30
31 #[cfg_attr(feature = "abi", schemars(with = "String"))]
34 pub signature: A::Signature,
35}
36
37#[cfg(any(test, feature = "sha2", feature = "near-contract"))]
38impl<A: Algorithm + ?Sized> PayloadSignature<A> {
39 pub fn verify(
45 &self,
46 message: impl AsRef<[u8]>,
47 public_key: &A::PublicKey,
48 user_verification: UserVerification,
49 ) -> bool {
50 use defuse_digest::Digest;
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 = defuse_digest::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}