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