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