1#[cfg(feature = "ed25519")]
2mod ed25519;
3#[cfg(feature = "ed25519")]
4pub use self::ed25519::*;
5
6#[cfg(feature = "p256")]
7mod p256;
8#[cfg(feature = "p256")]
9pub use self::p256::*;
10
11use defuse_serde_utils::base64::{Base64, Unpadded, UrlSafe};
12use near_sdk::{
13 env, near,
14 serde::{Serialize, de::DeserializeOwned},
15 serde_json,
16};
17
18#[near(serializers = [json])]
19#[serde(bound(
20 serialize = "<A as Algorithm>::Signature: Serialize",
21 deserialize = "<A as Algorithm>::Signature: DeserializeOwned",
22))]
23#[derive(Debug, Clone)]
24pub struct PayloadSignature<A: Algorithm + ?Sized> {
25 #[cfg_attr(
27 all(feature = "abi", not(target_arch = "wasm32")),
28 schemars(with = "String")
29 )]
30 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
31 pub authenticator_data: Vec<u8>,
32 pub client_data_json: String,
34
35 #[cfg_attr(
36 all(feature = "abi", not(target_arch = "wasm32")),
37 schemars(with = "String"),
40 )]
41 pub signature: A::Signature,
42}
43
44impl<A: Algorithm + ?Sized> PayloadSignature<A> {
45 pub fn verify(
51 &self,
52 message: impl AsRef<[u8]>,
53 public_key: &A::PublicKey,
54 user_verification: UserVerification,
55 ) -> bool {
56 if self.authenticator_data.len() < 37
58 || !Self::verify_flags(self.authenticator_data[32], user_verification)
59 {
60 return false;
61 }
62
63 let Ok(c) = serde_json::from_str::<CollectedClientData>(&self.client_data_json) else {
65 return false;
66 };
67 if c.typ != ClientDataType::Get {
68 return false;
69 }
70
71 if c.challenge != message.as_ref() {
74 return false;
75 }
76
77 let hash = env::sha256_array(self.client_data_json.as_bytes());
80
81 A::verify(
84 &[self.authenticator_data.as_slice(), hash.as_slice()].concat(),
85 public_key,
86 &self.signature,
87 )
88 }
89
90 #[allow(clippy::identity_op)]
91 const AUTH_DATA_FLAGS_UP: u8 = 1 << 0;
92 const AUTH_DATA_FLAGS_UV: u8 = 1 << 2;
93 const AUTH_DATA_FLAGS_BE: u8 = 1 << 3;
94 const AUTH_DATA_FLAGS_BS: u8 = 1 << 4;
95
96 const fn verify_flags(flags: u8, user_verification: UserVerification) -> bool {
98 if flags & Self::AUTH_DATA_FLAGS_UP != Self::AUTH_DATA_FLAGS_UP {
100 return false;
101 }
102
103 if user_verification.is_required()
107 && (flags & Self::AUTH_DATA_FLAGS_UV != Self::AUTH_DATA_FLAGS_UV)
108 {
109 return false;
110 }
111
112 if (flags & Self::AUTH_DATA_FLAGS_BE != Self::AUTH_DATA_FLAGS_BE)
115 && (flags & Self::AUTH_DATA_FLAGS_BS == Self::AUTH_DATA_FLAGS_BS)
116 {
117 return false;
118 }
119
120 true
121 }
122}
123
124#[derive(Debug, Clone, Copy)]
125pub enum UserVerification {
126 Ignore,
127 Require,
128}
129
130impl UserVerification {
131 #[inline]
132 pub const fn is_required(&self) -> bool {
133 matches!(self, Self::Require)
134 }
135}
136
137pub trait Algorithm {
139 type PublicKey;
140 type Signature;
141
142 fn verify(msg: &[u8], public_key: &Self::PublicKey, signature: &Self::Signature) -> bool;
143}
144
145#[near(serializers = [json])]
147#[derive(Debug, Clone)]
148pub struct CollectedClientData {
149 #[serde(rename = "type")]
150 pub typ: ClientDataType,
151
152 #[serde_as(as = "Base64<UrlSafe, Unpadded>")]
153 pub challenge: Vec<u8>,
154
155 pub origin: String,
156}
157
158#[near(serializers = [json])]
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
160pub enum ClientDataType {
161 #[serde(rename = "webauthn.create")]
163 Create,
164
165 #[serde(rename = "webauthn.get")]
167 Get,
168}