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