ser.rs (130827B)
1 #[cfg(test)] 2 mod tests; 3 use super::{ 4 super::{ 5 super::response::ser::{Null, Type}, 6 auth::PrfInputOwned, 7 ser::PrfHelper, 8 }, 9 AuthenticatorAttachment, AuthenticatorSelectionCriteria, Challenge, CoseAlgorithmIdentifier, 10 CoseAlgorithmIdentifiers, CredProtect, CredentialCreationOptions, 11 CredentialMediationRequirement, Extension, ExtensionInfo, ExtensionReq, FIVE_MINUTES, 12 FourToSixtyThree, Hints, PrfInput, PublicKeyCredentialCreationOptions, 13 PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity, RegistrationClientState, 14 ResidentKeyRequirement, RpId, UserHandle, UserVerificationRequirement, 15 }; 16 #[cfg(doc)] 17 use core::str::FromStr; 18 use core::{ 19 convert, 20 error::Error as E, 21 fmt::{self, Display, Formatter}, 22 num::NonZeroU32, 23 str, 24 }; 25 use serde::{ 26 de::{Deserialize, Deserializer, Error, MapAccess, SeqAccess, Unexpected, Visitor}, 27 ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer}, 28 }; 29 /// `"type"` 30 const TYPE: &str = "type"; 31 /// `"public-key"` 32 const PUBLIC_KEY: &str = "public-key"; 33 /// `"alg"` 34 const ALG: &str = "alg"; 35 /// [EdDSA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 36 const EDDSA: i16 = -8i16; 37 /// [ES256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 38 const ES256: i16 = -7i16; 39 /// [ES384](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 40 const ES384: i16 = -35i16; 41 /// [ML-DSA-44](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 42 const MLDSA44: i16 = -48i16; 43 /// [ML-DSA-65](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 44 const MLDSA65: i16 = -49i16; 45 /// [ML-DSA-87](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 46 const MLDSA87: i16 = -50i16; 47 /// [RS256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms) 48 const RS256: i16 = -257i16; 49 impl Serialize for CoseAlgorithmIdentifier { 50 /// Serializes `self` into a `struct` based on 51 /// [`PublicKeyCredentialParameters`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialparameters). 52 #[inline] 53 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 54 where 55 S: Serializer, 56 { 57 serializer 58 .serialize_struct("PublicKeyCredentialParameters", 2) 59 .and_then(|mut ser| { 60 ser.serialize_field(TYPE, PUBLIC_KEY).and_then(|()| { 61 ser.serialize_field( 62 ALG, 63 &match *self { 64 Self::Mldsa87 => MLDSA87, 65 Self::Mldsa65 => MLDSA65, 66 Self::Mldsa44 => MLDSA44, 67 Self::Eddsa => EDDSA, 68 Self::Es256 => ES256, 69 Self::Es384 => ES384, 70 Self::Rs256 => RS256, 71 }, 72 ) 73 .and_then(|()| ser.end()) 74 }) 75 }) 76 } 77 } 78 impl Serialize for CoseAlgorithmIdentifiers { 79 /// Serializes `self` to conform with 80 /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-pubkeycredparams). 81 /// 82 /// # Examples 83 /// 84 /// ``` 85 /// # use webauthn_rp::request::register::{CoseAlgorithmIdentifier,CoseAlgorithmIdentifiers}; 86 /// assert_eq!( 87 /// serde_json::to_string(&CoseAlgorithmIdentifiers::ALL)?, 88 /// r#"[{"type":"public-key","alg":-50},{"type":"public-key","alg":-49},{"type":"public-key","alg":-48},{"type":"public-key","alg":-8},{"type":"public-key","alg":-7},{"type":"public-key","alg":-35},{"type":"public-key","alg":-257}]"# 89 /// ); 90 /// assert_eq!( 91 /// serde_json::to_string(&CoseAlgorithmIdentifiers::default().remove(CoseAlgorithmIdentifier::Es384))?, 92 /// r#"[{"type":"public-key","alg":-50},{"type":"public-key","alg":-49},{"type":"public-key","alg":-48},{"type":"public-key","alg":-8},{"type":"public-key","alg":-7},{"type":"public-key","alg":-257}]"# 93 /// ); 94 /// # Ok::<_, serde_json::Error>(()) 95 /// ``` 96 #[expect( 97 clippy::arithmetic_side_effects, 98 reason = "comment justifies correctness" 99 )] 100 #[inline] 101 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 102 where 103 S: Serializer, 104 { 105 // At most we add `1` seven times which clearly cannot overflow or `usize`. 106 serializer 107 .serialize_seq(Some( 108 usize::from(self.contains(CoseAlgorithmIdentifier::Mldsa87)) 109 + usize::from(self.contains(CoseAlgorithmIdentifier::Mldsa65)) 110 + usize::from(self.contains(CoseAlgorithmIdentifier::Mldsa44)) 111 + usize::from(self.contains(CoseAlgorithmIdentifier::Eddsa)) 112 + usize::from(self.contains(CoseAlgorithmIdentifier::Es256)) 113 + usize::from(self.contains(CoseAlgorithmIdentifier::Es384)) 114 + usize::from(self.contains(CoseAlgorithmIdentifier::Es384)), 115 )) 116 .and_then(|mut ser| { 117 if self.contains(CoseAlgorithmIdentifier::Mldsa87) { 118 ser.serialize_element(&CoseAlgorithmIdentifier::Mldsa87) 119 } else { 120 Ok(()) 121 } 122 .and_then(|()| { 123 if self.contains(CoseAlgorithmIdentifier::Mldsa65) { 124 ser.serialize_element(&CoseAlgorithmIdentifier::Mldsa65) 125 } else { 126 Ok(()) 127 } 128 .and_then(|()| { 129 if self.contains(CoseAlgorithmIdentifier::Mldsa44) { 130 ser.serialize_element(&CoseAlgorithmIdentifier::Mldsa44) 131 } else { 132 Ok(()) 133 } 134 .and_then(|()| { 135 if self.contains(CoseAlgorithmIdentifier::Eddsa) { 136 ser.serialize_element(&CoseAlgorithmIdentifier::Eddsa) 137 } else { 138 Ok(()) 139 } 140 .and_then(|()| { 141 if self.contains(CoseAlgorithmIdentifier::Es256) { 142 ser.serialize_element(&CoseAlgorithmIdentifier::Es256) 143 } else { 144 Ok(()) 145 } 146 .and_then(|()| { 147 if self.contains(CoseAlgorithmIdentifier::Es384) { 148 ser.serialize_element(&CoseAlgorithmIdentifier::Es384) 149 } else { 150 Ok(()) 151 } 152 .and_then(|()| { 153 if self.contains(CoseAlgorithmIdentifier::Rs256) { 154 ser.serialize_element(&CoseAlgorithmIdentifier::Rs256) 155 } else { 156 Ok(()) 157 } 158 .and_then(|()| ser.end()) 159 }) 160 }) 161 }) 162 }) 163 }) 164 }) 165 }) 166 } 167 } 168 /// `"name"`. 169 const NAME: &str = "name"; 170 /// `"id"`. 171 const ID: &str = "id"; 172 /// `newtype` around `RpId` to be used to serialize `PublicKeyCredentialRpEntity`. 173 struct PublicKeyCredentialRpEntity<'a>(&'a RpId); 174 impl Serialize for PublicKeyCredentialRpEntity<'_> { 175 /// Serializes `self` to conform with 176 /// [`PublicKeyCredentialRpEntity`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrpentity). 177 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 178 where 179 S: Serializer, 180 { 181 serializer 182 .serialize_struct("PublicKeyCredentialRpEntity", 2) 183 .and_then(|mut ser| { 184 ser.serialize_field(NAME, self.0) 185 .and_then(|()| ser.serialize_field(ID, self.0).and_then(|()| ser.end())) 186 }) 187 } 188 } 189 // We implement this separately from `user_serialize` for proper documentation and example purposes. 190 impl Serialize for UserHandle<1> { 191 /// Serializes `self` to conform with 192 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-id). 193 /// 194 /// # Examples 195 /// 196 /// ``` 197 /// # use webauthn_rp::request::register::UserHandle; 198 /// # #[cfg(feature = "custom")] 199 /// // We create this manually purely for example. One should almost always 200 /// // randomly generate this (e.g., `UserHandle::new`). 201 /// let id = UserHandle::from([0]); 202 /// # #[cfg(feature = "custom")] 203 /// assert_eq!(serde_json::to_string(&id)?, r#""AA""#); 204 /// # Ok::<_, serde_json::Error>(()) 205 /// ``` 206 #[inline] 207 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 208 where 209 S: Serializer, 210 { 211 serializer.serialize_str(base64url_nopad::encode_buffer( 212 self.0.as_slice(), 213 [0; base64url_nopad::encode_len(1)].as_mut_slice(), 214 )) 215 } 216 } 217 /// Implements [`Serialize`] for [`UserHandle`] of array of length of the passed `usize` literal. 218 /// 219 /// Only [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] inclusively are allowed to be passed. 220 macro_rules! user_serialize { 221 ( $( $x:literal),* ) => { 222 $( 223 impl Serialize for UserHandle<$x> { 224 /// See [`UserHandle::serialize`]. 225 #[inline] 226 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 227 where 228 S: Serializer, 229 { 230 231 serializer.serialize_str(base64url_nopad::encode_buffer(self.0.as_slice(), [0; base64url_nopad::encode_len($x)].as_mut_slice())) 232 } 233 } 234 )* 235 }; 236 } 237 // MUST only pass `2`–[`USER_HANDLE_MAX_LEN`] inclusively. 238 user_serialize!( 239 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 240 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 241 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 242 ); 243 /// `"displayName"`. 244 const DISPLAY_NAME: &str = "displayName"; 245 impl<const LEN: usize> Serialize for PublicKeyCredentialUserEntity<'_, '_, '_, LEN> 246 where 247 UserHandle<LEN>: Serialize, 248 { 249 /// Serializes `self` to conform with 250 /// [`PublicKeyCredentialUserEntityJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentityjson). 251 /// 252 /// # Examples 253 /// 254 /// ``` 255 /// # use webauthn_rp::request::register::{PublicKeyCredentialUserEntity, UserHandle}; 256 /// # #[cfg(feature = "custom")] 257 /// // We create this manually purely for example. One should almost always 258 /// // randomly generate this (e.g., `UserHandle::new`). 259 /// let id = UserHandle::from([0]); 260 /// # #[cfg(feature = "custom")] 261 /// assert_eq!( 262 /// serde_json::to_string(&PublicKeyCredentialUserEntity { 263 /// name: "georg.cantor", 264 /// id: &id, 265 /// display_name: "Гео́рг Ка́нтор", 266 /// }).unwrap(), 267 /// r#"{"name":"georg.cantor","id":"AA","displayName":"Гео́рг Ка́нтор"}"# 268 /// ); 269 /// // The display name gets serialized as an empty string 270 /// // iff `Self::display_name` is `None`. 271 /// # #[cfg(feature = "custom")] 272 /// assert_eq!( 273 /// serde_json::to_string(&PublicKeyCredentialUserEntity { 274 /// name: "georg.cantor", 275 /// id: &id, 276 /// display_name: "", 277 /// }).unwrap(), 278 /// r#"{"name":"georg.cantor","id":"AA","displayName":""}"# 279 /// ); 280 /// # Ok::<_, webauthn_rp::AggErr>(()) 281 /// ``` 282 #[inline] 283 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 284 where 285 S: Serializer, 286 { 287 serializer 288 .serialize_struct("PublicKeyCredentialUserEntity", 3) 289 .and_then(|mut ser| { 290 ser.serialize_field(NAME, &self.name).and_then(|()| { 291 ser.serialize_field(ID, &self.id).and_then(|()| { 292 ser.serialize_field(DISPLAY_NAME, &self.display_name) 293 .and_then(|()| ser.end()) 294 }) 295 }) 296 }) 297 } 298 } 299 /// `"required"` 300 const REQUIRED: &str = "required"; 301 /// `"discouraged"` 302 const DISCOURAGED: &str = "discouraged"; 303 /// `"preferred"` 304 const PREFERRED: &str = "preferred"; 305 impl Serialize for ResidentKeyRequirement { 306 /// Serializes `self` to conform with 307 /// [`ResidentKeyRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement). 308 /// 309 /// # Examples 310 /// 311 /// ``` 312 /// # use webauthn_rp::request::register::ResidentKeyRequirement; 313 /// assert_eq!( 314 /// serde_json::to_string(&ResidentKeyRequirement::Required)?, 315 /// r#""required""# 316 /// ); 317 /// assert_eq!( 318 /// serde_json::to_string(&ResidentKeyRequirement::Discouraged)?, 319 /// r#""discouraged""# 320 /// ); 321 /// assert_eq!( 322 /// serde_json::to_string(&ResidentKeyRequirement::Preferred)?, 323 /// r#""preferred""# 324 /// ); 325 /// # Ok::<_, serde_json::Error>(()) 326 /// ``` 327 #[inline] 328 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 329 where 330 S: Serializer, 331 { 332 serializer.serialize_str(match *self { 333 Self::Required => REQUIRED, 334 Self::Discouraged => DISCOURAGED, 335 Self::Preferred => PREFERRED, 336 }) 337 } 338 } 339 /// `"platform"`. 340 const PLATFORM: &str = "platform"; 341 /// `"cross-platform"`. 342 const CROSS_PLATFORM: &str = "cross-platform"; 343 /// `"authenticatorAttachment"`. 344 const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment"; 345 /// `"residentKey"`. 346 const RESIDENT_KEY: &str = "residentKey"; 347 /// `"requireResidentKey"`. 348 const REQUIRE_RESIDENT_KEY: &str = "requireResidentKey"; 349 /// `"userVerification"`. 350 const USER_VERIFICATION: &str = "userVerification"; 351 impl Serialize for AuthenticatorSelectionCriteria { 352 /// Serializes `self` to conform with 353 /// [`AuthenticatorSelectionCriteria`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorselectioncriteria). 354 /// 355 /// # Examples 356 /// 357 /// ``` 358 /// # use webauthn_rp::{request::register::AuthenticatorSelectionCriteria, response::AuthenticatorAttachment}; 359 /// assert_eq!( 360 /// serde_json::to_string(&AuthenticatorSelectionCriteria::passkey())?, 361 /// r#"{"residentKey":"required","requireResidentKey":true,"userVerification":"required"}"# 362 /// ); 363 /// assert_eq!( 364 /// serde_json::to_string(&AuthenticatorSelectionCriteria::second_factor())?, 365 /// r#"{"residentKey":"discouraged","requireResidentKey":false,"userVerification":"discouraged"}"# 366 /// ); 367 /// let mut crit = AuthenticatorSelectionCriteria::passkey(); 368 /// crit.authenticator_attachment = AuthenticatorAttachment::CrossPlatform; 369 /// assert_eq!( 370 /// serde_json::to_string(&crit)?, 371 /// r#"{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"}"# 372 /// ); 373 /// # Ok::<_, serde_json::Error>(()) 374 /// ``` 375 #[inline] 376 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 377 where 378 S: Serializer, 379 { 380 let count = if matches!(self.authenticator_attachment, AuthenticatorAttachment::None) { 381 3 382 } else { 383 4 384 }; 385 serializer 386 .serialize_struct("AuthenticatorSelectionCriteria", count) 387 .and_then(|mut ser| { 388 if count == 3 { 389 Ok(()) 390 } else { 391 ser.serialize_field( 392 AUTHENTICATOR_ATTACHMENT, 393 if matches!( 394 self.authenticator_attachment, 395 AuthenticatorAttachment::Platform 396 ) { 397 PLATFORM 398 } else { 399 CROSS_PLATFORM 400 }, 401 ) 402 } 403 .and_then(|()| { 404 ser.serialize_field(RESIDENT_KEY, &self.resident_key) 405 .and_then(|()| { 406 ser.serialize_field( 407 REQUIRE_RESIDENT_KEY, 408 &matches!(self.resident_key, ResidentKeyRequirement::Required), 409 ) 410 .and_then(|()| { 411 ser.serialize_field(USER_VERIFICATION, &self.user_verification) 412 .and_then(|()| ser.end()) 413 }) 414 }) 415 }) 416 }) 417 } 418 } 419 /// Helper that serializes prf registration information to conform with 420 /// [`AuthenticationExtensionsPRFInputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfinputs). 421 struct Prf<'a, 'b>(PrfInput<'a, 'b>); 422 impl Serialize for Prf<'_, '_> { 423 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 424 where 425 S: Serializer, 426 { 427 serializer.serialize_struct("Prf", 1).and_then(|mut ser| { 428 ser.serialize_field("eval", &self.0) 429 .and_then(|()| ser.end()) 430 }) 431 } 432 } 433 /// `credProps` key name. 434 const CRED_PROPS: &str = "credProps"; 435 /// `minPinLength` key name. 436 const MIN_PIN_LENGTH: &str = "minPinLength"; 437 /// `prf` key name. 438 const PRF: &str = "prf"; 439 /// `credentialProtectionPolicy` key name. 440 const CREDENTIAL_PROTECTION_POLICY: &str = "credentialProtectionPolicy"; 441 /// `enforceCredentialProtectionPolicy` key name. 442 const ENFORCE_CREDENTIAL_PROTECTION_POLICY: &str = "enforceCredentialProtectionPolicy"; 443 /// `"userVerificationOptional"`. 444 const USER_VERIFICATION_OPTIONAL: &str = "userVerificationOptional"; 445 /// `"userVerificationOptionalWithCredentialIDList"`. 446 const USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST: &str = 447 "userVerificationOptionalWithCredentialIDList"; 448 /// `"userVerificationRequired"`. 449 const USER_VERIFICATION_REQUIRED: &str = "userVerificationRequired"; 450 impl Serialize for Extension<'_, '_> { 451 /// Serializes `self` to conform with 452 /// [`AuthenticationExtensionsClientInputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientinputsjson). 453 /// 454 /// # Examples 455 /// 456 /// ``` 457 /// # use webauthn_rp::request::{ 458 /// # register::{CredProtect, Extension, FourToSixtyThree}, 459 /// # PrfInput, ExtensionInfo, ExtensionReq, 460 /// # }; 461 /// assert_eq!(serde_json::to_string(&Extension::default())?, r#"{}"#); 462 /// assert_eq!( 463 /// serde_json::to_string(&Extension { 464 /// cred_props: Some(ExtensionReq::Allow), 465 /// cred_protect: CredProtect::UserVerificationRequired(false, ExtensionInfo::RequireEnforceValue), 466 /// min_pin_length: Some((FourToSixtyThree::Sixteen, ExtensionInfo::AllowDontEnforceValue)), 467 /// prf: Some((PrfInput { first: [0].as_slice(), second: None, }, ExtensionInfo::AllowEnforceValue)) 468 /// })?, 469 /// r#"{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"AA"}}}"# 470 /// ); 471 /// # Ok::<_, serde_json::Error>(()) 472 /// ``` 473 #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] 474 #[expect( 475 clippy::arithmetic_side_effects, 476 reason = "comment explains how overflow is not possible" 477 )] 478 #[inline] 479 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 480 where 481 S: Serializer, 482 { 483 // The max is 1 + 2 + 1 + 1 = 5, so overflow is no concern. 484 let count = usize::from(self.cred_props.is_some()) 485 + if matches!(self.cred_protect, CredProtect::None) { 486 0 487 } else { 488 2 489 } 490 + usize::from(self.min_pin_length.is_some()) 491 + usize::from(self.prf.is_some()); 492 serializer 493 .serialize_struct("Extension", count) 494 .and_then(|mut ser| { 495 self.cred_props 496 .map_or(Ok(()), |_| ser.serialize_field(CRED_PROPS, &true)) 497 .and_then(|()| { 498 if matches!(self.cred_protect, CredProtect::None) { 499 Ok(()) 500 } else { 501 let enforce_policy; 502 // [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension) 503 // is serialized by serializing its fields directly and not as a map of fields. 504 ser.serialize_field( 505 CREDENTIAL_PROTECTION_POLICY, 506 match self.cred_protect { 507 CredProtect::None => unreachable!( 508 "Extensions is incorrectly serializing credProtect" 509 ), 510 CredProtect::UserVerificationOptional(enforce, _) => { 511 enforce_policy = enforce; 512 USER_VERIFICATION_OPTIONAL 513 } 514 CredProtect::UserVerificationOptionalWithCredentialIdList( 515 enforce, 516 _, 517 ) => { 518 enforce_policy = enforce; 519 USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST 520 } 521 CredProtect::UserVerificationRequired(enforce, _) => { 522 enforce_policy = enforce; 523 USER_VERIFICATION_REQUIRED 524 } 525 }, 526 ) 527 .and_then(|()| { 528 ser.serialize_field( 529 ENFORCE_CREDENTIAL_PROTECTION_POLICY, 530 &enforce_policy, 531 ) 532 }) 533 } 534 .and_then(|()| { 535 self.min_pin_length 536 .map_or(Ok(()), |_| ser.serialize_field(MIN_PIN_LENGTH, &true)) 537 .and_then(|()| { 538 self.prf 539 .map_or(Ok(()), |(prf, _)| { 540 ser.serialize_field(PRF, &Prf(prf)) 541 }) 542 .and_then(|()| ser.end()) 543 }) 544 }) 545 }) 546 }) 547 } 548 } 549 /// `"rp"` 550 const RP: &str = "rp"; 551 /// `"user"` 552 const USER: &str = "user"; 553 /// `"challenge"` 554 const CHALLENGE: &str = "challenge"; 555 /// `"pubKeyCredParams"` 556 const PUB_KEY_CRED_PARAMS: &str = "pubKeyCredParams"; 557 /// `"timeout"` 558 const TIMEOUT: &str = "timeout"; 559 /// `"excludeCredentials"` 560 const EXCLUDE_CREDENTIALS: &str = "excludeCredentials"; 561 /// `"authenticatorSelection"` 562 const AUTHENTICATOR_SELECTION: &str = "authenticatorSelection"; 563 /// `"hints"` 564 const HINTS: &str = "hints"; 565 /// `"attestation"` 566 const ATTESTATION: &str = "attestation"; 567 /// `"attestationFormats"` 568 const ATTESTATION_FORMATS: &str = "attestationFormats"; 569 /// `"extensions"` 570 const EXTENSIONS: &str = "extensions"; 571 /// "none". 572 const NONE: &str = "none"; 573 impl<'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> Serialize 574 for PublicKeyCredentialCreationOptions< 575 '_, 576 'user_name, 577 'user_display_name, 578 'user_id, 579 '_, 580 '_, 581 USER_LEN, 582 > 583 where 584 PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>: Serialize, 585 { 586 /// Serializes `self` to conform with 587 /// [`PublicKeyCredentialCreationOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptionsjson). 588 #[inline] 589 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 590 where 591 S: Serializer, 592 { 593 serializer 594 .serialize_struct("PublicKeyCredentialCreationOptions", 11) 595 .and_then(|mut ser| { 596 ser.serialize_field(RP, &PublicKeyCredentialRpEntity(self.rp_id)) 597 .and_then(|()| { 598 ser.serialize_field(USER, &self.user).and_then(|()| { 599 ser.serialize_field(CHALLENGE, &self.challenge) 600 .and_then(|()| { 601 ser.serialize_field( 602 PUB_KEY_CRED_PARAMS, 603 &self.pub_key_cred_params, 604 ) 605 .and_then(|()| { 606 ser.serialize_field(TIMEOUT, &self.timeout).and_then(|()| { 607 ser.serialize_field( 608 EXCLUDE_CREDENTIALS, 609 self.exclude_credentials.as_slice(), 610 ) 611 .and_then( 612 |()| { 613 ser.serialize_field( 614 AUTHENTICATOR_SELECTION, 615 &self.authenticator_selection, 616 ) 617 .and_then(|()| { 618 ser.serialize_field(HINTS, &self.hints) 619 .and_then(|()| { 620 ser.serialize_field( 621 ATTESTATION, 622 NONE, 623 ) 624 .and_then(|()| { 625 ser.serialize_field( 626 ATTESTATION_FORMATS, 627 [NONE].as_slice(), 628 ) 629 .and_then(|()| { 630 ser.serialize_field( 631 EXTENSIONS, 632 &self.extensions, 633 ) 634 .and_then(|()| ser.end()) 635 }) 636 }) 637 }) 638 }) 639 }, 640 ) 641 }) 642 }) 643 }) 644 }) 645 }) 646 }) 647 } 648 } 649 /// `"mediation"`. 650 const MEDIATION: &str = "mediation"; 651 /// `"publicKey"`. 652 const PUBLIC_KEY_NO_HYPEN: &str = "publicKey"; 653 impl< 654 'rp_id, 655 'user_name, 656 'user_display_name, 657 'user_id, 658 'prf_first, 659 'prf_second, 660 const USER_LEN: usize, 661 > Serialize 662 for CredentialCreationOptions< 663 'rp_id, 664 'user_name, 665 'user_display_name, 666 'user_id, 667 'prf_first, 668 'prf_second, 669 USER_LEN, 670 > 671 where 672 PublicKeyCredentialCreationOptions< 673 'rp_id, 674 'user_name, 675 'user_display_name, 676 'user_id, 677 'prf_first, 678 'prf_second, 679 USER_LEN, 680 >: Serialize, 681 { 682 /// Serializes `self` to conform with 683 /// [`CredentialCreationOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialcreationoptions). 684 /// 685 /// Note [`signal`](https://www.w3.org/TR/credential-management-1/#dom-credentialcreationoptions-signal) 686 /// is not present, and [`publicKey`](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry) 687 /// is serialized according to [`PublicKeyCredentialCreationOptions::serialize`]. 688 #[inline] 689 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 690 where 691 S: Serializer, 692 { 693 serializer 694 .serialize_struct("CredentialCreationOptions", 2) 695 .and_then(|mut ser| { 696 ser.serialize_field(MEDIATION, &self.mediation) 697 .and_then(|()| { 698 ser.serialize_field(PUBLIC_KEY_NO_HYPEN, &self.public_key) 699 .and_then(|()| ser.end()) 700 }) 701 }) 702 } 703 } 704 impl< 705 'rp_id, 706 'user_name, 707 'user_display_name, 708 'user_id, 709 'prf_first, 710 'prf_second, 711 const USER_LEN: usize, 712 > Serialize 713 for RegistrationClientState< 714 'rp_id, 715 'user_name, 716 'user_display_name, 717 'user_id, 718 'prf_first, 719 'prf_second, 720 USER_LEN, 721 > 722 where 723 CredentialCreationOptions< 724 'rp_id, 725 'user_name, 726 'user_display_name, 727 'user_id, 728 'prf_first, 729 'prf_second, 730 USER_LEN, 731 >: Serialize, 732 { 733 /// Serializes `self` according to [`CredentialCreationOptions::serialize`]. 734 /// 735 /// # Examples 736 /// 737 /// ``` 738 /// # #[cfg(all(feature = "bin", feature = "custom"))] 739 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 740 /// # use webauthn_rp::{ 741 /// # request::{ 742 /// # register::{ 743 /// # FourToSixtyThree, UserHandle64, CredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle 744 /// # }, 745 /// # AsciiDomain, ExtensionInfo, Hints, PublicKeyCredentialHint, RpId, PublicKeyCredentialDescriptor, Credentials, UserVerificationRequirement, 746 /// # }, 747 /// # response::{AuthTransports, AuthenticatorAttachment, CredentialId}, 748 /// # }; 749 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 750 /// /// from the database. 751 /// # #[cfg(all(feature = "bin", feature = "custom"))] 752 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 753 /// // ⋮ 754 /// # AuthTransports::decode(32) 755 /// } 756 /// let mut creds = Vec::with_capacity(1); 757 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 758 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 759 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 760 /// # #[cfg(all(feature = "bin", feature = "custom"))] 761 /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?; 762 /// # #[cfg(all(feature = "bin", feature = "custom"))] 763 /// let transports = get_transports((&id).into())?; 764 /// # #[cfg(all(feature = "bin", feature = "custom"))] 765 /// creds.push(PublicKeyCredentialDescriptor { id, transports }); 766 /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); 767 /// let user_handle = UserHandle64::new(); 768 /// let mut options = CredentialCreationOptions::passkey(&rp_id, PublicKeyCredentialUserEntity { name: "pierre.de.fermat", id: &user_handle, display_name: "Pierre de Fermat", }, creds); 769 /// options.public_key.authenticator_selection.authenticator_attachment = AuthenticatorAttachment::None; 770 /// options.public_key.hints = Hints::EMPTY.add(PublicKeyCredentialHint::SecurityKey); 771 /// options.public_key.extensions.min_pin_length = Some((FourToSixtyThree::Sixteen, ExtensionInfo::RequireEnforceValue)); 772 /// # #[cfg(all(feature = "bin", feature = "custom"))] 773 /// let client_state = serde_json::to_string(&options.start_ceremony()?.1).unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState::serialize")); 774 /// let json = serde_json::json!({ 775 /// "mediation":"required", 776 /// "publicKey":{ 777 /// "rp":{ 778 /// "name":"example.com", 779 /// "id":"example.com" 780 /// }, 781 /// "user":{ 782 /// "name":"pierre.de.fermat", 783 /// "id":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 784 /// "displayName":"Pierre de Fermat" 785 /// }, 786 /// "challenge":"AAAAAAAAAAAAAAAAAAAAAA", 787 /// "pubKeyCredParams":[ 788 /// { 789 /// "type":"public-key", 790 /// "alg":-50 791 /// }, 792 /// { 793 /// "type":"public-key", 794 /// "alg":-49 795 /// }, 796 /// { 797 /// "type":"public-key", 798 /// "alg":-48 799 /// }, 800 /// { 801 /// "type":"public-key", 802 /// "alg":-8 803 /// }, 804 /// { 805 /// "type":"public-key", 806 /// "alg":-7 807 /// }, 808 /// { 809 /// "type":"public-key", 810 /// "alg":-35 811 /// }, 812 /// { 813 /// "type":"public-key", 814 /// "alg":-257 815 /// }, 816 /// ], 817 /// "timeout":300000, 818 /// "excludeCredentials":[ 819 /// { 820 /// "type":"public-key", 821 /// "id":"AAAAAAAAAAAAAAAAAAAAAA", 822 /// "transports":["usb"] 823 /// } 824 /// ], 825 /// "authenticatorSelection":{ 826 /// "residentKey":"required", 827 /// "requireResidentKey":true, 828 /// "userVerification":"required" 829 /// }, 830 /// "hints":[ 831 /// "security-key" 832 /// ], 833 /// "attestation":"none", 834 /// "attestationFormats":[ 835 /// "none" 836 /// ], 837 /// "extensions":{ 838 /// "credentialProtectionPolicy":"userVerificationRequired", 839 /// "enforceCredentialProtectionPolicy":false, 840 /// "minPinLength":true 841 /// } 842 /// } 843 /// }).to_string(); 844 /// // Since `Challenge`s are randomly generated, we don't know what it will be. 845 /// // Similarly since we randomly generated a 64-byte `UserHandle`, we don't know what 846 /// // it will be; thus we test the JSON string for everything except those two. 847 /// # #[cfg(all(feature = "bin", feature = "custom"))] 848 /// assert_eq!(client_state.get(..124), json.get(..124)); 849 /// # #[cfg(all(feature = "bin", feature = "custom"))] 850 /// assert_eq!(client_state.get(210..259), json.get(210..259)); 851 /// # #[cfg(all(feature = "bin", feature = "custom"))] 852 /// assert_eq!(client_state.get(281..), json.get(281..)); 853 /// # Ok::<_, webauthn_rp::AggErr>(()) 854 /// ``` 855 #[inline] 856 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 857 where 858 S: Serializer, 859 { 860 self.0.serialize(serializer) 861 } 862 } 863 impl<'de, const LEN: usize> Deserialize<'de> for UserHandle<LEN> 864 where 865 Self: Default, 866 { 867 /// Deserializes [`prim@str`] based on 868 /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-userhandle). 869 /// 870 /// # Examples 871 /// 872 /// ``` 873 /// # use webauthn_rp::request::register::{UserHandle, USER_HANDLE_MIN_LEN}; 874 /// # #[cfg(feature = "custom")] 875 /// assert_eq!( 876 /// serde_json::from_str::<UserHandle<USER_HANDLE_MIN_LEN>>(r#""AA""#)?, 877 /// UserHandle::from([0]) 878 /// ); 879 /// # Ok::<_, serde_json::Error>(()) 880 ///``` 881 #[inline] 882 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 883 where 884 D: Deserializer<'de>, 885 { 886 /// `Visitor` for `UserHandle`. 887 struct UserHandleVisitor<const L: usize>; 888 impl<const L: usize> Visitor<'_> for UserHandleVisitor<L> 889 where 890 UserHandle<L>: Default, 891 { 892 type Value = UserHandle<L>; 893 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 894 formatter.write_str("UserHandle") 895 } 896 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 897 where 898 E: Error, 899 { 900 if base64url_nopad::encode_len(L) == v.len() { 901 let mut data = [0; L]; 902 base64url_nopad::decode_buffer_exact(v.as_bytes(), data.as_mut_slice()) 903 .map_err(E::custom) 904 .map(|()| UserHandle(data)) 905 } else { 906 Err(E::invalid_value( 907 Unexpected::Str(v), 908 &format!("{L} bytes encoded in base64url without padding").as_str(), 909 )) 910 } 911 } 912 } 913 deserializer.deserialize_str(UserHandleVisitor) 914 } 915 } 916 impl<'de> Deserialize<'de> for CoseAlgorithmIdentifier { 917 /// Deserializes [`i16`] based on 918 /// [COSE Algorithms](https://www.iana.org/assignments/cose/cose.xhtml#algorithms). 919 #[inline] 920 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 921 where 922 D: Deserializer<'de>, 923 { 924 /// `Visitor` for `CoseAlgorithmIdentifier`. 925 /// 926 /// We visit all signed integral types sans `i8` just in case a `Deserializer` only implements one of them. 927 struct CoseAlgorithmIdentifierVisitor; 928 impl Visitor<'_> for CoseAlgorithmIdentifierVisitor { 929 type Value = CoseAlgorithmIdentifier; 930 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 931 formatter.write_str("CoseAlgorithmIdentifier") 932 } 933 fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E> 934 where 935 E: Error, 936 { 937 match v { 938 RS256 => Ok(CoseAlgorithmIdentifier::Rs256), 939 MLDSA87 => Ok(CoseAlgorithmIdentifier::Mldsa87), 940 MLDSA65 => Ok(CoseAlgorithmIdentifier::Mldsa65), 941 MLDSA44 => Ok(CoseAlgorithmIdentifier::Mldsa44), 942 ES384 => Ok(CoseAlgorithmIdentifier::Es384), 943 EDDSA => Ok(CoseAlgorithmIdentifier::Eddsa), 944 ES256 => Ok(CoseAlgorithmIdentifier::Es256), 945 _ => Err(E::invalid_value( 946 Unexpected::Signed(i64::from(v)), 947 &format!( 948 "{MLDSA87}, {MLDSA65}, {MLDSA44}, {EDDSA}, {ES256}, {ES384}, or {RS256}" 949 ) 950 .as_str(), 951 )), 952 } 953 } 954 fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E> 955 where 956 E: Error, 957 { 958 i16::try_from(v) 959 .map_err(E::custom) 960 .and_then(|val| self.visit_i16(val)) 961 } 962 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E> 963 where 964 E: Error, 965 { 966 i16::try_from(v) 967 .map_err(E::custom) 968 .and_then(|val| self.visit_i16(val)) 969 } 970 } 971 deserializer.deserialize_i16(CoseAlgorithmIdentifierVisitor) 972 } 973 } 974 /// Helper to deserialize `PublicKeyCredentialRpEntity` with an optional `RpId`. 975 /// 976 /// Used in [`ClientCredentialCreationOptions::deserialize`]. 977 struct PublicKeyCredentialRpEntityHelper(Option<RpId>); 978 impl<'de> Deserialize<'de> for PublicKeyCredentialRpEntityHelper { 979 /// Conforms to the following schema: 980 /// 981 /// ```json 982 /// { 983 /// "id": null | <RpId>, 984 /// "name": null | <RpId> | <Nickname> 985 /// } 986 /// ``` 987 /// 988 /// None of the fields are required, and missing fields are interpreted the same as fields 989 /// with `null` values. 990 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 991 where 992 D: Deserializer<'de>, 993 { 994 /// `Visitor` for `PublicKeyCredentialRpEntityHelper`. 995 struct PublicKeyCredentialRpEntityHelperVisitor; 996 impl<'d> Visitor<'d> for PublicKeyCredentialRpEntityHelperVisitor { 997 type Value = PublicKeyCredentialRpEntityHelper; 998 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 999 formatter.write_str("PublicKeyCredentialRpEntityHelper") 1000 } 1001 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1002 where 1003 A: MapAccess<'d>, 1004 { 1005 /// Field for `PublicKeyCredentialRpEntityHelper`. 1006 enum Field { 1007 /// `id`. 1008 Id, 1009 /// `name`. 1010 Name, 1011 } 1012 impl<'e> Deserialize<'e> for Field { 1013 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1014 where 1015 D: Deserializer<'e>, 1016 { 1017 /// `Visitor` for `Field`. 1018 struct FieldVisitor; 1019 impl Visitor<'_> for FieldVisitor { 1020 type Value = Field; 1021 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1022 write!(formatter, "'{ID}' or '{NAME}'") 1023 } 1024 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1025 where 1026 E: Error, 1027 { 1028 match v { 1029 ID => Ok(Field::Id), 1030 NAME => Ok(Field::Name), 1031 _ => Err(E::unknown_field(v, FIELDS)), 1032 } 1033 } 1034 } 1035 deserializer.deserialize_identifier(FieldVisitor) 1036 } 1037 } 1038 /// Helper to deserialize `name`. 1039 struct Name; 1040 impl<'e> Deserialize<'e> for Name { 1041 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1042 where 1043 D: Deserializer<'e>, 1044 { 1045 /// `Visitor` for `Name`. 1046 struct NameVisitor; 1047 impl Visitor<'_> for NameVisitor { 1048 type Value = Name; 1049 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1050 formatter.write_str("RpId name") 1051 } 1052 fn visit_str<E>(self, _: &str) -> Result<Self::Value, E> 1053 where 1054 E: Error, 1055 { 1056 Ok(Name) 1057 } 1058 } 1059 deserializer.deserialize_str(NameVisitor) 1060 } 1061 } 1062 let mut id = None; 1063 let mut name = false; 1064 while let Some(key) = map.next_key()? { 1065 match key { 1066 Field::Id => { 1067 if id.is_some() { 1068 return Err(Error::duplicate_field(ID)); 1069 } 1070 id = map.next_value::<Option<_>>().map(Some)?; 1071 } 1072 Field::Name => { 1073 if name { 1074 return Err(Error::duplicate_field(NAME)); 1075 } 1076 name = map.next_value::<Option<Name>>().map(|_n| true)?; 1077 } 1078 } 1079 } 1080 Ok(PublicKeyCredentialRpEntityHelper(id.flatten())) 1081 } 1082 } 1083 /// Fields for `PublicKeyCredentialRpEntityHelper`. 1084 const FIELDS: &[&str; 2] = &[ID, NAME]; 1085 deserializer.deserialize_struct( 1086 "PublicKeyCredentialRpEntityHelper", 1087 FIELDS, 1088 PublicKeyCredentialRpEntityHelperVisitor, 1089 ) 1090 } 1091 } 1092 /// Similar to [`PublicKeyCredentialUserEntity`] except the [`UserHandle`] is owned, and all fields are 1093 /// optional. 1094 /// 1095 /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`]. 1096 #[derive(Debug, Default)] 1097 pub struct PublicKeyCredentialUserEntityOwned<const LEN: usize> { 1098 /// See [`PublicKeyCredentialUserEntity::name`]. 1099 pub name: Option<String>, 1100 /// See [`PublicKeyCredentialUserEntity::id`]. 1101 pub id: Option<UserHandle<LEN>>, 1102 /// See [`PublicKeyCredentialUserEntity::display_name`]. 1103 pub display_name: Option<String>, 1104 } 1105 /// Error returned when converting a [`PublicKeyCredentialUserEntityOwned`] into a 1106 /// [`PublicKeyCredentialUserEntity`] (e.g., via [`PublicKeyCredentialUserEntityOwned::with_id`]). 1107 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 1108 pub enum PublicKeyCredentialUserEntityOwnedErr { 1109 /// Variant returned when [`PublicKeyCredentialUserEntityOwned::name`] is `None`. 1110 MissingName, 1111 /// Variant returned when [`PublicKeyCredentialUserEntityOwned::id`] is `None`. 1112 MissingId, 1113 /// Variant returned when [`PublicKeyCredentialUserEntityOwned::display_name`] is `None`. 1114 MissingDisplayName, 1115 } 1116 impl Display for PublicKeyCredentialUserEntityOwnedErr { 1117 #[inline] 1118 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1119 f.write_str(match *self { 1120 Self::MissingName => "user entity info did not have a username", 1121 Self::MissingId => "user entity info did not have a user handle", 1122 Self::MissingDisplayName => "user entity info did not have a user display name", 1123 }) 1124 } 1125 } 1126 impl E for PublicKeyCredentialUserEntityOwnedErr {} 1127 impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<LEN> { 1128 /// Returns a `PublicKeyCredentialUserEntity` based on `self`. 1129 /// 1130 /// # Errors 1131 /// 1132 /// Errors iff any of the fields in `self` are `None`. 1133 #[inline] 1134 pub fn as_entity( 1135 &self, 1136 ) -> Result<PublicKeyCredentialUserEntity<'_, '_, '_, LEN>, PublicKeyCredentialUserEntityOwnedErr> 1137 { 1138 self.name 1139 .as_ref() 1140 .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingName) 1141 .and_then(|username| { 1142 self.id 1143 .as_ref() 1144 .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingId) 1145 .and_then(|id| { 1146 self.display_name 1147 .as_ref() 1148 .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingDisplayName) 1149 .map(|display| PublicKeyCredentialUserEntity { 1150 name: username, 1151 id, 1152 display_name: display, 1153 }) 1154 }) 1155 }) 1156 } 1157 /// Returns a `PublicKeyCredentialUserEntity` based on `self` and `id`. 1158 /// 1159 /// Note `id` is used _unconditionally_ regardless if [`Self::id`] is `Some`. 1160 /// 1161 /// # Errors 1162 /// 1163 /// Errors iff [`Self::name`] or [`Self::display_name`] are `None`. 1164 #[inline] 1165 pub fn with_id<'id>( 1166 &self, 1167 id: &'id UserHandle<LEN>, 1168 ) -> Result< 1169 PublicKeyCredentialUserEntity<'_, '_, 'id, LEN>, 1170 PublicKeyCredentialUserEntityOwnedErr, 1171 > { 1172 self.name 1173 .as_ref() 1174 .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingName) 1175 .and_then(|username| { 1176 self.display_name 1177 .as_ref() 1178 .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingDisplayName) 1179 .map(|display| PublicKeyCredentialUserEntity { 1180 name: username, 1181 id, 1182 display_name: display, 1183 }) 1184 }) 1185 } 1186 /// Returns a `PublicKeyCredentialUserEntity` based on `self`, `name`, and `display_name`. 1187 /// 1188 /// Note `name` and `display_name` are used _unconditionally_ regardless if [`Self::name`] or 1189 /// [`Self::display_name`] are `Some`. 1190 /// 1191 /// # Errors 1192 /// 1193 /// Errors iff [`Self::id`] is `None`. 1194 #[inline] 1195 pub fn with_name_and_display_name<'name, 'display_name>( 1196 &self, 1197 name: &'name str, 1198 display_name: &'display_name str, 1199 ) -> Result< 1200 PublicKeyCredentialUserEntity<'name, 'display_name, '_, LEN>, 1201 PublicKeyCredentialUserEntityOwnedErr, 1202 > { 1203 self.id 1204 .as_ref() 1205 .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingId) 1206 .map(|id| PublicKeyCredentialUserEntity { 1207 name, 1208 id, 1209 display_name, 1210 }) 1211 } 1212 } 1213 impl<'de, const LEN: usize> Deserialize<'de> for PublicKeyCredentialUserEntityOwned<LEN> 1214 where 1215 UserHandle<LEN>: Deserialize<'de>, 1216 { 1217 /// Deserializes a `struct` according to 1218 /// [`PublicKeyCredentialUserEntityJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentityjson). 1219 /// 1220 /// Note none of the fields are required and all of them are allowed to be `null`. 1221 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-id) is deserialized 1222 /// according to [`UserHandle::deserialize`], 1223 /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-name) deserializes 1224 /// [`prim@str`]. 1225 /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-displayname) 1226 /// deserializes [`prim@str`]. 1227 /// 1228 /// Unknown or duplicate fields lead to an error. Missing fields are interpreted the same as if the field 1229 /// were assigned `null`. 1230 /// 1231 /// # Examples 1232 /// 1233 /// ``` 1234 /// # use webauthn_rp::request::register::ser::PublicKeyCredentialUserEntityOwned; 1235 /// let val = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<16>>(r#"{"name":"paul.erdos","displayName":"Erdős Pál"}"#)?; 1236 /// assert!(val.name.is_some_and(|name| name == "paul.erdos")); 1237 /// assert!(val.display_name.is_some_and(|display| display == "Erdős Pál")); 1238 /// assert!(val.id.is_none()); 1239 /// # Ok::<_, serde_json::Error>(()) 1240 /// ``` 1241 #[inline] 1242 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1243 where 1244 D: Deserializer<'de>, 1245 { 1246 /// `Visitor` for `PublicKeyCredentialUserEntityOwned`. 1247 struct PublicKeyCredentialUserEntityOwnedVisitor<const L: usize>; 1248 impl<'d, const L: usize> Visitor<'d> for PublicKeyCredentialUserEntityOwnedVisitor<L> 1249 where 1250 UserHandle<L>: Deserialize<'d>, 1251 { 1252 type Value = PublicKeyCredentialUserEntityOwned<L>; 1253 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1254 formatter.write_str("PublicKeyCredentialUserEntityOwned") 1255 } 1256 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1257 where 1258 A: MapAccess<'d>, 1259 { 1260 /// Field for `PublicKeyCredentialUserEntityOwned`. 1261 enum Field { 1262 /// `id`. 1263 Id, 1264 /// `name`. 1265 Name, 1266 /// `displayName` 1267 DisplayName, 1268 } 1269 impl<'e> Deserialize<'e> for Field { 1270 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1271 where 1272 D: Deserializer<'e>, 1273 { 1274 /// `Visitor` for `Field`. 1275 struct FieldVisitor; 1276 impl Visitor<'_> for FieldVisitor { 1277 type Value = Field; 1278 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1279 write!(formatter, "'{ID}', '{NAME}', or '{DISPLAY_NAME}'") 1280 } 1281 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1282 where 1283 E: Error, 1284 { 1285 match v { 1286 ID => Ok(Field::Id), 1287 NAME => Ok(Field::Name), 1288 DISPLAY_NAME => Ok(Field::DisplayName), 1289 _ => Err(E::unknown_field(v, FIELDS)), 1290 } 1291 } 1292 } 1293 deserializer.deserialize_identifier(FieldVisitor) 1294 } 1295 } 1296 let mut user_handle = None; 1297 let mut username = None; 1298 let mut display = None; 1299 while let Some(key) = map.next_key()? { 1300 match key { 1301 Field::Id => { 1302 if user_handle.is_some() { 1303 return Err(Error::duplicate_field(ID)); 1304 } 1305 user_handle = map.next_value::<Option<_>>().map(Some)?; 1306 } 1307 Field::Name => { 1308 if username.is_some() { 1309 return Err(Error::duplicate_field(NAME)); 1310 } 1311 username = map.next_value::<Option<_>>().map(Some)?; 1312 } 1313 Field::DisplayName => { 1314 if display.is_some() { 1315 return Err(Error::duplicate_field(DISPLAY_NAME)); 1316 } 1317 display = map.next_value::<Option<_>>().map(Some)?; 1318 } 1319 } 1320 } 1321 Ok(PublicKeyCredentialUserEntityOwned { 1322 id: user_handle.flatten(), 1323 name: username.flatten(), 1324 display_name: display.flatten(), 1325 }) 1326 } 1327 } 1328 /// Fields for `PublicKeyCredentialUserEntityOwned`. 1329 const FIELDS: &[&str; 3] = &[ID, NAME, DISPLAY_NAME]; 1330 deserializer.deserialize_struct( 1331 "PublicKeyCredentialUserEntityOwned", 1332 FIELDS, 1333 PublicKeyCredentialUserEntityOwnedVisitor, 1334 ) 1335 } 1336 } 1337 /// `newtype` around `CoseAlgorithmIdentifier`. 1338 struct PubParam(CoseAlgorithmIdentifier); 1339 impl<'de> Deserialize<'de> for PubParam { 1340 /// Conforms to the following schema: 1341 /// 1342 /// ```json 1343 /// { 1344 /// "alg": <CoseAlgorithmIdentifier>, 1345 /// "type": "public-key", 1346 /// } 1347 /// ``` 1348 /// 1349 /// `"alg"` is required. 1350 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1351 where 1352 D: Deserializer<'de>, 1353 { 1354 /// `Visitor` for `PubParam`. 1355 struct PubParamVisitor; 1356 impl<'d> Visitor<'d> for PubParamVisitor { 1357 type Value = PubParam; 1358 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1359 formatter.write_str("PubParam") 1360 } 1361 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1362 where 1363 A: MapAccess<'d>, 1364 { 1365 /// Field for `PubParam`. 1366 enum Field { 1367 /// `"type"`. 1368 Type, 1369 /// `"alg"`. 1370 Alg, 1371 } 1372 impl<'e> Deserialize<'e> for Field { 1373 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1374 where 1375 D: Deserializer<'e>, 1376 { 1377 /// `Visitor` for `Field`. 1378 struct FieldVisitor; 1379 impl Visitor<'_> for FieldVisitor { 1380 type Value = Field; 1381 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1382 write!(formatter, "'{TYPE}' or '{ALG}'") 1383 } 1384 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1385 where 1386 E: Error, 1387 { 1388 match v { 1389 TYPE => Ok(Field::Type), 1390 ALG => Ok(Field::Alg), 1391 _ => Err(E::unknown_field(v, FIELDS)), 1392 } 1393 } 1394 } 1395 deserializer.deserialize_identifier(FieldVisitor) 1396 } 1397 } 1398 let mut typ = false; 1399 let mut alg = None; 1400 while let Some(key) = map.next_key()? { 1401 match key { 1402 Field::Type => { 1403 if typ { 1404 return Err(Error::duplicate_field(TYPE)); 1405 } 1406 typ = map.next_value::<Type>().map(|_t| true)?; 1407 } 1408 Field::Alg => { 1409 if alg.is_some() { 1410 return Err(Error::duplicate_field(ALG)); 1411 } 1412 alg = map.next_value().map(Some)?; 1413 } 1414 } 1415 } 1416 alg.ok_or_else(|| Error::missing_field(ALG)).map(PubParam) 1417 } 1418 } 1419 /// Fields for `PubParam`. 1420 const FIELDS: &[&str; 2] = &[TYPE, ALG]; 1421 deserializer.deserialize_struct("PubParam", FIELDS, PubParamVisitor) 1422 } 1423 } 1424 impl<'de> Deserialize<'de> for CoseAlgorithmIdentifiers { 1425 /// Deserializes a sequence based on 1426 /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-pubkeycredparams) 1427 /// except [`type`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialparameters-type) is not required. 1428 /// 1429 /// Note the sequence of [`CoseAlgorithmIdentifier`]s MUST match [`CoseAlgorithmIdentifier::cmp`] or an 1430 /// error will occur (e.g., if [`CoseAlgorithmIdentifier::Mldsa87`] exists, then it must appear first). 1431 /// 1432 /// An empty sequence will be treated as [`Self::ALL`]. 1433 /// 1434 /// Unknown or duplicate fields lead to an error. 1435 /// 1436 /// # Examples 1437 /// 1438 /// ``` 1439 /// # use webauthn_rp::request::register::CoseAlgorithmIdentifiers; 1440 /// assert!(serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":"public-key","alg":-50},{"type":"public-key","alg":-49},{"type":"public-key","alg":-48},{"type":"public-key","alg":-8},{"type":"public-key","alg":-7},{"type":"public-key","alg":-35},{"type":"public-key","alg":-257}]"#).is_ok()); 1441 /// ``` 1442 #[expect(clippy::too_many_lines, reason = "132 is fine")] 1443 #[inline] 1444 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1445 where 1446 D: Deserializer<'de>, 1447 { 1448 /// `Visitor` for `CoseAlgorithmIdentifiers`. 1449 struct CoseAlgorithmIdentifiersVisitor; 1450 impl<'d> Visitor<'d> for CoseAlgorithmIdentifiersVisitor { 1451 type Value = CoseAlgorithmIdentifiers; 1452 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1453 formatter.write_str("CoseAlgorithmIdentifiers") 1454 } 1455 #[expect(clippy::too_many_lines, reason = "118 is fine")] 1456 #[expect(clippy::else_if_without_else, reason = "prefer it this way")] 1457 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 1458 where 1459 A: SeqAccess<'d>, 1460 { 1461 let mut mldsa87 = false; 1462 let mut mldsa65 = false; 1463 let mut mldsa44 = false; 1464 let mut eddsa = false; 1465 let mut es256 = false; 1466 let mut es384 = false; 1467 let mut rs256 = false; 1468 while let Some(elem) = seq.next_element::<PubParam>()? { 1469 match elem.0 { 1470 CoseAlgorithmIdentifier::Mldsa87 => { 1471 if mldsa87 { 1472 return Err(Error::custom( 1473 "pubKeyCredParams contained duplicate ML-DSA-87 values", 1474 )); 1475 } else if mldsa65 || mldsa44 || eddsa || es256 || es384 || rs256 { 1476 return Err(Error::custom( 1477 "pubKeyCredParams contained ML-DSA-87, but it wasn't the first value", 1478 )); 1479 } 1480 mldsa87 = true; 1481 } 1482 CoseAlgorithmIdentifier::Mldsa65 => { 1483 if mldsa65 { 1484 return Err(Error::custom( 1485 "pubKeyCredParams contained duplicate ML-DSA-65 values", 1486 )); 1487 } else if mldsa44 || eddsa || es256 || es384 || rs256 { 1488 return Err(Error::custom( 1489 "pubKeyCredParams contained ML-DSA-65, but it was preceded by Mldsa44, Eddsa, Es256, Es384, or Rs256", 1490 )); 1491 } 1492 mldsa65 = true; 1493 } 1494 CoseAlgorithmIdentifier::Mldsa44 => { 1495 if mldsa44 { 1496 return Err(Error::custom( 1497 "pubKeyCredParams contained duplicate ML-DSA-44 values", 1498 )); 1499 } else if eddsa || es256 || es384 || rs256 { 1500 return Err(Error::custom( 1501 "pubKeyCredParams contained ML-DSA-44, but it was preceded by Eddsa, Es256, Es384, or Rs256", 1502 )); 1503 } 1504 mldsa44 = true; 1505 } 1506 CoseAlgorithmIdentifier::Eddsa => { 1507 if eddsa { 1508 return Err(Error::custom( 1509 "pubKeyCredParams contained duplicate EdDSA values", 1510 )); 1511 } else if es256 || es384 || rs256 { 1512 return Err(Error::custom( 1513 "pubKeyCredParams contained Eddsa, but it was preceded by Es256, Es384, or Rs256", 1514 )); 1515 } 1516 eddsa = true; 1517 } 1518 CoseAlgorithmIdentifier::Es256 => { 1519 if es256 { 1520 return Err(Error::custom( 1521 "pubKeyCredParams contained duplicate Es256 values", 1522 )); 1523 } else if es384 || rs256 { 1524 return Err(Error::custom( 1525 "pubKeyCredParams contained Es256, but it was preceded by Es384 or Rs256", 1526 )); 1527 } 1528 es256 = true; 1529 } 1530 CoseAlgorithmIdentifier::Es384 => { 1531 if es384 { 1532 return Err(Error::custom( 1533 "pubKeyCredParams contained duplicate Es384 values", 1534 )); 1535 } else if rs256 { 1536 return Err(Error::custom( 1537 "pubKeyCredParams contained Es384, but it was preceded by Rs256", 1538 )); 1539 } 1540 es384 = true; 1541 } 1542 CoseAlgorithmIdentifier::Rs256 => { 1543 if rs256 { 1544 return Err(Error::custom( 1545 "pubKeyCredParams contained duplicate Rs256 values", 1546 )); 1547 } 1548 rs256 = true; 1549 } 1550 } 1551 } 1552 let mut algs = CoseAlgorithmIdentifiers(0); 1553 if mldsa87 { 1554 algs = algs.add(CoseAlgorithmIdentifier::Mldsa87); 1555 } 1556 if mldsa65 { 1557 algs = algs.add(CoseAlgorithmIdentifier::Mldsa65); 1558 } 1559 if mldsa44 { 1560 algs = algs.add(CoseAlgorithmIdentifier::Mldsa44); 1561 } 1562 if eddsa { 1563 algs = algs.add(CoseAlgorithmIdentifier::Eddsa); 1564 } 1565 if es256 { 1566 algs = algs.add(CoseAlgorithmIdentifier::Es256); 1567 } 1568 if es384 { 1569 algs = algs.add(CoseAlgorithmIdentifier::Es384); 1570 } 1571 if rs256 { 1572 algs = algs.add(CoseAlgorithmIdentifier::Rs256); 1573 } 1574 Ok(if algs.0 == 0 { 1575 CoseAlgorithmIdentifiers::ALL 1576 } else { 1577 algs 1578 }) 1579 } 1580 } 1581 deserializer.deserialize_seq(CoseAlgorithmIdentifiersVisitor) 1582 } 1583 } 1584 /// Helper for `UserVerificatonRequirement::deserialize` and [`ResidentKeyRequirement::deserialize`]. 1585 enum Requirement { 1586 /// Required. 1587 Required, 1588 /// Discouraged. 1589 Discouraged, 1590 /// Preferred. 1591 Preferred, 1592 } 1593 impl<'de> Deserialize<'de> for Requirement { 1594 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1595 where 1596 D: Deserializer<'de>, 1597 { 1598 /// `Visitor` for `Requirement`. 1599 struct RequirementVisitor; 1600 impl Visitor<'_> for RequirementVisitor { 1601 type Value = Requirement; 1602 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1603 write!(formatter, "'{REQUIRED}', '{DISCOURAGED}', or '{PREFERRED}'") 1604 } 1605 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1606 where 1607 E: Error, 1608 { 1609 match v { 1610 REQUIRED => Ok(Requirement::Required), 1611 DISCOURAGED => Ok(Requirement::Discouraged), 1612 PREFERRED => Ok(Requirement::Preferred), 1613 _ => Err(E::invalid_value( 1614 Unexpected::Str(v), 1615 &format!("'{REQUIRED}', '{DISCOURAGED}', or '{PREFERRED}'").as_str(), 1616 )), 1617 } 1618 } 1619 } 1620 deserializer.deserialize_str(RequirementVisitor) 1621 } 1622 } 1623 impl From<Requirement> for ResidentKeyRequirement { 1624 #[inline] 1625 fn from(value: Requirement) -> Self { 1626 match value { 1627 Requirement::Required => Self::Required, 1628 Requirement::Discouraged => Self::Discouraged, 1629 Requirement::Preferred => Self::Preferred, 1630 } 1631 } 1632 } 1633 impl From<Requirement> for UserVerificationRequirement { 1634 #[inline] 1635 fn from(value: Requirement) -> Self { 1636 match value { 1637 Requirement::Required => Self::Required, 1638 Requirement::Discouraged => Self::Discouraged, 1639 Requirement::Preferred => Self::Preferred, 1640 } 1641 } 1642 } 1643 impl<'de> Deserialize<'de> for ResidentKeyRequirement { 1644 /// Deserializes [`prim@str`] based on 1645 /// [`ResidentKeyRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement). 1646 /// 1647 /// # Examples 1648 /// 1649 /// ``` 1650 /// # use webauthn_rp::request::register::ResidentKeyRequirement; 1651 /// assert!( 1652 /// matches!( 1653 /// serde_json::from_str(r#""required""#)?, 1654 /// ResidentKeyRequirement::Required 1655 /// ) 1656 /// ); 1657 /// # Ok::<_, serde_json::Error>(()) 1658 /// ``` 1659 #[inline] 1660 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1661 where 1662 D: Deserializer<'de>, 1663 { 1664 Requirement::deserialize(deserializer).map(Self::from) 1665 } 1666 } 1667 impl<'de> Deserialize<'de> for UserVerificationRequirement { 1668 /// Deserializes [`prim@str`] based on 1669 /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement). 1670 /// 1671 /// # Examples 1672 /// 1673 /// ``` 1674 /// # use webauthn_rp::request::UserVerificationRequirement; 1675 /// assert!( 1676 /// matches!( 1677 /// serde_json::from_str(r#""required""#)?, 1678 /// UserVerificationRequirement::Required 1679 /// ) 1680 /// ); 1681 /// # Ok::<_, serde_json::Error>(()) 1682 /// ``` 1683 #[inline] 1684 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1685 where 1686 D: Deserializer<'de>, 1687 { 1688 Requirement::deserialize(deserializer).map(Self::from) 1689 } 1690 } 1691 impl<'de> Deserialize<'de> for AuthenticatorSelectionCriteria { 1692 /// Deserializes a `struct` based on 1693 /// [`AuthenticatorSelectionCriteria`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorselectioncriteria). 1694 /// 1695 /// Note that none of the fields are required, and all are allowed to be `null`. Additionally 1696 /// [`residentKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey) and 1697 /// [`requireResidentKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-requireresidentkey) 1698 /// must be consistent (i.e., `requireResidentKey` iff `residentKey` is [`ResidentKeyRequirement::Required`]). 1699 /// 1700 /// Missing and `null` fields default to the corresponding [`Default`] value. Unknown and duplicate fields 1701 /// lead to an error. 1702 /// 1703 /// # Examples 1704 /// 1705 /// ``` 1706 /// # use webauthn_rp::{request::register::AuthenticatorSelectionCriteria, response::AuthenticatorAttachment}; 1707 /// assert_eq!( 1708 /// serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"authenticatorAttachment":null,"residentKey":"required","requireResidentKey":true,"userVerification":"required"}"#)?.authenticator_attachment, 1709 /// AuthenticatorAttachment::None, 1710 /// ); 1711 /// # Ok::<_, serde_json::Error>(()) 1712 /// ``` 1713 #[expect(clippy::too_many_lines, reason = "144 isn't too bad")] 1714 #[inline] 1715 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1716 where 1717 D: Deserializer<'de>, 1718 { 1719 /// `Visitor` for `AuthenticatorSelectionCriteria`. 1720 struct AuthenticatorSelectionCriteriaVisitor; 1721 impl<'de> Visitor<'de> for AuthenticatorSelectionCriteriaVisitor { 1722 type Value = AuthenticatorSelectionCriteria; 1723 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1724 formatter.write_str("AuthenticatorSelectionCriteria") 1725 } 1726 #[expect(clippy::too_many_lines, reason = "121 isn't too bad")] 1727 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1728 where 1729 A: MapAccess<'de>, 1730 { 1731 /// Field for `AuthenticatorSelectionCriteria`. 1732 enum Field { 1733 /// `"authenticatorAttachment"`. 1734 AuthenticatorAttachment, 1735 /// `"residentKey"`. 1736 ResidentKey, 1737 /// `"requireResidentKey"`. 1738 RequireResidentKey, 1739 /// `"userVerification"`. 1740 UserVerification, 1741 } 1742 impl<'e> Deserialize<'e> for Field { 1743 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1744 where 1745 D: Deserializer<'e>, 1746 { 1747 /// `Visitor` for `Field`. 1748 struct FieldVisitor; 1749 impl Visitor<'_> for FieldVisitor { 1750 type Value = Field; 1751 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1752 write!( 1753 formatter, 1754 "'{AUTHENTICATOR_ATTACHMENT}', '{RESIDENT_KEY}', '{REQUIRE_RESIDENT_KEY}', or '{USER_VERIFICATION}'" 1755 ) 1756 } 1757 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1758 where 1759 E: Error, 1760 { 1761 match v { 1762 AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment), 1763 RESIDENT_KEY => Ok(Field::ResidentKey), 1764 REQUIRE_RESIDENT_KEY => Ok(Field::RequireResidentKey), 1765 USER_VERIFICATION => Ok(Field::UserVerification), 1766 _ => Err(Error::unknown_field(v, FIELDS)), 1767 } 1768 } 1769 } 1770 deserializer.deserialize_identifier(FieldVisitor) 1771 } 1772 } 1773 let mut attach = None; 1774 let mut res_key = None; 1775 let mut res_req: Option<Option<bool>> = None; 1776 let mut uv = None; 1777 while let Some(key) = map.next_key()? { 1778 match key { 1779 Field::AuthenticatorAttachment => { 1780 if attach.is_some() { 1781 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT)); 1782 } 1783 attach = map.next_value::<Option<_>>().map(Some)?; 1784 } 1785 Field::ResidentKey => { 1786 if res_key.is_some() { 1787 return Err(Error::duplicate_field(RESIDENT_KEY)); 1788 } 1789 res_key = map.next_value::<Option<_>>().and_then(|opt| { 1790 opt.map_or(Ok(Some(None)), |res| res_req.map_or(Ok(Some(opt)), |req_opt| req_opt.map_or(Ok(Some(opt)), |req| { 1791 match res { 1792 ResidentKeyRequirement::Required => { 1793 if req { 1794 Ok(Some(opt)) 1795 } else { 1796 Err(Error::custom(format!("'{RESIDENT_KEY}' is '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is false"))) 1797 } 1798 } 1799 ResidentKeyRequirement::Discouraged | ResidentKeyRequirement::Preferred => { 1800 if req { 1801 Err(Error::custom(format!("'{RESIDENT_KEY}' is not '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is true"))) 1802 } else { 1803 Ok(Some(opt)) 1804 } 1805 } 1806 } 1807 }))) 1808 })?; 1809 } 1810 Field::RequireResidentKey => { 1811 if res_req.is_some() { 1812 return Err(Error::duplicate_field(REQUIRE_RESIDENT_KEY)); 1813 } 1814 res_req = map.next_value::<Option<_>>().and_then(|opt| { 1815 opt.map_or(Ok(Some(None)), |req| res_key.map_or(Ok(Some(opt)), |req_opt| req_opt.map_or(Ok(Some(opt)), |res| { 1816 match res { 1817 ResidentKeyRequirement::Required => { 1818 if req { 1819 Ok(Some(opt)) 1820 } else { 1821 Err(Error::custom(format!("'{RESIDENT_KEY}' is '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is false"))) 1822 } 1823 } 1824 ResidentKeyRequirement::Discouraged | ResidentKeyRequirement::Preferred => { 1825 if req { 1826 Err(Error::custom(format!("'{RESIDENT_KEY}' is not '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is true"))) 1827 } else { 1828 Ok(Some(opt)) 1829 } 1830 } 1831 } 1832 }))) 1833 })?; 1834 } 1835 Field::UserVerification => { 1836 if uv.is_some() { 1837 return Err(Error::duplicate_field(USER_VERIFICATION)); 1838 } 1839 uv = map.next_value::<Option<_>>().map(Some)?; 1840 } 1841 } 1842 } 1843 Ok(AuthenticatorSelectionCriteria { 1844 authenticator_attachment: attach.flatten().unwrap_or_default(), 1845 resident_key: res_key.flatten().unwrap_or_else(|| { 1846 if res_req.flatten().is_some_and(convert::identity) { 1847 ResidentKeyRequirement::Required 1848 } else { 1849 ResidentKeyRequirement::Discouraged 1850 } 1851 }), 1852 user_verification: uv 1853 .flatten() 1854 .unwrap_or(UserVerificationRequirement::Preferred), 1855 }) 1856 } 1857 } 1858 /// Fields for `AuthenticatorSelectionCriteria`. 1859 const FIELDS: &[&str; 4] = &[ 1860 AUTHENTICATOR_ATTACHMENT, 1861 RESIDENT_KEY, 1862 REQUIRE_RESIDENT_KEY, 1863 USER_VERIFICATION, 1864 ]; 1865 deserializer.deserialize_struct( 1866 "AuthenticatorSelectionCriteria", 1867 FIELDS, 1868 AuthenticatorSelectionCriteriaVisitor, 1869 ) 1870 } 1871 } 1872 /// Helper for [`ClientCredentialCreationOptions::deserialize`]. 1873 struct Attestation; 1874 impl<'de> Deserialize<'de> for Attestation { 1875 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1876 where 1877 D: Deserializer<'de>, 1878 { 1879 /// `Visitor` for `Attestation`. 1880 struct AttestationVisitor; 1881 impl Visitor<'_> for AttestationVisitor { 1882 type Value = Attestation; 1883 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1884 formatter.write_str(NONE) 1885 } 1886 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1887 where 1888 E: Error, 1889 { 1890 if v == NONE { 1891 Ok(Attestation) 1892 } else { 1893 Err(E::invalid_value(Unexpected::Str(v), &NONE)) 1894 } 1895 } 1896 } 1897 deserializer.deserialize_str(AttestationVisitor) 1898 } 1899 } 1900 /// Helper for [`ClientCredentialCreationOptions::deserialize`]. 1901 struct AttestationFormats; 1902 impl<'de> Deserialize<'de> for AttestationFormats { 1903 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1904 where 1905 D: Deserializer<'de>, 1906 { 1907 /// `Visitor` for `AttestationFormats`. 1908 struct AttestationFormatsVisitor; 1909 impl<'d> Visitor<'d> for AttestationFormatsVisitor { 1910 type Value = AttestationFormats; 1911 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1912 formatter.write_str("AttestationFormats") 1913 } 1914 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 1915 where 1916 A: SeqAccess<'d>, 1917 { 1918 seq.next_element::<Attestation>().and_then(|opt| { 1919 opt.map_or(Ok(AttestationFormats), |_f| { 1920 seq.next_element::<Attestation>().and_then(|opt2| { 1921 opt2.map_or(Ok(AttestationFormats), |_val| Err(Error::custom("attestationFormats must be an empty sequence or contain exactly one string whose value is 'none'"))) 1922 }) 1923 }) 1924 }) 1925 } 1926 } 1927 deserializer.deserialize_seq(AttestationFormatsVisitor) 1928 } 1929 } 1930 impl<'de> Deserialize<'de> for FourToSixtyThree { 1931 /// Deserializes a `u8` based on [`Self::from_u8`]. 1932 /// 1933 /// # Examples 1934 /// 1935 /// ``` 1936 /// # use webauthn_rp::request::register::FourToSixtyThree; 1937 /// # use serde_json::Error; 1938 /// assert_eq!(serde_json::from_str::<FourToSixtyThree>("4")?, FourToSixtyThree::Four); 1939 /// assert_eq!(serde_json::from_str::<FourToSixtyThree>("63")?, FourToSixtyThree::SixtyThree); 1940 /// assert!(serde_json::from_str::<FourToSixtyThree>("0").is_err()); 1941 /// assert!(serde_json::from_str::<FourToSixtyThree>("3").is_err()); 1942 /// assert!(serde_json::from_str::<FourToSixtyThree>("64").is_err()); 1943 /// # Ok::<_, Error>(()) 1944 /// ``` 1945 #[inline] 1946 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1947 where 1948 D: Deserializer<'de>, 1949 { 1950 u8::deserialize(deserializer).and_then(|val| { 1951 Self::from_u8(val).ok_or_else(|| { 1952 Error::invalid_value( 1953 Unexpected::Unsigned(u64::from(val)), 1954 &"integer inclusively between 4 and 63", 1955 ) 1956 }) 1957 }) 1958 } 1959 } 1960 /// Similar to [`Extension`] except [`PrfInputOwned`] is used. 1961 /// 1962 /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`]. 1963 #[derive(Debug, Default)] 1964 pub struct ExtensionOwned { 1965 /// See [`Extension::cred_props`]. 1966 pub cred_props: Option<ExtensionReq>, 1967 /// See [`Extension::cred_protect`]. 1968 pub cred_protect: CredProtect, 1969 /// See [`Extension::min_pin_length`]. 1970 pub min_pin_length: Option<(FourToSixtyThree, ExtensionInfo)>, 1971 /// See [`Extension::prf`]. 1972 pub prf: Option<PrfInputOwned>, 1973 } 1974 impl ExtensionOwned { 1975 /// Returns an `Extension` based on `self`. 1976 /// 1977 /// Note [`PrfInputOwned::ext_req`] is converted into an [`ExtensionInfo`] such that the value is enforced. 1978 #[inline] 1979 #[must_use] 1980 pub fn as_extension(&self) -> Extension<'_, '_> { 1981 Extension { 1982 cred_props: self.cred_props, 1983 cred_protect: self.cred_protect, 1984 min_pin_length: self.min_pin_length, 1985 prf: self.prf.as_ref().map(|prf| { 1986 ( 1987 PrfInput { 1988 first: &prf.first, 1989 second: prf.second.as_deref(), 1990 }, 1991 if matches!(prf.ext_req, ExtensionReq::Require) { 1992 ExtensionInfo::RequireEnforceValue 1993 } else { 1994 ExtensionInfo::AllowEnforceValue 1995 }, 1996 ) 1997 }), 1998 } 1999 } 2000 /// Returns an `Extension` based on `self` and `prf`. 2001 /// 2002 /// Note `prf` is used _unconditionally_ regardless if [`Self::prf`] is `Some`. 2003 #[inline] 2004 #[must_use] 2005 pub const fn with_prf<'prf_first, 'prf_second>( 2006 &self, 2007 prf: (PrfInput<'prf_first, 'prf_second>, ExtensionInfo), 2008 ) -> Extension<'prf_first, 'prf_second> { 2009 Extension { 2010 cred_props: self.cred_props, 2011 cred_protect: self.cred_protect, 2012 min_pin_length: self.min_pin_length, 2013 prf: Some(prf), 2014 } 2015 } 2016 } 2017 impl<'de> Deserialize<'de> for ExtensionOwned { 2018 /// Deserializes a `struct` according to the following pseudo-schema: 2019 /// 2020 /// ```json 2021 /// { 2022 /// "credProps": null | false | true, 2023 /// "credentialProtectionPolicy": null | "userVerificationOptional" | "userVerificationOptionalWithCredentialIDList" | "userVerificationRequired", 2024 /// "enforceCredentialProtectionPolicy": null | false | true, 2025 /// "minPinLength": null | false | true, 2026 /// "prf": null | PRFJSON 2027 /// } 2028 /// // PRFJSON: 2029 /// { 2030 /// "eval": PRFInputs 2031 /// } 2032 /// // PRFInputs: 2033 /// { 2034 /// "first": <base64url-encoded string>, 2035 /// "second": null | <base64url-encoded string> 2036 /// } 2037 /// ``` 2038 /// 2039 /// where the only required fields are `"eval"` and `"first"`. Additionally `"credentialProtectionPolicy"` 2040 /// must exist if `"enforceCredentialProtectionPolicy"` exists, and it must not be `null` if the latter 2041 /// is not `null`. If the former is defined and not `null` but the latter is not defined or is `null`, then 2042 /// `false` will be used for the latter. Unknown or duplicate fields lead to an error. 2043 /// 2044 /// All extensions are not required to have a response sent back; but _if_ a response is sent back, its value 2045 /// will be enforced. In the case of `"minPinLength"`, [`FourToSixtyThree::Four`] will be the minimum 2046 /// length enforced (i.e., any valid response is guaranteed to satisfy since it will have length at least 2047 /// as large). 2048 /// 2049 /// Unknown or duplicate fields lead to an error. 2050 /// 2051 /// # Examples 2052 /// 2053 /// ``` 2054 /// # use webauthn_rp::request::{ExtensionInfo, ExtensionReq, register::{CredProtect, FourToSixtyThree, ser::ExtensionOwned}}; 2055 /// let ext = serde_json::from_str::<ExtensionOwned>( 2056 /// r#"{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":null}}}"#, 2057 /// )?; 2058 /// assert!( 2059 /// ext.cred_props 2060 /// .map_or(false, |req| matches!(req, ExtensionReq::Allow)) 2061 /// ); 2062 /// assert!( 2063 /// matches!(ext.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue)) 2064 /// ); 2065 /// assert!(ext.min_pin_length.map_or(false, |pin| pin.0 == FourToSixtyThree::Four 2066 /// && matches!(pin.1, ExtensionInfo::AllowEnforceValue))); 2067 /// assert!(ext.prf.map_or(false, |prf| prf.first.is_empty() 2068 /// && prf.second.is_none() 2069 /// && matches!(prf.ext_req, ExtensionReq::Allow))); 2070 /// # Ok::<_, serde_json::Error>(()) 2071 /// ``` 2072 #[expect(clippy::too_many_lines, reason = "want to keep logic internal")] 2073 #[inline] 2074 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2075 where 2076 D: Deserializer<'de>, 2077 { 2078 /// `Visitor` for `ExtensionOwned`. 2079 struct ExtensionOwnedVisitor; 2080 impl<'d> Visitor<'d> for ExtensionOwnedVisitor { 2081 type Value = ExtensionOwned; 2082 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2083 formatter.write_str("ExtensionOwned") 2084 } 2085 #[expect(clippy::too_many_lines, reason = "want to keep logic internal")] 2086 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 2087 where 2088 A: MapAccess<'d>, 2089 { 2090 /// Field for `ExtensionOwned`. 2091 enum Field { 2092 /// `credProps`. 2093 CredProps, 2094 /// `credentialProtectionPolicy`. 2095 CredentialProtectionPolicy, 2096 /// `enforceCredentialProtectionPolicy`. 2097 EnforceCredentialProtectionPolicy, 2098 /// `minPinLength`. 2099 MinPinLength, 2100 /// `prf` 2101 Prf, 2102 } 2103 impl<'e> Deserialize<'e> for Field { 2104 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2105 where 2106 D: Deserializer<'e>, 2107 { 2108 /// `Visitor` for `Field`. 2109 struct FieldVisitor; 2110 impl Visitor<'_> for FieldVisitor { 2111 type Value = Field; 2112 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2113 write!( 2114 formatter, 2115 "'{CRED_PROPS}', '{CREDENTIAL_PROTECTION_POLICY}', '{ENFORCE_CREDENTIAL_PROTECTION_POLICY}', '{MIN_PIN_LENGTH}', or '{PRF}'" 2116 ) 2117 } 2118 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 2119 where 2120 E: Error, 2121 { 2122 match v { 2123 CRED_PROPS => Ok(Field::CredProps), 2124 CREDENTIAL_PROTECTION_POLICY => { 2125 Ok(Field::CredentialProtectionPolicy) 2126 } 2127 ENFORCE_CREDENTIAL_PROTECTION_POLICY => { 2128 Ok(Field::EnforceCredentialProtectionPolicy) 2129 } 2130 MIN_PIN_LENGTH => Ok(Field::MinPinLength), 2131 PRF => Ok(Field::Prf), 2132 _ => Err(E::unknown_field(v, FIELDS)), 2133 } 2134 } 2135 } 2136 deserializer.deserialize_identifier(FieldVisitor) 2137 } 2138 } 2139 /// Credential protection policy values. 2140 #[expect(clippy::enum_variant_names, reason = "consistent with ctap names")] 2141 enum Policy { 2142 /// `userVerificationOptional`. 2143 UserVerificationOptional, 2144 /// `userVerificationOptionalWithCredentialIdList`. 2145 UserVerificationOptionalWithCredentialIdLisit, 2146 /// `userVerificationRequired`. 2147 UserVerificationRequired, 2148 } 2149 impl<'e> Deserialize<'e> for Policy { 2150 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2151 where 2152 D: Deserializer<'e>, 2153 { 2154 /// `Visitor` for `Policy`. 2155 struct PolicyVisitor; 2156 impl Visitor<'_> for PolicyVisitor { 2157 type Value = Policy; 2158 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2159 write!( 2160 formatter, 2161 "'{USER_VERIFICATION_OPTIONAL}', '{USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST}', or '{USER_VERIFICATION_REQUIRED}'" 2162 ) 2163 } 2164 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 2165 where 2166 E: Error, 2167 { 2168 match v { 2169 USER_VERIFICATION_OPTIONAL => Ok(Policy::UserVerificationOptional), 2170 USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST => Ok(Policy::UserVerificationOptionalWithCredentialIdLisit), 2171 USER_VERIFICATION_REQUIRED => Ok(Policy::UserVerificationRequired), 2172 _ => Err(E::invalid_value(Unexpected::Str(v), &format!("'{USER_VERIFICATION_OPTIONAL}', '{USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST}', or '{USER_VERIFICATION_REQUIRED}'").as_str())), 2173 } 2174 } 2175 } 2176 deserializer.deserialize_str(PolicyVisitor) 2177 } 2178 } 2179 let mut props: Option<Option<bool>> = None; 2180 let mut policy = None; 2181 let mut enforce = None; 2182 let mut pin: Option<Option<bool>> = None; 2183 let mut prf_inputs = None; 2184 while let Some(key) = map.next_key()? { 2185 match key { 2186 Field::CredProps => { 2187 if props.is_some() { 2188 return Err(Error::duplicate_field(CRED_PROPS)); 2189 } 2190 props = map.next_value().map(Some)?; 2191 } 2192 Field::CredentialProtectionPolicy => { 2193 if policy.is_some() { 2194 return Err(Error::duplicate_field(CREDENTIAL_PROTECTION_POLICY)); 2195 } 2196 policy = map.next_value::<Option<Policy>>().map(Some)?; 2197 } 2198 Field::EnforceCredentialProtectionPolicy => { 2199 if enforce.is_some() { 2200 return Err(Error::duplicate_field( 2201 ENFORCE_CREDENTIAL_PROTECTION_POLICY, 2202 )); 2203 } 2204 enforce = map.next_value::<Option<_>>().map(Some)?; 2205 } 2206 Field::MinPinLength => { 2207 if pin.is_some() { 2208 return Err(Error::duplicate_field(MIN_PIN_LENGTH)); 2209 } 2210 pin = map.next_value().map(Some)?; 2211 } 2212 Field::Prf => { 2213 if prf_inputs.is_some() { 2214 return Err(Error::duplicate_field(PRF)); 2215 } 2216 prf_inputs = map 2217 .next_value::<Option<PrfHelper>>() 2218 .map(|opt| Some(opt.map(|p| p.0)))?; 2219 } 2220 } 2221 } 2222 policy.map_or_else( 2223 || { 2224 if enforce.is_some() { 2225 Err(Error::custom(format!("'{ENFORCE_CREDENTIAL_PROTECTION_POLICY}' must not exist when '{CREDENTIAL_PROTECTION_POLICY}' does not exist"))) 2226 } else { 2227 Ok(CredProtect::None) 2228 } 2229 }, 2230 |opt_policy| opt_policy.map_or_else( 2231 || { 2232 if enforce.is_some_and(|opt| opt.is_some()) { 2233 Err(Error::custom(format!("'{ENFORCE_CREDENTIAL_PROTECTION_POLICY}' must be null or not exist when '{CREDENTIAL_PROTECTION_POLICY}' is null"))) 2234 } else { 2235 Ok(CredProtect::None) 2236 } 2237 }, 2238 |cred_policy| { 2239 match cred_policy { 2240 Policy::UserVerificationOptional => Ok(CredProtect::UserVerificationOptional(enforce.flatten().unwrap_or_default(), ExtensionInfo::AllowEnforceValue)), 2241 Policy::UserVerificationOptionalWithCredentialIdLisit => Ok(CredProtect::UserVerificationOptionalWithCredentialIdList(enforce.flatten().unwrap_or_default(), ExtensionInfo::AllowEnforceValue)), 2242 Policy::UserVerificationRequired => Ok(CredProtect::UserVerificationRequired(enforce.flatten().unwrap_or_default(), ExtensionInfo::AllowEnforceValue)), 2243 } 2244 } 2245 ), 2246 ).map(|cred_protect| { 2247 ExtensionOwned { cred_props: props.flatten().and_then(|p| p.then_some(ExtensionReq::Allow)), cred_protect, min_pin_length: pin.flatten().and_then(|m| m.then_some((FourToSixtyThree::Four, ExtensionInfo::AllowEnforceValue))), prf: prf_inputs.flatten(), } 2248 }) 2249 } 2250 } 2251 /// Fields for `ExtensionOwned`. 2252 const FIELDS: &[&str; 5] = &[ 2253 CRED_PROPS, 2254 CREDENTIAL_PROTECTION_POLICY, 2255 ENFORCE_CREDENTIAL_PROTECTION_POLICY, 2256 MIN_PIN_LENGTH, 2257 PRF, 2258 ]; 2259 deserializer.deserialize_struct("ExtensionOwned", FIELDS, ExtensionOwnedVisitor) 2260 } 2261 } 2262 /// Similar to [`PublicKeyCredentialCreationOptions`] except the fields are based on owned data, and 2263 /// [`Self::rp_id`] is optional. 2264 /// 2265 /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`]. 2266 #[derive(Debug)] 2267 pub struct PublicKeyCredentialCreationOptionsOwned<const USER_LEN: usize> { 2268 /// See [`PublicKeyCredentialCreationOptions::rp_id`]. 2269 pub rp_id: Option<RpId>, 2270 /// See [`PublicKeyCredentialCreationOptions::user`]. 2271 pub user: PublicKeyCredentialUserEntityOwned<USER_LEN>, 2272 /// See [`PublicKeyCredentialCreationOptions::pub_key_cred_params`]. 2273 pub pub_key_cred_params: CoseAlgorithmIdentifiers, 2274 /// See [`PublicKeyCredentialCreationOptions::timeout`]. 2275 pub timeout: NonZeroU32, 2276 /// See [`PublicKeyCredentialCreationOptions::authenticator_selection`]. 2277 pub authenticator_selection: AuthenticatorSelectionCriteria, 2278 /// See [`PublicKeyCredentialCreationOptions::hints`]. 2279 pub hints: Hints, 2280 /// See [`PublicKeyCredentialCreationOptions::extensions`]. 2281 pub extensions: ExtensionOwned, 2282 } 2283 /// Error returned when converting a [`PublicKeyCredentialCreationOptionsOwned`] into a 2284 /// [`PublicKeyCredentialCreationOptions`] (e.g., via [`PublicKeyCredentialCreationOptionsOwned::with_rp_id`]). 2285 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 2286 pub enum PublicKeyCredentialCreationOptionsOwnedErr { 2287 /// Variant returned when [`PublicKeyCredentialCreationOptionsOwned::rp_id`] is `None`. 2288 MissingRpId, 2289 /// Variant returned when [`PublicKeyCredentialCreationOptionsOwned::user`] cannot be converted into a 2290 /// a [`PublicKeyCredentialCreationOptions`]. 2291 UserEntity(PublicKeyCredentialUserEntityOwnedErr), 2292 } 2293 impl Display for PublicKeyCredentialCreationOptionsOwnedErr { 2294 #[inline] 2295 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 2296 match *self { 2297 Self::MissingRpId => f.write_str("creation options did not have an RP ID"), 2298 Self::UserEntity(err) => err.fmt(f), 2299 } 2300 } 2301 } 2302 impl E for PublicKeyCredentialCreationOptionsOwnedErr {} 2303 impl From<PublicKeyCredentialUserEntityOwnedErr> for PublicKeyCredentialCreationOptionsOwnedErr { 2304 #[inline] 2305 fn from(value: PublicKeyCredentialUserEntityOwnedErr) -> Self { 2306 Self::UserEntity(value) 2307 } 2308 } 2309 impl<const USER_LEN: usize> PublicKeyCredentialCreationOptionsOwned<USER_LEN> { 2310 /// Returns a `PublicKeyCredentialCreationOptions` based on `self` and `exclude_credentials`. 2311 /// 2312 /// # Errors 2313 /// 2314 /// Errors iff [`Self::rp_id`] is `None` or [`PublicKeyCredentialUserEntityOwned::as_entity`] errors. 2315 #[inline] 2316 pub fn as_options( 2317 &self, 2318 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2319 ) -> Result< 2320 PublicKeyCredentialCreationOptions<'_, '_, '_, '_, '_, '_, USER_LEN>, 2321 PublicKeyCredentialCreationOptionsOwnedErr, 2322 > { 2323 self.rp_id 2324 .as_ref() 2325 .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId) 2326 .and_then(|rp_id| { 2327 self.user 2328 .as_entity() 2329 .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity) 2330 .map(|user| PublicKeyCredentialCreationOptions { 2331 rp_id, 2332 user, 2333 challenge: Challenge::new(), 2334 pub_key_cred_params: self.pub_key_cred_params, 2335 timeout: self.timeout, 2336 exclude_credentials, 2337 authenticator_selection: self.authenticator_selection, 2338 hints: self.hints, 2339 extensions: self.extensions.as_extension(), 2340 }) 2341 }) 2342 } 2343 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, and `rp_id`. 2344 /// 2345 /// Note `rp_id` is used _unconditionally_ regardless if [`Self::rp_id`] is `Some`. 2346 /// 2347 /// # Errors 2348 /// 2349 /// Errors iff [`PublicKeyCredentialUserEntityOwned::as_entity`] errors. 2350 #[inline] 2351 pub fn with_rp_id<'rp_id>( 2352 &self, 2353 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2354 rp_id: &'rp_id RpId, 2355 ) -> Result< 2356 PublicKeyCredentialCreationOptions<'rp_id, '_, '_, '_, '_, '_, USER_LEN>, 2357 PublicKeyCredentialCreationOptionsOwnedErr, 2358 > { 2359 self.user 2360 .as_entity() 2361 .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity) 2362 .map(|user| PublicKeyCredentialCreationOptions { 2363 rp_id, 2364 user, 2365 challenge: Challenge::new(), 2366 pub_key_cred_params: self.pub_key_cred_params, 2367 timeout: self.timeout, 2368 exclude_credentials, 2369 authenticator_selection: self.authenticator_selection, 2370 hints: self.hints, 2371 extensions: self.extensions.as_extension(), 2372 }) 2373 } 2374 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, and `user`. 2375 /// 2376 /// Note `user` is used _unconditionally_ regardless of what [`Self::user`] is. 2377 /// 2378 /// # Errors 2379 /// 2380 /// Errors iff [`Self::rp_id`] is `None`. 2381 #[inline] 2382 pub fn with_user<'user_name, 'user_display_name, 'user_id>( 2383 &self, 2384 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2385 user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>, 2386 ) -> Result< 2387 PublicKeyCredentialCreationOptions< 2388 '_, 2389 'user_name, 2390 'user_display_name, 2391 'user_id, 2392 '_, 2393 '_, 2394 USER_LEN, 2395 >, 2396 PublicKeyCredentialCreationOptionsOwnedErr, 2397 > { 2398 self.rp_id 2399 .as_ref() 2400 .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId) 2401 .map(|rp_id| PublicKeyCredentialCreationOptions { 2402 rp_id, 2403 user, 2404 challenge: Challenge::new(), 2405 pub_key_cred_params: self.pub_key_cred_params, 2406 timeout: self.timeout, 2407 exclude_credentials, 2408 authenticator_selection: self.authenticator_selection, 2409 hints: self.hints, 2410 extensions: self.extensions.as_extension(), 2411 }) 2412 } 2413 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, and `extensions`. 2414 /// 2415 /// Note `extensions` is used _unconditionally_ regardless of what [`Self::extensions`] is. 2416 /// 2417 /// # Errors 2418 /// 2419 /// Errors iff [`Self::rp_id`] is `None` or [`PublicKeyCredentialUserEntityOwned::as_entity`] errors. 2420 #[inline] 2421 pub fn with_extensions<'prf_first, 'prf_second>( 2422 &self, 2423 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2424 extensions: Extension<'prf_first, 'prf_second>, 2425 ) -> Result< 2426 PublicKeyCredentialCreationOptions<'_, '_, '_, '_, 'prf_first, 'prf_second, USER_LEN>, 2427 PublicKeyCredentialCreationOptionsOwnedErr, 2428 > { 2429 self.rp_id 2430 .as_ref() 2431 .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId) 2432 .and_then(|rp_id| { 2433 self.user 2434 .as_entity() 2435 .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity) 2436 .map(|user| PublicKeyCredentialCreationOptions { 2437 rp_id, 2438 user, 2439 challenge: Challenge::new(), 2440 pub_key_cred_params: self.pub_key_cred_params, 2441 timeout: self.timeout, 2442 exclude_credentials, 2443 authenticator_selection: self.authenticator_selection, 2444 hints: self.hints, 2445 extensions, 2446 }) 2447 }) 2448 } 2449 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `rp_id`, and `user`. 2450 /// 2451 /// Note `rp_id` and `user` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some` or what 2452 /// [`Self::user`] is. 2453 #[inline] 2454 #[must_use] 2455 pub fn with_rp_id_and_user<'rp_id, 'user_name, 'user_display_name, 'user_id>( 2456 &self, 2457 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2458 rp_id: &'rp_id RpId, 2459 user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>, 2460 ) -> PublicKeyCredentialCreationOptions< 2461 'rp_id, 2462 'user_name, 2463 'user_display_name, 2464 'user_id, 2465 '_, 2466 '_, 2467 USER_LEN, 2468 > { 2469 PublicKeyCredentialCreationOptions { 2470 rp_id, 2471 user, 2472 challenge: Challenge::new(), 2473 pub_key_cred_params: self.pub_key_cred_params, 2474 timeout: self.timeout, 2475 exclude_credentials, 2476 authenticator_selection: self.authenticator_selection, 2477 hints: self.hints, 2478 extensions: self.extensions.as_extension(), 2479 } 2480 } 2481 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `rp_id`, and 2482 /// `extensions`. 2483 /// 2484 /// Note `rp_id` and `extensions` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some` or what 2485 /// [`Self::extensions`] is. 2486 /// 2487 /// # Errors 2488 /// 2489 /// Errors iff [`PublicKeyCredentialUserEntityOwned::as_entity`] errors. 2490 #[inline] 2491 pub fn with_rp_id_and_extensions<'rp_id, 'prf_first, 'prf_second>( 2492 &self, 2493 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2494 rp_id: &'rp_id RpId, 2495 extensions: Extension<'prf_first, 'prf_second>, 2496 ) -> Result< 2497 PublicKeyCredentialCreationOptions<'rp_id, '_, '_, '_, 'prf_first, 'prf_second, USER_LEN>, 2498 PublicKeyCredentialCreationOptionsOwnedErr, 2499 > { 2500 self.user 2501 .as_entity() 2502 .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity) 2503 .map(|user| PublicKeyCredentialCreationOptions { 2504 rp_id, 2505 user, 2506 challenge: Challenge::new(), 2507 pub_key_cred_params: self.pub_key_cred_params, 2508 timeout: self.timeout, 2509 exclude_credentials, 2510 authenticator_selection: self.authenticator_selection, 2511 hints: self.hints, 2512 extensions, 2513 }) 2514 } 2515 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `user`, and 2516 /// `extensions`. 2517 /// 2518 /// Note `user` and `extensions` are used _unconditionally_ regardless of what the values of [`Self::user`] 2519 /// or [`Self::extensions`] are. 2520 /// 2521 /// # Errors 2522 /// 2523 /// Errors iff [`Self::rp_id`] is `None`. 2524 #[inline] 2525 pub fn with_user_and_extensions< 2526 'user_name, 2527 'user_display_name, 2528 'user_id, 2529 'prf_first, 2530 'prf_second, 2531 >( 2532 &self, 2533 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2534 user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>, 2535 extensions: Extension<'prf_first, 'prf_second>, 2536 ) -> Result< 2537 PublicKeyCredentialCreationOptions< 2538 '_, 2539 'user_name, 2540 'user_display_name, 2541 'user_id, 2542 'prf_first, 2543 'prf_second, 2544 USER_LEN, 2545 >, 2546 PublicKeyCredentialCreationOptionsOwnedErr, 2547 > { 2548 self.rp_id 2549 .as_ref() 2550 .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId) 2551 .map(|rp_id| PublicKeyCredentialCreationOptions { 2552 rp_id, 2553 user, 2554 challenge: Challenge::new(), 2555 pub_key_cred_params: self.pub_key_cred_params, 2556 timeout: self.timeout, 2557 exclude_credentials, 2558 authenticator_selection: self.authenticator_selection, 2559 hints: self.hints, 2560 extensions, 2561 }) 2562 } 2563 /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `rp_id`, `user`, 2564 /// and `extensions`. 2565 /// 2566 /// Note `rp_id`, `user`, and `extensions` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some` 2567 /// or what the values of [`Self::user`] and [`Self::extensions`] are. 2568 #[inline] 2569 #[must_use] 2570 pub fn with_rp_id_user_and_extensions< 2571 'rp_id, 2572 'user_name, 2573 'user_display_name, 2574 'user_id, 2575 'prf_first, 2576 'prf_second, 2577 >( 2578 &self, 2579 exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>, 2580 rp_id: &'rp_id RpId, 2581 user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>, 2582 extensions: Extension<'prf_first, 'prf_second>, 2583 ) -> PublicKeyCredentialCreationOptions< 2584 'rp_id, 2585 'user_name, 2586 'user_display_name, 2587 'user_id, 2588 'prf_first, 2589 'prf_second, 2590 USER_LEN, 2591 > { 2592 PublicKeyCredentialCreationOptions { 2593 rp_id, 2594 user, 2595 challenge: Challenge::new(), 2596 pub_key_cred_params: self.pub_key_cred_params, 2597 timeout: self.timeout, 2598 exclude_credentials, 2599 authenticator_selection: self.authenticator_selection, 2600 hints: self.hints, 2601 extensions, 2602 } 2603 } 2604 } 2605 impl<const USER_LEN: usize> Default for PublicKeyCredentialCreationOptionsOwned<USER_LEN> { 2606 #[inline] 2607 fn default() -> Self { 2608 Self { 2609 rp_id: None, 2610 user: PublicKeyCredentialUserEntityOwned::default(), 2611 pub_key_cred_params: CoseAlgorithmIdentifiers::default(), 2612 timeout: FIVE_MINUTES, 2613 authenticator_selection: AuthenticatorSelectionCriteria { 2614 authenticator_attachment: AuthenticatorAttachment::default(), 2615 resident_key: ResidentKeyRequirement::Discouraged, 2616 user_verification: UserVerificationRequirement::Preferred, 2617 }, 2618 hints: Hints::EMPTY, 2619 extensions: ExtensionOwned::default(), 2620 } 2621 } 2622 } 2623 impl<'de, const USER_LEN: usize> Deserialize<'de> 2624 for PublicKeyCredentialCreationOptionsOwned<USER_LEN> 2625 where 2626 PublicKeyCredentialUserEntityOwned<USER_LEN>: Deserialize<'de>, 2627 { 2628 /// Deserializes a `struct` based on 2629 /// [`PublicKeyCredentialCreationOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptionsjson). 2630 /// 2631 /// Note that none of the fields are required, and all are allowed to be `null`. 2632 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-authenticatorattachment) 2633 /// must be consistent with 2634 /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-hints) 2635 /// (e.g., if [`"platform"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattachment-platform) is 2636 /// requested, then `hints` must either not exist, be `null`, be empty, or be `["client-device"]`). 2637 /// 2638 /// If [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-challenge) 2639 /// exists, it must be `null`. If 2640 /// [`excludeCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-excludecredentials) 2641 /// exists, it must be `null` or empty. If 2642 /// [`attestation`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-attestation) 2643 /// exists, it must be `null`or `"none"`. If 2644 /// [`attestationFormats`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-attestationformats) 2645 /// exists, it must be `null`, empty, or `["none"]`. 2646 /// 2647 /// If [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-timeout) exists, 2648 /// it must be `null` or positive. When it does not exist or is `null`, [`FIVE_MINUTES`] will be used. 2649 /// 2650 /// Fields that are missing or `null` will be replaced with their corresponding [`Default`] value. 2651 /// 2652 /// Unknown or duplicate fields lead to an error. 2653 #[expect(clippy::too_many_lines, reason = "want to keep logic internal")] 2654 #[inline] 2655 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2656 where 2657 D: Deserializer<'de>, 2658 { 2659 /// `Visitor` for `PublicKeyCredentialCreationOptionsOwned`. 2660 struct PublicKeyCredentialCreationOptionsOwnedVisitor<const LEN: usize>; 2661 impl<'d, const LEN: usize> Visitor<'d> for PublicKeyCredentialCreationOptionsOwnedVisitor<LEN> 2662 where 2663 PublicKeyCredentialUserEntityOwned<LEN>: Deserialize<'d>, 2664 { 2665 type Value = PublicKeyCredentialCreationOptionsOwned<LEN>; 2666 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2667 formatter.write_str("PublicKeyCredentialCreationOptionsOwned") 2668 } 2669 #[expect(clippy::too_many_lines, reason = "want to keep logic internal")] 2670 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 2671 where 2672 A: MapAccess<'d>, 2673 { 2674 /// Field for `PublicKeyCredentialCreationOptionsOwned`. 2675 enum Field { 2676 /// `rp`. 2677 Rp, 2678 /// `user`. 2679 User, 2680 /// `challenge`. 2681 Challenge, 2682 /// `pubKeyCredParams`. 2683 PubKeyCredParams, 2684 /// `timeout`. 2685 Timeout, 2686 /// `excludeCredentials`. 2687 ExcludeCredentials, 2688 /// `authenticatorSelection`. 2689 AuthenticatorSelection, 2690 /// `hints`. 2691 Hints, 2692 /// `extensions`. 2693 Extensions, 2694 /// `attestation`. 2695 Attestation, 2696 /// `attestationFormats`. 2697 AttestationFormats, 2698 } 2699 impl<'e> Deserialize<'e> for Field { 2700 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2701 where 2702 D: Deserializer<'e>, 2703 { 2704 /// `Visitor` for `Field`. 2705 struct FieldVisitor; 2706 impl Visitor<'_> for FieldVisitor { 2707 type Value = Field; 2708 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2709 write!( 2710 formatter, 2711 "'{RP}', '{USER}', '{CHALLENGE}', '{PUB_KEY_CRED_PARAMS}', '{TIMEOUT}', '{EXCLUDE_CREDENTIALS}', '{AUTHENTICATOR_SELECTION}', '{HINTS}', '{EXTENSIONS}', '{ATTESTATION}', or '{ATTESTATION_FORMATS}'" 2712 ) 2713 } 2714 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 2715 where 2716 E: Error, 2717 { 2718 match v { 2719 RP => Ok(Field::Rp), 2720 USER => Ok(Field::User), 2721 CHALLENGE => Ok(Field::Challenge), 2722 PUB_KEY_CRED_PARAMS => Ok(Field::PubKeyCredParams), 2723 TIMEOUT => Ok(Field::Timeout), 2724 EXCLUDE_CREDENTIALS => Ok(Field::ExcludeCredentials), 2725 AUTHENTICATOR_SELECTION => Ok(Field::AuthenticatorSelection), 2726 HINTS => Ok(Field::Hints), 2727 EXTENSIONS => Ok(Field::Extensions), 2728 ATTESTATION => Ok(Field::Attestation), 2729 ATTESTATION_FORMATS => Ok(Field::AttestationFormats), 2730 _ => Err(E::unknown_field(v, FIELDS)), 2731 } 2732 } 2733 } 2734 deserializer.deserialize_identifier(FieldVisitor) 2735 } 2736 } 2737 let mut rp = None; 2738 let mut user_info = None; 2739 let mut chall = None; 2740 let mut params = None; 2741 let mut time = None; 2742 let mut exclude = None; 2743 let mut auth = None; 2744 let mut hint = None; 2745 let mut ext = None; 2746 let mut attest = None; 2747 let mut formats = None; 2748 while let Some(key) = map.next_key()? { 2749 match key { 2750 Field::Rp => { 2751 if rp.is_some() { 2752 return Err(Error::duplicate_field(RP)); 2753 } 2754 rp = map 2755 .next_value::<Option<PublicKeyCredentialRpEntityHelper>>() 2756 .map(|opt| opt.map(|val| val.0)) 2757 .map(Some)?; 2758 } 2759 Field::User => { 2760 if user_info.is_some() { 2761 return Err(Error::duplicate_field(USER)); 2762 } 2763 user_info = map.next_value::<Option<_>>().map(Some)?; 2764 } 2765 Field::Challenge => { 2766 if chall.is_some() { 2767 return Err(Error::duplicate_field(CHALLENGE)); 2768 } 2769 chall = map.next_value::<Null>().map(Some)?; 2770 } 2771 Field::PubKeyCredParams => { 2772 if params.is_some() { 2773 return Err(Error::duplicate_field(PUB_KEY_CRED_PARAMS)); 2774 } 2775 params = map.next_value::<Option<_>>().map(Some)?; 2776 } 2777 Field::Timeout => { 2778 if time.is_some() { 2779 return Err(Error::duplicate_field(TIMEOUT)); 2780 } 2781 time = map.next_value::<Option<_>>().map(Some)?; 2782 } 2783 Field::ExcludeCredentials => { 2784 if exclude.is_some() { 2785 return Err(Error::duplicate_field(EXCLUDE_CREDENTIALS)); 2786 } 2787 exclude = map.next_value::<Option<[(); 0]>>().map(Some)?; 2788 } 2789 Field::AuthenticatorSelection => { 2790 if auth.is_some() { 2791 return Err(Error::duplicate_field(AUTHENTICATOR_SELECTION)); 2792 } 2793 auth = map.next_value::<Option<_>>().map(Some)?; 2794 } 2795 Field::Hints => { 2796 if hint.is_some() { 2797 return Err(Error::duplicate_field(HINTS)); 2798 } 2799 hint = map.next_value::<Option<_>>().map(Some)?; 2800 } 2801 Field::Extensions => { 2802 if ext.is_some() { 2803 return Err(Error::duplicate_field(EXTENSIONS)); 2804 } 2805 ext = map.next_value::<Option<_>>().map(Some)?; 2806 } 2807 Field::Attestation => { 2808 if attest.is_some() { 2809 return Err(Error::duplicate_field(ATTESTATION)); 2810 } 2811 attest = map.next_value::<Option<Attestation>>().map(Some)?; 2812 } 2813 Field::AttestationFormats => { 2814 if formats.is_some() { 2815 return Err(Error::duplicate_field(ATTESTATION_FORMATS)); 2816 } 2817 formats = map.next_value::<Option<AttestationFormats>>().map(Some)?; 2818 } 2819 } 2820 } 2821 Ok(PublicKeyCredentialCreationOptionsOwned { 2822 rp_id: rp.flatten().flatten(), 2823 user: user_info.flatten().unwrap_or_default(), 2824 pub_key_cred_params: params.flatten().unwrap_or_default(), 2825 timeout: time.flatten().unwrap_or(FIVE_MINUTES), 2826 authenticator_selection: auth.flatten().unwrap_or( 2827 AuthenticatorSelectionCriteria { 2828 authenticator_attachment: AuthenticatorAttachment::None, 2829 resident_key: ResidentKeyRequirement::Discouraged, 2830 user_verification: UserVerificationRequirement::Preferred, 2831 }, 2832 ), 2833 hints: hint.flatten().unwrap_or_default(), 2834 extensions: ext.flatten().unwrap_or_default(), 2835 }) 2836 } 2837 } 2838 /// Fields for `PublicKeyCredentialCreationOptionsOwned`. 2839 const FIELDS: &[&str; 11] = &[ 2840 RP, 2841 USER, 2842 CHALLENGE, 2843 PUB_KEY_CRED_PARAMS, 2844 TIMEOUT, 2845 EXCLUDE_CREDENTIALS, 2846 AUTHENTICATOR_SELECTION, 2847 HINTS, 2848 EXTENSIONS, 2849 ATTESTATION, 2850 ATTESTATION_FORMATS, 2851 ]; 2852 deserializer.deserialize_struct( 2853 "PublicKeyCredentialCreationOptionsOwned", 2854 FIELDS, 2855 PublicKeyCredentialCreationOptionsOwnedVisitor, 2856 ) 2857 } 2858 } 2859 /// Deserializes client-supplied data to assist in the creation of [`CredentialCreationOptions`]. 2860 /// 2861 /// It's common to tailor a registration ceremony based on a user's environment. The options that should be 2862 /// used are then sent to the server. For example, [`CredentialMediationRequirement::Conditional`] ceremonies 2863 /// typically work best for [`AuthenticatorAttachment::Platform`] authenticators; a subset of which cannot 2864 /// rely on [`UserVerificationRequirement::Required`]. Unfortunately one may not want to use 2865 /// [`UserVerificationRequirement::Preferred`] unconditionally either since security keys may benefit from 2866 /// [`CredProtect::UserVerificationRequired`] which can typically only be used when 2867 /// [`UserVerificationRequirement::Required`] is requested since many user agents error otherwise. 2868 /// 2869 /// To facilitate this, [`Self::deserialize`] can be used to deserialize the data sent from the client. 2870 #[derive(Debug)] 2871 pub struct ClientCredentialCreationOptions<const USER_LEN: usize> { 2872 /// See [`CredentialCreationOptions::mediation`]. 2873 pub mediation: CredentialMediationRequirement, 2874 /// See [`CredentialCreationOptions::public_key`]. 2875 pub public_key: PublicKeyCredentialCreationOptionsOwned<USER_LEN>, 2876 } 2877 impl<'de, const USER_LEN: usize> Deserialize<'de> for ClientCredentialCreationOptions<USER_LEN> 2878 where 2879 PublicKeyCredentialCreationOptionsOwned<USER_LEN>: Deserialize<'de>, 2880 { 2881 /// Deserializes a `struct` according to the following pseudo-schema: 2882 /// 2883 /// ```json 2884 /// { 2885 /// "mediation": null | "required" | "conditional", 2886 /// "publicKey": null | <PublicKeyCredentialCreationOptionsOwned> 2887 /// } 2888 /// ``` 2889 /// 2890 /// where none of the fields are required and `"publicKey"` is deserialized according to 2891 /// [`PublicKeyCredentialCreationOptionsOwned::deserialize`]. If any field is missing or is `null`, then 2892 /// the corresponding [`Default`] `impl` will be used. 2893 /// 2894 /// Unknown or duplicate fields lead to an error. 2895 #[inline] 2896 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2897 where 2898 D: Deserializer<'de>, 2899 { 2900 /// `Visitor` for `ClientCredentialCreationOptions`. 2901 struct ClientCredentialCreationOptionsVisitor<const LEN: usize>; 2902 impl<'d, const LEN: usize> Visitor<'d> for ClientCredentialCreationOptionsVisitor<LEN> 2903 where 2904 PublicKeyCredentialCreationOptionsOwned<LEN>: Deserialize<'d>, 2905 { 2906 type Value = ClientCredentialCreationOptions<LEN>; 2907 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2908 formatter.write_str("ClientCredentialCreationOptions") 2909 } 2910 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 2911 where 2912 A: MapAccess<'d>, 2913 { 2914 /// Field in `ClientCredentialCreationOptions`. 2915 enum Field { 2916 /// `mediation`. 2917 Mediation, 2918 /// `publicKey` 2919 PublicKey, 2920 } 2921 impl<'e> Deserialize<'e> for Field { 2922 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 2923 where 2924 D: Deserializer<'e>, 2925 { 2926 /// `Visitor` for `Field`. 2927 struct FieldVisitor; 2928 impl Visitor<'_> for FieldVisitor { 2929 type Value = Field; 2930 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 2931 write!(formatter, "'{MEDIATION}' or '{PUBLIC_KEY_NO_HYPEN}'") 2932 } 2933 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 2934 where 2935 E: Error, 2936 { 2937 match v { 2938 MEDIATION => Ok(Field::Mediation), 2939 PUBLIC_KEY_NO_HYPEN => Ok(Field::PublicKey), 2940 _ => Err(E::unknown_field(v, FIELDS)), 2941 } 2942 } 2943 } 2944 deserializer.deserialize_identifier(FieldVisitor) 2945 } 2946 } 2947 let mut med = None; 2948 let mut key = None; 2949 while let Some(k) = map.next_key()? { 2950 match k { 2951 Field::Mediation => { 2952 if med.is_some() { 2953 return Err(Error::duplicate_field(MEDIATION)); 2954 } 2955 med = map.next_value::<Option<_>>().map(Some)?; 2956 } 2957 Field::PublicKey => { 2958 if key.is_some() { 2959 return Err(Error::duplicate_field(PUBLIC_KEY_NO_HYPEN)); 2960 } 2961 key = map.next_value::<Option<_>>().map(Some)?; 2962 } 2963 } 2964 } 2965 Ok(ClientCredentialCreationOptions { 2966 mediation: med.flatten().unwrap_or_default(), 2967 public_key: key.flatten().unwrap_or_default(), 2968 }) 2969 } 2970 } 2971 /// Fields for `ClientCredentialCreationOptions`. 2972 const FIELDS: &[&str; 2] = &[MEDIATION, PUBLIC_KEY_NO_HYPEN]; 2973 deserializer.deserialize_struct( 2974 "ClientCredentialCreationOptions", 2975 FIELDS, 2976 ClientCredentialCreationOptionsVisitor, 2977 ) 2978 } 2979 }