iota_types/
passkey_authenticator.rs1use std::hash::{Hash, Hasher};
6
7use fastcrypto::{
8 error::FastCryptoError,
9 hash::HashFunction,
10 rsa::{Base64UrlUnpadded, Encoding},
11 secp256r1::{Secp256r1PublicKey, Secp256r1Signature},
12 traits::ToFromBytes,
13};
14use iota_sdk_crypto::{Verifier, passkey::PasskeyVerifier};
15use iota_sdk_types::crypto::IntentMessage;
16use once_cell::sync::OnceCell;
17use passkey_types::webauthn::{ClientDataType, CollectedClientData};
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)]
37pub struct PasskeyAuthenticator {
38 authenticator_data: Vec<u8>,
43
44 client_data_json: String,
49
50 signature: Secp256r1Signature,
53
54 pk: Secp256r1PublicKey,
57
58 _challenge: [u8; DefaultHash::OUTPUT_SIZE],
61
62 bytes: OnceCell<Vec<u8>>,
64}
65
66#[derive(Serialize, Deserialize, Debug)]
69pub struct RawPasskeyAuthenticator {
70 pub authenticator_data: Vec<u8>,
71 pub client_data_json: String,
72 pub user_signature: Signature,
73}
74
75impl TryFrom<RawPasskeyAuthenticator> for PasskeyAuthenticator {
78 type Error = IotaError;
79
80 fn try_from(raw: RawPasskeyAuthenticator) -> Result<Self, Self::Error> {
81 let client_data_json_parsed: CollectedClientData =
82 serde_json::from_str(&raw.client_data_json).map_err(|_| {
83 IotaError::InvalidSignature {
84 error: "Invalid client data json".to_string(),
85 }
86 })?;
87
88 if client_data_json_parsed.ty != ClientDataType::Get {
89 return Err(IotaError::InvalidSignature {
90 error: "Invalid client data type".to_string(),
91 });
92 };
93
94 let challenge = Base64UrlUnpadded::decode_vec(&client_data_json_parsed.challenge)
95 .map_err(|_| IotaError::InvalidSignature {
96 error: "Invalid encoded challenge".to_string(),
97 })?
98 .try_into()
99 .map_err(|_| IotaError::InvalidSignature {
100 error: "Invalid size for challenge".to_string(),
101 })?;
102
103 if raw.user_signature.scheme() != SignatureScheme::Secp256r1 {
104 return Err(IotaError::InvalidSignature {
105 error: "Invalid signature scheme".to_string(),
106 });
107 };
108
109 let pk = Secp256r1PublicKey::from_bytes(raw.user_signature.public_key_bytes()).map_err(
110 |_| IotaError::InvalidSignature {
111 error: "Invalid r1 pk".to_string(),
112 },
113 )?;
114
115 let signature = Secp256r1Signature::from_bytes(raw.user_signature.signature_bytes())
116 .map_err(|_| IotaError::InvalidSignature {
117 error: "Invalid r1 sig".to_string(),
118 })?;
119
120 Ok(PasskeyAuthenticator {
121 authenticator_data: raw.authenticator_data,
122 client_data_json: raw.client_data_json,
123 signature,
124 pk,
125 _challenge: challenge,
126 bytes: OnceCell::new(),
127 })
128 }
129}
130
131impl<'de> Deserialize<'de> for PasskeyAuthenticator {
132 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133 where
134 D: Deserializer<'de>,
135 {
136 use serde::de::Error;
137
138 let serializable = RawPasskeyAuthenticator::deserialize(deserializer)?;
139 serializable
140 .try_into()
141 .map_err(|e: IotaError| Error::custom(e.to_string()))
142 }
143}
144
145impl Serialize for PasskeyAuthenticator {
146 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
147 where
148 S: serde::ser::Serializer,
149 {
150 let mut bytes = Vec::with_capacity(Secp256r1IotaSignature::LENGTH);
151 bytes.push(SignatureScheme::Secp256r1.flag());
152 bytes.extend_from_slice(self.signature.as_ref());
153 bytes.extend_from_slice(self.pk.as_ref());
154
155 let raw = RawPasskeyAuthenticator {
156 authenticator_data: self.authenticator_data.clone(),
157 client_data_json: self.client_data_json.clone(),
158 user_signature: Signature::Secp256r1IotaSignature(
159 Secp256r1IotaSignature::from_bytes(&bytes).unwrap(), ),
163 };
164 raw.serialize(serializer)
165 }
166}
167
168impl PasskeyAuthenticator {
169 pub fn new_for_testing(
172 authenticator_data: Vec<u8>,
173 client_data_json: String,
174 user_signature: Signature,
175 ) -> Result<Self, IotaError> {
176 let raw = RawPasskeyAuthenticator {
177 authenticator_data,
178 client_data_json,
179 user_signature,
180 };
181 raw.try_into()
182 }
183
184 pub fn get_pk(&self) -> IotaResult<PublicKey> {
186 Ok(PublicKey::Passkey((&self.pk).into()))
187 }
188
189 pub fn authenticator_data(&self) -> &[u8] {
190 &self.authenticator_data
191 }
192
193 pub fn client_data_json(&self) -> &str {
194 &self.client_data_json
195 }
196
197 pub fn signature(&self) -> Signature {
198 let mut bytes = Vec::with_capacity(Secp256r1IotaSignature::LENGTH);
199 bytes.push(SignatureScheme::Secp256r1.flag());
200 bytes.extend_from_slice(self.signature.as_ref());
201 bytes.extend_from_slice(self.pk.as_ref());
202
203 Signature::Secp256r1IotaSignature(Secp256r1IotaSignature::from_bytes(&bytes).unwrap())
205 }
206}
207
208impl PartialEq for PasskeyAuthenticator {
210 fn eq(&self, other: &Self) -> bool {
211 self.as_ref() == other.as_ref()
212 }
213}
214
215impl Eq for PasskeyAuthenticator {}
217
218impl Hash for PasskeyAuthenticator {
220 fn hash<H: Hasher>(&self, state: &mut H) {
221 self.as_ref().hash(state);
222 }
223}
224
225impl AuthenticatorTrait for PasskeyAuthenticator {
226 fn verify_claims<T>(
228 &self,
229 intent_msg: &IntentMessage<T>,
230 author: IotaAddress,
231 _aux_verify_data: &VerifyParams,
232 ) -> IotaResult
233 where
234 T: Serialize,
235 {
236 let digest = intent_msg.signing_digest();
237
238 let authenticator = iota_sdk_types::PasskeyAuthenticator::from_bytes(self.as_bytes())
240 .map_err(|_| IotaError::InvalidSignature {
241 error: "Invalid passkey authenticator bytes".to_string(),
242 })?;
243
244 PasskeyVerifier::new()
245 .with_address(author)
246 .verify(&*digest, &authenticator)
247 .map_err(|e| IotaError::InvalidSignature {
248 error: format!("Invalid passkey authentication: {e}"),
249 })
250 }
251}
252
253impl ToFromBytes for PasskeyAuthenticator {
254 fn from_bytes(bytes: &[u8]) -> Result<Self, FastCryptoError> {
255 if bytes.first().ok_or(FastCryptoError::InvalidInput)?
257 != &SignatureScheme::PasskeyAuthenticator.flag()
258 {
259 return Err(FastCryptoError::InvalidInput);
260 }
261 let passkey: PasskeyAuthenticator =
262 bcs::from_bytes(&bytes[1..]).map_err(|_| FastCryptoError::InvalidSignature)?;
263 Ok(passkey)
264 }
265}
266
267impl AsRef<[u8]> for PasskeyAuthenticator {
268 fn as_ref(&self) -> &[u8] {
269 self.bytes
270 .get_or_try_init::<_, eyre::Report>(|| {
271 let as_bytes = bcs::to_bytes(self).expect("BCS serialization should not fail");
272 let mut bytes = Vec::with_capacity(1 + as_bytes.len());
273 bytes.push(SignatureScheme::PasskeyAuthenticator.flag());
274 bytes.extend_from_slice(as_bytes.as_slice());
275 Ok(bytes)
276 })
277 .expect("OnceCell invariant violated")
278 }
279}
280
281pub fn to_signing_message<T: Serialize>(
284 intent_msg: &IntentMessage<T>,
285) -> [u8; DefaultHash::OUTPUT_SIZE] {
286 let mut hasher = DefaultHash::default();
287 bcs::serialize_into(&mut hasher, intent_msg).expect("Message serialization should not fail");
288 hasher.finalize().digest
289}