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