ser_relaxed.rs (22739B)
1 #[cfg(test)] 2 mod tests; 3 #[cfg(doc)] 4 use super::super::{Challenge, CredentialId}; 5 use super::{ 6 super::{ 7 super::request::register::{USER_HANDLE_MAX_LEN, UserHandle}, 8 auth::ser::{ 9 AUTH_ASSERT_FIELDS, AuthData, AuthenticatorAssertionVisitor, ClientExtensionsOutputs, 10 ClientExtensionsOutputsVisitor, EXT_FIELDS, 11 }, 12 ser::{ 13 AuthenticationExtensionsPrfOutputsHelper, Base64DecodedVal, ClientExtensions, 14 PublicKeyCredential, Type, 15 }, 16 ser_relaxed::AuthenticationExtensionsPrfValuesRelaxed, 17 }, 18 Authentication, AuthenticatorAssertion, AuthenticatorAttachment, 19 }; 20 use core::{ 21 fmt::{self, Formatter}, 22 marker::PhantomData, 23 }; 24 use serde::de::{Deserialize, Deserializer, Error, MapAccess, Visitor}; 25 /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation. 26 struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs); 27 impl ClientExtensions for ClientExtensionsOutputsRelaxed { 28 fn empty() -> Self { 29 Self(ClientExtensionsOutputs::empty()) 30 } 31 } 32 impl<'de> Deserialize<'de> for ClientExtensionsOutputsRelaxed { 33 /// Same as [`ClientExtensionsOutputs::deserialize`] except unknown keys are ignored. 34 /// 35 /// Note that duplicate keys are still forbidden. 36 #[inline] 37 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 38 where 39 D: Deserializer<'de>, 40 { 41 deserializer 42 .deserialize_struct( 43 "ClientExtensionsOutputsRelaxed", 44 EXT_FIELDS, 45 ClientExtensionsOutputsVisitor::< 46 true, 47 AuthenticationExtensionsPrfOutputsHelper< 48 true, 49 false, 50 AuthenticationExtensionsPrfValuesRelaxed, 51 >, 52 >(PhantomData), 53 ) 54 .map(Self) 55 } 56 } 57 /// `newtype` around `AuthenticatorAssertion` with a "relaxed" [`Self::deserialize`] implementation. 58 #[derive(Debug)] 59 pub struct AuthenticatorAssertionRelaxed<const USER_LEN: usize, const DISCOVERABLE: bool>( 60 pub AuthenticatorAssertion<USER_LEN, DISCOVERABLE>, 61 ); 62 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 63 for AuthenticatorAssertionRelaxed<USER_LEN, DISCOVERABLE> 64 where 65 UserHandle<USER_LEN>: Deserialize<'de>, 66 { 67 /// Same as [`AuthenticatorAssertion::deserialize`] except unknown keys are ignored. 68 /// 69 /// Note that duplicate keys are still forbidden. 70 #[inline] 71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 72 where 73 D: Deserializer<'de>, 74 { 75 deserializer 76 .deserialize_struct( 77 "AuthenticatorAssertionRelaxed", 78 AUTH_ASSERT_FIELDS, 79 AuthenticatorAssertionVisitor::<true, USER_LEN, DISCOVERABLE>, 80 ) 81 .map(Self) 82 } 83 } 84 /// `newtype` around `Authentication` with a "relaxed" [`Self::deserialize`] implementation. 85 #[derive(Debug)] 86 pub struct AuthenticationRelaxed<const USER_LEN: usize, const DISCOVERABLE: bool>( 87 pub Authentication<USER_LEN, DISCOVERABLE>, 88 ); 89 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 90 for AuthenticationRelaxed<USER_LEN, DISCOVERABLE> 91 where 92 UserHandle<USER_LEN>: Deserialize<'de>, 93 { 94 /// Same as [`Authentication::deserialize`] except unknown keys are ignored; 95 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-response) is deserialized 96 /// via [`AuthenticatorAssertionRelaxed::deserialize`]; 97 /// [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-clientextensionresults) 98 /// is deserialized such unknown keys are ignored but duplicate keys are forbidden, 99 /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf) is `null` or an 100 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs) 101 /// such that unknown keys are allowed but duplicate keys are forbidden, 102 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) 103 /// is forbidden (including being assigned `null`), 104 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results) must not exist, 105 /// be `null`, or be an 106 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues) 107 /// where unknown keys are ignored, duplicate keys are forbidden, 108 /// [`first`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-first) is not required but 109 /// if it exists it must be `null`, and 110 /// [`second`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfvalues-second) can exist but 111 /// must be `null` if so; and only 112 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-authenticationresponsejson-id) and `response` are required. 113 /// `rawId` and `type` and allowed to not exist. For the other fields, they are allowed to not exist or be `null`. 114 /// 115 /// Note that duplicate keys are still forbidden, and data matching still applies when applicable. 116 #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")] 117 #[inline] 118 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 119 where 120 D: Deserializer<'de>, 121 { 122 PublicKeyCredential::< 123 true, 124 false, 125 AuthenticatorAssertionRelaxed<USER_LEN, DISCOVERABLE>, 126 ClientExtensionsOutputsRelaxed, 127 >::deserialize(deserializer) 128 .map(|cred| { 129 Self(Authentication { 130 raw_id: cred.id.unwrap_or_else(|| { 131 unreachable!("there is a bug in PublicKeyCredential::deserialize") 132 }), 133 response: cred.response.0, 134 authenticator_attachment: cred.authenticator_attachment, 135 }) 136 }) 137 } 138 } 139 /// `AuthenticationRelaxed` with a required `UserHandle`. 140 pub type DiscoverableAuthenticationRelaxed<const USER_LEN: usize> = 141 AuthenticationRelaxed<USER_LEN, true>; 142 /// `AuthenticationRelaxed` with a required `UserHandle64`. 143 pub type DiscoverableAuthenticationRelaxed64 = AuthenticationRelaxed<USER_HANDLE_MAX_LEN, true>; 144 /// `AuthenticationRelaxed` with a required `UserHandle16`. 145 pub type DiscoverableAuthenticationRelaxed16 = AuthenticationRelaxed<16, true>; 146 /// `AuthenticationRelaxed` with an optional `UserHandle`. 147 pub type NonDiscoverableAuthenticationRelaxed<const USER_LEN: usize> = 148 AuthenticationRelaxed<USER_LEN, false>; 149 /// `AuthenticationRelaxed` with an optional `UserHandle64`. 150 pub type NonDiscoverableAuthenticationRelaxed64 = AuthenticationRelaxed<USER_HANDLE_MAX_LEN, false>; 151 /// `AuthenticationRelaxed` with an optional `UserHandle16`. 152 pub type NonDiscoverableAuthenticationRelaxed16 = AuthenticationRelaxed<16, false>; 153 /// `newtype` around `Authentication` with a custom [`Self::deserialize`] implementation. 154 #[derive(Debug)] 155 pub struct CustomAuthentication<const USER_LEN: usize, const DISCOVERABLE: bool>( 156 pub Authentication<USER_LEN, DISCOVERABLE>, 157 ); 158 impl<'de, const USER_LEN: usize, const DISCOVERABLE: bool> Deserialize<'de> 159 for CustomAuthentication<USER_LEN, DISCOVERABLE> 160 where 161 UserHandle<USER_LEN>: Deserialize<'de>, 162 { 163 /// Despite the spec having a 164 /// [pre-defined format](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationresponsejson) that clients 165 /// can follow, the downside is the superfluous data it contains. 166 /// 167 /// There simply is no reason to send the [`CredentialId`] twice. This redundant data puts RPs in 168 /// a position where they either ignore the data or parse the data to ensure no contradictions exist 169 /// (e.g., [FIDO conformance requires one to verify `id` and `rawId` exist and match](https://github.com/w3c/webauthn/issues/2119#issuecomment-2287875401)). 170 /// 171 /// While [`Authentication::deserialize`] _strictly_ adheres to the JSON definition, this implementation 172 /// strictly disallows superfluous data. Specifically the following JSON is required to be sent where duplicate 173 /// and unknown keys are disallowed: 174 /// 175 /// ```json 176 /// { 177 /// "authenticatorAttachment": null | "platform" | "cross-platform", 178 /// "authenticatorData": <base64url string>, 179 /// "clientDataJSON": <base64url string>, 180 /// "clientExtensionResults": { 181 /// "prf": null | PRFJSON 182 /// }, 183 /// "id": <see CredentialId::deserialize>, 184 /// "signature": <base64url string>, 185 /// "type": "public-key", 186 /// "userHandle": null | <see UserHandle::deserialize> 187 /// } 188 /// // PRFJSON: 189 /// { 190 /// "results": null | PRFOutputsJSON 191 /// } 192 /// // PRFOutputsJSON: 193 /// { 194 /// "first": null, 195 /// "second": null 196 /// } 197 /// ``` 198 /// 199 /// `"userHandle"` is required to exist and not be `null` iff `DISCOVERABLE`. When it does exist and 200 /// is not `null`, then it is deserialized via [`UserHandle::deserialize`]. All of the remaining keys are 201 /// required with the exceptions of `"authenticatorAttachment"` and `"type"`. `"prf"` is not required in the 202 /// `clientExtensionResults` object, `"results"` is required in the `PRFJSON` object, and `"first"` 203 /// (but not `"second"`) is required in `PRFOutputsJSON`. 204 /// 205 /// # Examples 206 /// 207 /// ``` 208 /// # use webauthn_rp::{request::register::{UserHandle, USER_HANDLE_MIN_LEN}, response::auth::ser_relaxed::CustomAuthentication}; 209 /// assert!( 210 /// // The below payload is technically valid, but `AuthenticationServerState::verify` will fail 211 /// // since the authenticatorData is not valid. This is true for `Authentication::deserialize` 212 /// // as well since authenticatorData parsing is always deferred. 213 /// serde_json::from_str::<CustomAuthentication<USER_HANDLE_MIN_LEN, true>>( 214 /// r#"{ 215 /// "authenticatorData": "AA", 216 /// "authenticatorAttachment": "cross-platform", 217 /// "clientExtensionResults": {}, 218 /// "clientDataJSON": "AA", 219 /// "id": "AAAAAAAAAAAAAAAAAAAAAA", 220 /// "signature": "AA", 221 /// "type": "public-key", 222 /// "userHandle": "AA" 223 /// }"# 224 /// ).is_ok()); 225 /// ``` 226 #[expect( 227 clippy::too_many_lines, 228 reason = "want to hide; thus don't put in outer scope" 229 )] 230 #[inline] 231 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 232 where 233 D: Deserializer<'de>, 234 { 235 /// `Visitor` for `CustomAuthentication`. 236 struct CustomAuthenticationVisitor<const LEN: usize, const DISC: bool>; 237 impl<'d, const LEN: usize, const DISC: bool> Visitor<'d> for CustomAuthenticationVisitor<LEN, DISC> 238 where 239 UserHandle<LEN>: Deserialize<'d>, 240 { 241 type Value = CustomAuthentication<LEN, DISC>; 242 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 243 formatter.write_str("CustomAuthentication") 244 } 245 #[expect( 246 clippy::too_many_lines, 247 reason = "want to hide; thus don't put in outer scope" 248 )] 249 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 250 where 251 A: MapAccess<'d>, 252 { 253 /// Fields in the JSON. 254 enum Field { 255 /// `authenticatorAttachment` key. 256 AuthenticatorAttachment, 257 /// `authenticatorData` key. 258 AuthenticatorData, 259 /// `clientDataJSON` key. 260 ClientDataJson, 261 /// `clientExtensionResults` key. 262 ClientExtensionResults, 263 /// `id` key. 264 Id, 265 /// `signature` key. 266 Signature, 267 /// `type` key. 268 Type, 269 /// `userHandle` key. 270 UserHandle, 271 } 272 impl<'e> Deserialize<'e> for Field { 273 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 274 where 275 D: Deserializer<'e>, 276 { 277 /// `Visitor` for `Field`. 278 struct FieldVisitor; 279 impl Visitor<'_> for FieldVisitor { 280 type Value = Field; 281 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 282 write!( 283 formatter, 284 "'{AUTHENTICATOR_ATTACHMENT}', '{AUTHENTICATOR_DATA}', '{CLIENT_DATA_JSON}', '{CLIENT_EXTENSION_RESULTS}', '{ID}', '{SIGNATURE}', '{TYPE}', or '{USER_HANDLE}'" 285 ) 286 } 287 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 288 where 289 E: Error, 290 { 291 match v { 292 AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment), 293 AUTHENTICATOR_DATA => Ok(Field::AuthenticatorData), 294 CLIENT_DATA_JSON => Ok(Field::ClientDataJson), 295 CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults), 296 ID => Ok(Field::Id), 297 SIGNATURE => Ok(Field::Signature), 298 TYPE => Ok(Field::Type), 299 USER_HANDLE => Ok(Field::UserHandle), 300 _ => Err(E::unknown_field(v, FIELDS)), 301 } 302 } 303 } 304 deserializer.deserialize_identifier(FieldVisitor) 305 } 306 } 307 let mut authenticator_attachment = None; 308 let mut authenticator_data = None; 309 let mut client_data_json = None; 310 let mut ext = false; 311 let mut id = None; 312 let mut signature = None; 313 let mut typ = false; 314 let mut user_handle = None; 315 while let Some(key) = map.next_key()? { 316 match key { 317 Field::AuthenticatorAttachment => { 318 if authenticator_attachment.is_some() { 319 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT)); 320 } 321 authenticator_attachment = map.next_value::<Option<_>>().map(Some)?; 322 } 323 Field::AuthenticatorData => { 324 if authenticator_data.is_some() { 325 return Err(Error::duplicate_field(AUTHENTICATOR_DATA)); 326 } 327 authenticator_data = 328 map.next_value::<AuthData>().map(|val| Some(val.0))?; 329 } 330 Field::ClientDataJson => { 331 if client_data_json.is_some() { 332 return Err(Error::duplicate_field(CLIENT_DATA_JSON)); 333 } 334 client_data_json = map 335 .next_value::<Base64DecodedVal>() 336 .map(|val| Some(val.0))?; 337 } 338 Field::ClientExtensionResults => { 339 if ext { 340 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS)); 341 } 342 ext = map.next_value::<ClientExtensionsOutputs>().map(|_| true)?; 343 } 344 Field::Id => { 345 if id.is_some() { 346 return Err(Error::duplicate_field(ID)); 347 } 348 id = map.next_value().map(Some)?; 349 } 350 Field::Signature => { 351 if signature.is_some() { 352 return Err(Error::duplicate_field(SIGNATURE)); 353 } 354 signature = map 355 .next_value::<Base64DecodedVal>() 356 .map(|val| Some(val.0))?; 357 } 358 Field::Type => { 359 if typ { 360 return Err(Error::duplicate_field(TYPE)); 361 } 362 typ = map.next_value::<Type>().map(|_| true)?; 363 } 364 Field::UserHandle => { 365 if user_handle.is_some() { 366 return Err(Error::duplicate_field(USER_HANDLE)); 367 } 368 user_handle = map.next_value().map(Some)?; 369 } 370 } 371 } 372 authenticator_data 373 .ok_or_else(|| Error::missing_field(AUTHENTICATOR_DATA)) 374 .and_then(|auth_data| { 375 client_data_json 376 .ok_or_else(|| Error::missing_field(CLIENT_DATA_JSON)) 377 .and_then(|c_data| { 378 id.ok_or_else(|| Error::missing_field(ID)) 379 .and_then(|raw_id| { 380 signature 381 .ok_or_else(|| Error::missing_field(SIGNATURE)) 382 .and_then(|sig| { 383 if ext { 384 if DISC { 385 user_handle.ok_or_else(|| Error::missing_field(USER_HANDLE)) 386 } else { 387 user_handle.map_or_else(|| Ok(None), Ok) 388 }.map(|user| { 389 CustomAuthentication(Authentication { 390 response: AuthenticatorAssertion::new_inner( 391 c_data, 392 auth_data, 393 sig, 394 user, 395 ), 396 authenticator_attachment: 397 authenticator_attachment.map_or( 398 AuthenticatorAttachment::None, 399 |auth_attach| { 400 auth_attach.unwrap_or( 401 AuthenticatorAttachment::None, 402 ) 403 }, 404 ), 405 raw_id, 406 }) 407 }) 408 } else { 409 Err(Error::missing_field( 410 CLIENT_EXTENSION_RESULTS, 411 )) 412 } 413 }) 414 }) 415 }) 416 }) 417 } 418 } 419 /// `authenticatorAttachment` key. 420 const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment"; 421 /// `authenticatorData` key. 422 const AUTHENTICATOR_DATA: &str = "authenticatorData"; 423 /// `clientDataJSON` key. 424 const CLIENT_DATA_JSON: &str = "clientDataJSON"; 425 /// `clientExtensionResults` key. 426 const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults"; 427 /// `id` key. 428 const ID: &str = "id"; 429 /// `signature` key. 430 const SIGNATURE: &str = "signature"; 431 /// `type` key. 432 const TYPE: &str = "type"; 433 /// `userHandle` key. 434 const USER_HANDLE: &str = "userHandle"; 435 /// Fields. 436 const FIELDS: &[&str; 8] = &[ 437 AUTHENTICATOR_ATTACHMENT, 438 AUTHENTICATOR_DATA, 439 CLIENT_DATA_JSON, 440 CLIENT_EXTENSION_RESULTS, 441 ID, 442 SIGNATURE, 443 TYPE, 444 USER_HANDLE, 445 ]; 446 deserializer.deserialize_struct("CustomAuthentication", FIELDS, CustomAuthenticationVisitor) 447 } 448 } 449 /// `CustomAuthentication` with a required `UserHandle`. 450 pub type DiscoverableCustomAuthentication<const USER_LEN: usize> = 451 CustomAuthentication<USER_LEN, true>; 452 /// `CustomAuthentication` with a required `UserHandle64`. 453 pub type DiscoverableCustomAuthentication64 = CustomAuthentication<USER_HANDLE_MAX_LEN, true>; 454 /// `CustomAuthentication` with a required `UserHandle16`. 455 pub type DiscoverableCustomAuthentication16 = CustomAuthentication<16, true>; 456 /// `CustomAuthentication` with an optional `UserHandle`. 457 pub type NonDiscoverableCustomAuthentication<const USER_LEN: usize> = 458 CustomAuthentication<USER_LEN, false>; 459 /// `CustomAuthentication` with an optional `UserHandle64`. 460 pub type NonDiscoverableCustomAuthentication64 = CustomAuthentication<USER_HANDLE_MAX_LEN, false>; 461 /// `CustomAuthentication` with an optional `UserHandle16`. 462 pub type NonDiscoverableCustomAuthentication16 = CustomAuthentication<16, false>;