ser.rs (19105B)
1 #[cfg(test)] 2 mod tests; 3 use super::{ 4 super::{ 5 super::response::ser::{Base64DecodedVal, PublicKeyCredential}, 6 ser::{ 7 AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues, 8 ClientExtensions, 9 }, 10 }, 11 Authentication, AuthenticatorAssertion, UserHandle, 12 error::UnknownCredentialOptions, 13 }; 14 #[cfg(doc)] 15 use super::{AuthenticatorAttachment, CredentialId}; 16 use core::{ 17 fmt::{self, Formatter}, 18 marker::PhantomData, 19 str, 20 }; 21 use rsa::sha2::{Sha256, digest::OutputSizeUser as _}; 22 use serde::{ 23 de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpected, Visitor}, 24 ser::{Serialize, SerializeStruct as _, Serializer}, 25 }; 26 /// Authenticator data. 27 pub(super) struct AuthData(pub Vec<u8>); 28 impl<'e> Deserialize<'e> for AuthData { 29 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 30 where 31 D: Deserializer<'e>, 32 { 33 /// `Visitor` for `AuthData`. 34 struct AuthDataVisitor; 35 impl Visitor<'_> for AuthDataVisitor { 36 type Value = AuthData; 37 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 38 formatter.write_str("AuthenticatorData") 39 } 40 #[expect( 41 clippy::arithmetic_side_effects, 42 reason = "comment justifies its correctness" 43 )] 44 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 45 where 46 E: Error, 47 { 48 base64url_nopad::decode_len(v.len()) 49 .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"base64url-encoded value")) 50 .and_then(|len| { 51 // The decoded length is 3/4 of the encoded length, so overflow could only occur 52 // if usize::MAX / 4 < 32 => usize::MAX < 128 < u16::MAX; thus overflow is not 53 // possible. 54 // We add 32 since the SHA-256 hash of `clientDataJSON` will be added to the 55 // raw authenticator data by `AuthenticatorDataAssertion::new`. 56 let mut auth_data = vec![0; len + Sha256::output_size()]; 57 auth_data.truncate(len); 58 base64url_nopad::decode_buffer_exact(v.as_bytes(), auth_data.as_mut_slice()) 59 .map_err(E::custom) 60 .map(|()| AuthData(auth_data)) 61 }) 62 } 63 } 64 deserializer.deserialize_str(AuthDataVisitor) 65 } 66 } 67 /// `Visitor` for `AuthenticatorAssertion`. 68 /// 69 /// Unknown fields are ignored and only `clientDataJSON`, `authenticatorData`, and `signature` are required iff 70 /// `RELAXED`. 71 pub(super) struct AuthenticatorAssertionVisitor< 72 const RELAXED: bool, 73 const USER_LEN: usize, 74 const DISCOVERABLE: bool, 75 >; 76 impl<'d, const R: bool, const LEN: usize, const DISC: bool> Visitor<'d> 77 for AuthenticatorAssertionVisitor<R, LEN, DISC> 78 where 79 UserHandle<LEN>: Deserialize<'d>, 80 { 81 type Value = AuthenticatorAssertion<LEN, DISC>; 82 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 83 formatter.write_str("AuthenticatorAssertion") 84 } 85 #[expect(clippy::too_many_lines, reason = "107 lines is fine")] 86 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 87 where 88 A: MapAccess<'d>, 89 { 90 /// Fields in `AuthenticatorAssertionResponseJSON`. 91 enum Field<const IGNORE_UNKNOWN: bool> { 92 /// `clientDataJSON`. 93 ClientDataJson, 94 /// `authenticatorData`. 95 AuthenticatorData, 96 /// `signature`. 97 Signature, 98 /// `userHandle`. 99 UserHandle, 100 /// Unknown field. 101 Other, 102 } 103 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 104 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 105 where 106 D: Deserializer<'e>, 107 { 108 /// `Visitor` for `Field`. 109 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 110 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 111 type Value = Field<IG>; 112 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 113 write!( 114 formatter, 115 "'{CLIENT_DATA_JSON}', '{AUTHENTICATOR_DATA}', '{SIGNATURE}', or '{USER_HANDLE}'" 116 ) 117 } 118 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 119 where 120 E: Error, 121 { 122 match v { 123 CLIENT_DATA_JSON => Ok(Field::ClientDataJson), 124 AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData), 125 SIGNATURE => Ok(Field::Signature), 126 USER_HANDLE => Ok(Field::UserHandle), 127 _ => { 128 if IG { 129 Ok(Field::Other) 130 } else { 131 Err(E::unknown_field(v, AUTH_ASSERT_FIELDS)) 132 } 133 } 134 } 135 } 136 } 137 deserializer.deserialize_identifier(FieldVisitor::<I>) 138 } 139 } 140 let mut client_data = None; 141 let mut auth = None; 142 let mut sig = None; 143 let mut user_handle = None; 144 while let Some(key) = map.next_key::<Field<R>>()? { 145 match key { 146 Field::ClientDataJson => { 147 if client_data.is_some() { 148 return Err(Error::duplicate_field(CLIENT_DATA_JSON)); 149 } 150 client_data = map 151 .next_value::<Base64DecodedVal>() 152 .map(|c_data| c_data.0) 153 .map(Some)?; 154 } 155 Field::AuthenticatorData => { 156 if auth.is_some() { 157 return Err(Error::duplicate_field(AUTHENTICATOR_DATA)); 158 } 159 auth = map 160 .next_value::<AuthData>() 161 .map(|auth_data| Some(auth_data.0))?; 162 } 163 Field::Signature => { 164 if sig.is_some() { 165 return Err(Error::duplicate_field(SIGNATURE)); 166 } 167 sig = map 168 .next_value::<Base64DecodedVal>() 169 .map(|signature| signature.0) 170 .map(Some)?; 171 } 172 Field::UserHandle => { 173 if user_handle.is_some() { 174 return Err(Error::duplicate_field(USER_HANDLE)); 175 } 176 user_handle = map.next_value().map(Some)?; 177 } 178 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 179 } 180 } 181 client_data 182 .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON)) 183 .and_then(|client_data_json| { 184 auth.ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA)) 185 .and_then(|authenticator_data| { 186 sig.ok_or_else(|| Error::missing_field(SIGNATURE)) 187 .and_then(|signature| { 188 if DISC { 189 user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE)) 190 } else { 191 user_handle.map_or_else(|| Ok(None), Ok) 192 } 193 .map(|user| { 194 AuthenticatorAssertion::new_inner( 195 client_data_json, 196 authenticator_data, 197 signature, 198 user, 199 ) 200 }) 201 }) 202 }) 203 }) 204 } 205 } 206 /// `"clientDataJSON"`. 207 const CLIENT_DATA_JSON: &str = "clientDataJSON"; 208 /// `"authenticatorData"`. 209 const AUTHENTICATOR_DATA: &str = "authenticatorData"; 210 /// `"signature"`. 211 const SIGNATURE: &str = "signature"; 212 /// `"userHandle"`. 213 const USER_HANDLE: &str = "userHandle"; 214 /// Fields in `AuthenticatorAssertionResponseJSON`. 215 pub(super) const AUTH_ASSERT_FIELDS: &[&str; 4] = 216 &[CLIENT_DATA_JSON, AUTHENTICATOR_DATA, SIGNATURE, USER_HANDLE]; 217 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 218 for AuthenticatorAssertion<USER_LEN, DISCOVERABLE> 219 where 220 UserHandle<USER_LEN>: Deserialize<'de>, 221 { 222 /// Deserializes a `struct` based on 223 /// [`AuthenticatorAssertionResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorassertionresponsejson). 224 /// 225 /// Note unknown keys and duplicate keys are forbidden; 226 /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-clientdatajson), 227 /// [`authenticatorData`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-authenticatordata), 228 /// and 229 /// [`signature`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-signature) are 230 /// base64url-decoded; 231 /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-userhandle) is 232 /// required and must not be `null` iff `DISCOVERABLE`. When it exists and is not `null`, it is deserialized 233 /// via [`UserHandle::deserialize`]. All `required` fields in the `AuthenticatorAssertionResponseJSON` Web IDL 234 /// `dictionary` exist (and are not `null`). 235 #[inline] 236 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 237 where 238 D: Deserializer<'de>, 239 { 240 deserializer.deserialize_struct( 241 "AuthenticatorAssertion", 242 AUTH_ASSERT_FIELDS, 243 AuthenticatorAssertionVisitor::<false, USER_LEN, DISCOVERABLE>, 244 ) 245 } 246 } 247 /// Empty map of client extensions. 248 pub(super) struct ClientExtensionsOutputs; 249 /// `Visitor` for `ClientExtensionsOutputs`. 250 /// 251 /// Unknown fields are ignored iff `RELAXED`. 252 pub(super) struct ClientExtensionsOutputsVisitor<const RELAXED: bool, PRF>( 253 pub PhantomData<fn() -> PRF>, 254 ); 255 impl<'d, const R: bool, P> Visitor<'d> for ClientExtensionsOutputsVisitor<R, P> 256 where 257 P: for<'a> Deserialize<'a>, 258 { 259 type Value = ClientExtensionsOutputs; 260 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 261 formatter.write_str("ClientExtensionsOutputs") 262 } 263 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 264 where 265 A: MapAccess<'d>, 266 { 267 /// Allowed fields. 268 enum Field<const IGNORE_UNKNOWN: bool> { 269 /// `prf`. 270 Prf, 271 /// Unknown field. 272 Other, 273 } 274 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 275 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 276 where 277 D: Deserializer<'e>, 278 { 279 /// `Visitor` for `Field`. 280 /// 281 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`. 282 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 283 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 284 type Value = Field<IG>; 285 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 286 write!(formatter, "'{PRF}'") 287 } 288 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 289 where 290 E: Error, 291 { 292 match v { 293 PRF => Ok(Field::Prf), 294 _ => { 295 if IG { 296 Ok(Field::Other) 297 } else { 298 Err(E::unknown_field(v, EXT_FIELDS)) 299 } 300 } 301 } 302 } 303 } 304 deserializer.deserialize_identifier(FieldVisitor::<I>) 305 } 306 } 307 let mut prf = None; 308 while let Some(key) = map.next_key::<Field<R>>()? { 309 match key { 310 Field::Prf => { 311 if prf.is_some() { 312 return Err(Error::duplicate_field(PRF)); 313 } 314 prf = map.next_value::<Option<P>>().map(Some)?; 315 } 316 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 317 } 318 } 319 Ok(ClientExtensionsOutputs) 320 } 321 } 322 impl ClientExtensions for ClientExtensionsOutputs { 323 fn empty() -> Self { 324 Self 325 } 326 } 327 /// `"prf"` 328 const PRF: &str = "prf"; 329 /// `AuthenticationExtensionsClientOutputsJSON` fields. 330 pub(super) const EXT_FIELDS: &[&str; 1] = &[PRF]; 331 impl<'de> Deserialize<'de> for ClientExtensionsOutputs { 332 /// Deserializes a `struct` based on 333 /// [`AuthenticationExtensionsClientOutputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputsjson). 334 /// 335 /// Note that unknown and duplicate keys are forbidden and 336 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` 337 /// or deserialized via [`AuthenticationExtensionsPrfOutputs::deserialize`]. 338 #[inline] 339 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 340 where 341 D: Deserializer<'de>, 342 { 343 deserializer.deserialize_struct( 344 "ClientExtensionsOutputs", 345 EXT_FIELDS, 346 ClientExtensionsOutputsVisitor::< 347 false, 348 AuthenticationExtensionsPrfOutputsHelper< 349 false, 350 false, 351 AuthenticationExtensionsPrfValues, 352 >, 353 >(PhantomData), 354 ) 355 } 356 } 357 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 358 for Authentication<USER_LEN, DISCOVERABLE> 359 where 360 UserHandle<USER_LEN>: Deserialize<'de>, 361 { 362 /// Deserializes a `struct` based on 363 /// [`AuthenticationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson). 364 /// 365 /// Note that unknown and duplicate keys are forbidden; 366 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and 367 /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-rawid) are deserialized 368 /// via [`CredentialId::deserialize`]; 369 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized 370 /// via [`AuthenticatorAssertion::deserialize`]; 371 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-authenticatorattachment) 372 /// is `null` or deserialized via [`AuthenticatorAttachment::deserialize`]; 373 /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults) 374 /// is deserialized such that it is an empty map or a map that only contains 375 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) which additionally must be 376 /// `null` or an 377 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) 378 /// such that unknown and duplicate keys are forbidden, 379 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) 380 /// is forbidden (including being assigned `null`), 381 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist, 382 /// be `null`, or be an 383 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 384 /// with no unknown or duplicate keys, 385 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) must exist but be 386 /// `null`, and 387 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but 388 /// must be `null` if so; all `required` fields in the `AuthenticationResponseJSON` Web IDL `dictionary` exist 389 /// (and are not `null`); [`type`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-type) is 390 /// `"public-key"`; and the decoded `id` and decoded `rawId` are the same. 391 #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")] 392 #[inline] 393 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 394 where 395 D: Deserializer<'de>, 396 { 397 PublicKeyCredential::< 398 false, 399 false, 400 AuthenticatorAssertion<USER_LEN, DISCOVERABLE>, 401 ClientExtensionsOutputs, 402 >::deserialize(deserializer) 403 .map(|cred| Self { 404 raw_id: cred.id.unwrap_or_else(|| { 405 unreachable!("there is a bug in PublicKeyCredential::deserialize") 406 }), 407 response: cred.response, 408 authenticator_attachment: cred.authenticator_attachment, 409 }) 410 } 411 } 412 impl Serialize for UnknownCredentialOptions<'_, '_> { 413 /// Serializes `self` to conform with 414 /// [`UnknownCredentialOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-unknowncredentialoptions). 415 /// 416 /// # Examples 417 /// 418 /// ``` 419 /// # use core::str::FromStr; 420 /// # use webauthn_rp::{request::{AsciiDomain, RpId}, response::{auth::error::UnknownCredentialOptions, CredentialId}}; 421 /// # #[cfg(feature = "custom")] 422 /// let credential_id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?; 423 /// # #[cfg(feature = "custom")] 424 /// assert_eq!( 425 /// serde_json::to_string(&UnknownCredentialOptions { 426 /// rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 427 /// credential_id: (&credential_id).into(), 428 /// }) 429 /// .unwrap(), 430 /// r#"{"rpId":"example.com","credentialId":"AAAAAAAAAAAAAAAAAAAAAA"}"# 431 /// ); 432 /// # Ok::<_, webauthn_rp::AggErr>(()) 433 /// ``` 434 #[inline] 435 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 436 where 437 S: Serializer, 438 { 439 serializer 440 .serialize_struct("UnknownCredentialOptions", 2) 441 .and_then(|mut ser| { 442 ser.serialize_field("rpId", self.rp_id).and_then(|()| { 443 ser.serialize_field("credentialId", &self.credential_id) 444 .and_then(|()| ser.end()) 445 }) 446 }) 447 } 448 }