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