1use std::{
6 hash::{Hash, Hasher},
7 sync::Arc,
8};
9
10use fastcrypto::{
11 error::FastCryptoError,
12 hash::{HashFunction, Sha256},
13 rsa::{Base64UrlUnpadded, Encoding},
14 secp256r1::{Secp256r1PublicKey, Secp256r1Signature},
15 traits::{ToFromBytes, VerifyingKey},
16};
17use once_cell::sync::OnceCell;
18use passkey_types::webauthn::{ClientDataType, CollectedClientData};
19use schemars::JsonSchema;
20use serde::{Deserialize, Deserializer, Serialize};
21use shared_crypto::intent::IntentMessage;
22
23use crate::{
24 base_types::{EpochId, IotaAddress},
25 crypto::{
26 DefaultHash, IotaSignature, IotaSignatureInner, PublicKey, Secp256r1IotaSignature,
27 Signature, SignatureScheme,
28 },
29 digests::ZKLoginInputsDigest,
30 error::{IotaError, IotaResult},
31 signature::{AuthenticatorTrait, VerifyParams},
32 signature_verification::VerifiedDigestCache,
33};
34
35#[cfg(test)]
36#[path = "unit_tests/passkey_authenticator_test.rs"]
37mod passkey_authenticator_test;
38
39#[derive(Debug, Clone, JsonSchema)]
42pub struct PasskeyAuthenticator {
43 authenticator_data: Vec<u8>,
48
49 client_data_json: String,
54
55 #[serde(skip)]
58 signature: Secp256r1Signature,
59
60 #[serde(skip)]
63 pk: Secp256r1PublicKey,
64
65 #[serde(skip)]
68 challenge: [u8; DefaultHash::OUTPUT_SIZE],
69
70 #[serde(skip)]
72 bytes: OnceCell<Vec<u8>>,
73}
74
75#[derive(Serialize, Deserialize, Debug)]
78pub struct RawPasskeyAuthenticator {
79 pub authenticator_data: Vec<u8>,
80 pub client_data_json: String,
81 pub user_signature: Signature,
82}
83
84impl TryFrom<RawPasskeyAuthenticator> for PasskeyAuthenticator {
87 type Error = IotaError;
88
89 fn try_from(raw: RawPasskeyAuthenticator) -> Result<Self, Self::Error> {
90 let client_data_json_parsed: CollectedClientData =
91 serde_json::from_str(&raw.client_data_json).map_err(|_| {
92 IotaError::InvalidSignature {
93 error: "Invalid client data json".to_string(),
94 }
95 })?;
96
97 if client_data_json_parsed.ty != ClientDataType::Get {
98 return Err(IotaError::InvalidSignature {
99 error: "Invalid client data type".to_string(),
100 });
101 };
102
103 let challenge = Base64UrlUnpadded::decode_vec(&client_data_json_parsed.challenge)
104 .map_err(|_| IotaError::InvalidSignature {
105 error: "Invalid encoded challenge".to_string(),
106 })?
107 .try_into()
108 .map_err(|_| IotaError::InvalidSignature {
109 error: "Invalid encoded challenge".to_string(),
110 })?;
111
112 if raw.user_signature.scheme() != SignatureScheme::Secp256r1 {
113 return Err(IotaError::InvalidSignature {
114 error: "Invalid signature scheme".to_string(),
115 });
116 };
117
118 let pk = Secp256r1PublicKey::from_bytes(raw.user_signature.public_key_bytes()).map_err(
119 |_| IotaError::InvalidSignature {
120 error: "Invalid r1 pk".to_string(),
121 },
122 )?;
123
124 let signature = Secp256r1Signature::from_bytes(raw.user_signature.signature_bytes())
125 .map_err(|_| IotaError::InvalidSignature {
126 error: "Invalid r1 sig".to_string(),
127 })?;
128
129 Ok(PasskeyAuthenticator {
130 authenticator_data: raw.authenticator_data,
131 client_data_json: raw.client_data_json,
132 signature,
133 pk,
134 challenge,
135 bytes: OnceCell::new(),
136 })
137 }
138}
139
140impl<'de> Deserialize<'de> for PasskeyAuthenticator {
141 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142 where
143 D: Deserializer<'de>,
144 {
145 use serde::de::Error;
146
147 let serializable = RawPasskeyAuthenticator::deserialize(deserializer)?;
148 serializable
149 .try_into()
150 .map_err(|e: IotaError| Error::custom(e.to_string()))
151 }
152}
153
154impl Serialize for PasskeyAuthenticator {
155 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
156 where
157 S: serde::ser::Serializer,
158 {
159 let mut bytes = Vec::with_capacity(Secp256r1IotaSignature::LENGTH);
160 bytes.push(SignatureScheme::Secp256r1.flag());
161 bytes.extend_from_slice(self.signature.as_ref());
162 bytes.extend_from_slice(self.pk.as_ref());
163
164 let raw = RawPasskeyAuthenticator {
165 authenticator_data: self.authenticator_data.clone(),
166 client_data_json: self.client_data_json.clone(),
167 user_signature: Signature::Secp256r1IotaSignature(
168 Secp256r1IotaSignature::from_bytes(&bytes).unwrap(),
169 ),
170 };
171 raw.serialize(serializer)
172 }
173}
174impl PasskeyAuthenticator {
175 pub fn new_for_testing(
178 authenticator_data: Vec<u8>,
179 client_data_json: String,
180 user_signature: Signature,
181 ) -> Result<Self, IotaError> {
182 let raw = RawPasskeyAuthenticator {
183 authenticator_data,
184 client_data_json,
185 user_signature,
186 };
187 raw.try_into()
188 }
189
190 pub fn get_pk(&self) -> IotaResult<PublicKey> {
192 Ok(PublicKey::Passkey((&self.pk).into()))
193 }
194}
195
196impl PartialEq for PasskeyAuthenticator {
198 fn eq(&self, other: &Self) -> bool {
199 self.as_ref() == other.as_ref()
200 }
201}
202
203impl Eq for PasskeyAuthenticator {}
205
206impl Hash for PasskeyAuthenticator {
208 fn hash<H: Hasher>(&self, state: &mut H) {
209 self.as_ref().hash(state);
210 }
211}
212
213impl AuthenticatorTrait for PasskeyAuthenticator {
214 fn verify_user_authenticator_epoch(
215 &self,
216 _epoch: EpochId,
217 _max_epoch_upper_bound_delta: Option<u64>,
218 ) -> IotaResult {
219 Ok(())
220 }
221
222 fn verify_claims<T>(
224 &self,
225 intent_msg: &IntentMessage<T>,
226 author: IotaAddress,
227 _aux_verify_data: &VerifyParams,
228 _zklogin_inputs_cache: Arc<VerifiedDigestCache<ZKLoginInputsDigest>>,
229 ) -> IotaResult
230 where
231 T: Serialize,
232 {
233 if self.challenge != to_signing_message(intent_msg) {
236 return Err(IotaError::InvalidSignature {
237 error: "Invalid challenge".to_string(),
238 });
239 };
240
241 let mut message = self.authenticator_data.clone();
243 let client_data_hash = Sha256::digest(self.client_data_json.as_bytes()).digest;
244 message.extend_from_slice(&client_data_hash);
245
246 if author != IotaAddress::from(&self.get_pk()?) {
248 return Err(IotaError::InvalidSignature {
249 error: "Invalid author".to_string(),
250 });
251 };
252
253 self.pk
255 .verify(&message, &self.signature)
256 .map_err(|_| IotaError::InvalidSignature {
257 error: "Fails to verify".to_string(),
258 })
259 }
260}
261
262impl ToFromBytes for PasskeyAuthenticator {
263 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
264 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
266 != &SignatureScheme::PasskeyAuthenticator.flag()
267 {
268 return Err(FastCryptoError::InvalidInput);
269 }
270 let passkey: PasskeyAuthenticator =
271 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
272 Ok(passkey)
273 }
274}
275
276impl AsRef<[u8]> for PasskeyAuthenticator {
277 fn as_ref(&self) -> &[u8] {
278 self.bytes
279 .get_or_try_init::<_, eyre::Report>(|| {
280 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
281 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
282 bytes.push(SignatureScheme::PasskeyAuthenticator.flag());
283 bytes.extend_from_slice(as_bytes.as_slice());
284 Ok(bytes)
285 })
286 .expect("OnceCell invariant violated")
287 }
288}
289
290pub fn to_signing_message<T: Serialize>(
293 intent_msg: &IntentMessage<T>,
294) -> [u8; DefaultHash::OUTPUT_SIZE] {
295 let mut hasher = DefaultHash::default();
296 bcs::serialize_into(&mut hasher, intent_msg).expect("Message serialization should not fail");
297 hasher.finalize().digest
298}