ser.rs (47171B)
1 extern crate alloc; 2 use super::{ 3 AllAcceptedCredentialsOptions, AuthTransports, AuthenticatorAttachment, AuthenticatorTransport, 4 Challenge, CredentialId, CurrentUserDetailsOptions, Origin, SentChallenge, 5 }; 6 use alloc::borrow::Cow; 7 use core::{ 8 fmt::{self, Formatter}, 9 marker::PhantomData, 10 }; 11 use serde::{ 12 de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, SeqAccess, Unexpected, Visitor}, 13 ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer}, 14 }; 15 /// [`"ble"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-ble). 16 const BLE: &str = "ble"; 17 /// [`"hybrid"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-hybrid). 18 const HYBRID: &str = "hybrid"; 19 /// [`"internal"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-internal). 20 const INTERNAL: &str = "internal"; 21 /// [`"nfc"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-nfc). 22 const NFC: &str = "nfc"; 23 /// [`"smart-card"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-smart-card). 24 const SMART_CARD: &str = "smart-card"; 25 /// [`"usb"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatortransport-usb). 26 const USB: &str = "usb"; 27 impl Serialize for AuthenticatorTransport { 28 /// Serializes `self` as 29 /// [`AuthenticatorTransport`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatortransport). 30 /// 31 /// # Examples 32 /// 33 /// ``` 34 /// # use webauthn_rp::response::AuthenticatorTransport; 35 /// assert_eq!( 36 /// serde_json::to_string(&AuthenticatorTransport::Usb)?, 37 /// r#""usb""# 38 /// ); 39 /// # Ok::<_, serde_json::Error>(()) 40 /// ``` 41 #[inline] 42 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 43 where 44 S: Serializer, 45 { 46 serializer.serialize_str(match *self { 47 Self::Ble => BLE, 48 Self::Hybrid => HYBRID, 49 Self::Internal => INTERNAL, 50 Self::Nfc => NFC, 51 Self::SmartCard => SMART_CARD, 52 Self::Usb => USB, 53 }) 54 } 55 } 56 impl<'de> Deserialize<'de> for AuthenticatorTransport { 57 /// Deserializes [`prim@str`] based on 58 /// [`AuthenticatorTransport`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatortransport). 59 /// 60 /// Note `"cable"` is also supported and will be interpreted as [`Self::Hybrid`]. 61 /// 62 /// # Examples 63 /// 64 /// ``` 65 /// # use webauthn_rp::response::AuthenticatorTransport; 66 /// assert!(matches!( 67 /// serde_json::from_str::<AuthenticatorTransport>(r#""usb""#)?, 68 /// AuthenticatorTransport::Usb 69 /// )); 70 /// // Case matters. 71 /// assert!(serde_json::from_str::<AuthenticatorTransport>(r#""Usb""#).is_err()); 72 /// # Ok::<_, serde_json::Error>(()) 73 /// ``` 74 #[inline] 75 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 76 where 77 D: Deserializer<'de>, 78 { 79 /// `Visitor` for `AuthenticatorTransport`. 80 struct AuthenticatorTransportVisitor; 81 impl Visitor<'_> for AuthenticatorTransportVisitor { 82 type Value = AuthenticatorTransport; 83 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 84 formatter.write_str("AuthenticatorTransport") 85 } 86 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 87 where 88 E: Error, 89 { 90 /// Legacy version of [`Self::Hybrid`]. 91 const CA_BLE: &str = "cable"; 92 match v { 93 BLE => Ok(AuthenticatorTransport::Ble), 94 CA_BLE | HYBRID => Ok(AuthenticatorTransport::Hybrid), 95 INTERNAL => Ok(AuthenticatorTransport::Internal), 96 NFC => Ok(AuthenticatorTransport::Nfc), 97 SMART_CARD => Ok(AuthenticatorTransport::SmartCard), 98 USB => Ok(AuthenticatorTransport::Usb), 99 _ => Err(E::invalid_value( 100 Unexpected::Str(v), 101 &format!( 102 "'{BLE}', '{CA_BLE}', '{HYBRID}', '{INTERNAL}', '{NFC}', '{SMART_CARD}', or '{USB}'" 103 ) 104 .as_str(), 105 )), 106 } 107 } 108 } 109 deserializer.deserialize_str(AuthenticatorTransportVisitor) 110 } 111 } 112 impl Serialize for AuthTransports { 113 /// Serializes `self` based on 114 /// [`transports`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports). 115 /// 116 /// # Examples 117 /// 118 /// ``` 119 /// # use webauthn_rp::response::{AuthTransports, AuthenticatorTransport}; 120 /// # #[cfg(feature = "custom")] 121 /// assert_eq!( 122 /// serde_json::to_string(&AuthTransports::ALL)?, 123 /// r#"["ble","hybrid","internal","nfc","smart-card","usb"]"# 124 /// ); 125 /// # Ok::<_, serde_json::Error>(()) 126 /// ``` 127 #[expect(clippy::unreachable, reason = "there is a bug, so we want to crash")] 128 #[inline] 129 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 130 where 131 S: Serializer, 132 { 133 let count = usize::try_from(self.count()) 134 .unwrap_or_else(|_e| unreachable!("there is a bug in AuthenticatorTransports::count")); 135 serializer.serialize_seq(Some(count)).and_then(|mut ser| { 136 if self.contains(AuthenticatorTransport::Ble) { 137 ser.serialize_element(&AuthenticatorTransport::Ble) 138 } else { 139 Ok(()) 140 } 141 .and_then(|()| { 142 if self.contains(AuthenticatorTransport::Hybrid) { 143 ser.serialize_element(&AuthenticatorTransport::Hybrid) 144 } else { 145 Ok(()) 146 } 147 .and_then(|()| { 148 if self.contains(AuthenticatorTransport::Internal) { 149 ser.serialize_element(&AuthenticatorTransport::Internal) 150 } else { 151 Ok(()) 152 } 153 .and_then(|()| { 154 if self.contains(AuthenticatorTransport::Nfc) { 155 ser.serialize_element(&AuthenticatorTransport::Nfc) 156 } else { 157 Ok(()) 158 } 159 .and_then(|()| { 160 if self.contains(AuthenticatorTransport::SmartCard) { 161 ser.serialize_element(&AuthenticatorTransport::SmartCard) 162 } else { 163 Ok(()) 164 } 165 .and_then(|()| { 166 if self.contains(AuthenticatorTransport::Usb) { 167 ser.serialize_element(&AuthenticatorTransport::Usb) 168 } else { 169 Ok(()) 170 } 171 .and_then(|()| ser.end()) 172 }) 173 }) 174 }) 175 }) 176 }) 177 }) 178 } 179 } 180 impl<'de> Deserialize<'de> for AuthTransports { 181 /// Deserializes a sequence based on 182 /// [`transports`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptor-transports). 183 /// 184 /// # Examples 185 /// 186 /// ``` 187 /// # use webauthn_rp::response::{AuthTransports, AuthenticatorTransport}; 188 /// # #[cfg(feature = "custom")] 189 /// assert_eq!( 190 /// serde_json::from_str::<AuthTransports>( 191 /// r#"["ble","hybrid","internal","nfc","smart-card","usb"]"# 192 /// ) 193 /// ?.count(), 194 /// 6 195 /// ); 196 /// // Errors since `"foo"` is not valid. 197 /// assert!(serde_json::from_str::<AuthTransports>(r#"["foo"]"#).is_err()); 198 /// # Ok::<_, serde_json::Error>(()) 199 /// ``` 200 #[inline] 201 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 202 where 203 D: Deserializer<'de>, 204 { 205 /// `Visitor` for `AuthTransports`. 206 struct AuthTransportsVisitor; 207 impl<'d> Visitor<'d> for AuthTransportsVisitor { 208 type Value = AuthTransports; 209 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 210 formatter.write_str("AuthTransports") 211 } 212 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error> 213 where 214 A: SeqAccess<'d>, 215 { 216 let mut transports = AuthTransports::new(); 217 while let Some(val) = seq.next_element::<AuthenticatorTransport>()? { 218 transports = transports.add_transport(val); 219 } 220 Ok(transports) 221 } 222 } 223 deserializer.deserialize_seq(AuthTransportsVisitor) 224 } 225 } 226 impl<T: AsRef<[u8]>> Serialize for CredentialId<T> { 227 /// Serializes `self` into a [`prim@str`] based on 228 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptorjson-id). 229 /// 230 /// # Examples 231 /// 232 /// ``` 233 /// # use webauthn_rp::response::CredentialId; 234 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 235 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 236 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 237 /// # #[cfg(feature = "custom")] 238 /// assert_eq!( 239 /// serde_json::to_string(&CredentialId::try_from(vec![0; 16])?).unwrap(), 240 /// r#""AAAAAAAAAAAAAAAAAAAAAA""# 241 /// ); 242 /// # Ok::<_, webauthn_rp::AggErr>(()) 243 ///``` 244 #[inline] 245 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 246 where 247 S: Serializer, 248 { 249 serializer.serialize_str(base64url_nopad::encode(self.0.as_ref()).as_str()) 250 } 251 } 252 impl<'de> Deserialize<'de> for CredentialId<Vec<u8>> { 253 /// Deserializes [`prim@str`] based on 254 /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialdescriptorjson-id). 255 /// 256 /// # Examples 257 /// 258 /// ``` 259 /// # use webauthn_rp::response::CredentialId; 260 /// # #[cfg(feature = "custom")] 261 /// assert_eq!( 262 /// serde_json::from_str::<CredentialId<_>>(r#""AAAAAAAAAAAAAAAAAAAAAA""#).unwrap(), 263 /// CredentialId::try_from(vec![0; 16])? 264 /// ); 265 /// # Ok::<_, webauthn_rp::AggErr>(()) 266 ///``` 267 #[inline] 268 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 269 where 270 D: Deserializer<'de>, 271 { 272 /// `Visitor` for `CredentialId`. 273 struct CredentialIdVisitor; 274 impl Visitor<'_> for CredentialIdVisitor { 275 type Value = CredentialId<Vec<u8>>; 276 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 277 formatter.write_str("CredentialId") 278 } 279 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 280 where 281 E: Error, 282 { 283 /// Minimum possible encoded length of a `CredentialId`. 284 const MIN_LEN: usize = base64url_nopad::encode_len(super::CRED_ID_MIN_LEN); 285 /// Maximum possible encoded length of a `CredentialId`. 286 const MAX_LEN: usize = base64url_nopad::encode_len(super::CRED_ID_MAX_LEN); 287 if (MIN_LEN..=MAX_LEN).contains(&v.len()) { 288 base64url_nopad::decode(v.as_bytes()) 289 .map_err(E::custom) 290 .map(CredentialId) 291 } else { 292 Err(E::invalid_value( 293 Unexpected::Str(v), 294 &"16 to 1023 bytes encoded in base64url without padding", 295 )) 296 } 297 } 298 } 299 deserializer.deserialize_str(CredentialIdVisitor) 300 } 301 } 302 impl<'de> Deserialize<'de> for AuthenticatorAttachment { 303 /// Deserializes [`prim@str`] based on 304 /// [`AuthenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatorattachment). 305 /// 306 /// # Examples 307 /// 308 /// ``` 309 /// # use webauthn_rp::response::AuthenticatorAttachment; 310 /// assert!(matches!( 311 /// serde_json::from_str::<AuthenticatorAttachment>(r#""cross-platform""#)?, 312 /// AuthenticatorAttachment::CrossPlatform) 313 /// ); 314 /// assert!(matches!( 315 /// serde_json::from_str::<AuthenticatorAttachment>(r#""platform""#)?, 316 /// AuthenticatorAttachment::Platform) 317 /// ); 318 /// // Case matters. 319 /// assert!(serde_json::from_str::<AuthenticatorAttachment>(r#""Platform""#).is_err()); 320 /// // `AuthenticatorAttachment::None` is not deserializable. 321 /// assert!(serde_json::from_str::<AuthenticatorAttachment>(r#""""#).is_err()); 322 /// assert!(serde_json::from_str::<AuthenticatorAttachment>("null").is_err()); 323 /// assert!(serde_json::from_str::<AuthenticatorAttachment>(r#""none""#).is_err()); 324 /// assert!(serde_json::from_str::<AuthenticatorAttachment>(r#""None""#).is_err()); 325 /// # Ok::<_, serde_json::Error>(()) 326 ///``` 327 #[inline] 328 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 329 where 330 D: Deserializer<'de>, 331 { 332 /// `Visitor` for `AuthenticatorAttachment`. 333 struct AuthenticatorAttachmentVisitor; 334 impl Visitor<'_> for AuthenticatorAttachmentVisitor { 335 type Value = AuthenticatorAttachment; 336 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 337 formatter.write_str("AuthenticatorAttachment") 338 } 339 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 340 where 341 E: Error, 342 { 343 /// `"platform"` 344 const PLATFORM: &str = "platform"; 345 /// `"cross-platform"` 346 const CROSS_PLATFORM: &str = "cross-platform"; 347 match v { 348 PLATFORM => Ok(AuthenticatorAttachment::Platform), 349 CROSS_PLATFORM => Ok(AuthenticatorAttachment::CrossPlatform), 350 _ => Err(E::invalid_value( 351 Unexpected::Str(v), 352 &format!("'{PLATFORM}' or '{CROSS_PLATFORM}'").as_str(), 353 )), 354 } 355 } 356 } 357 deserializer.deserialize_str(AuthenticatorAttachmentVisitor) 358 } 359 } 360 /// Container of data that was encoded in base64url. 361 pub(crate) struct Base64DecodedVal(pub Vec<u8>); 362 impl<'de> Deserialize<'de> for Base64DecodedVal { 363 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 364 where 365 D: Deserializer<'de>, 366 { 367 /// `Visitor` for `Base64DecodedVal`. 368 struct Base64DecodedValVisitor; 369 impl Visitor<'_> for Base64DecodedValVisitor { 370 type Value = Base64DecodedVal; 371 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 372 formatter.write_str("base64url-encoded data") 373 } 374 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 375 where 376 E: Error, 377 { 378 base64url_nopad::decode(v.as_bytes()) 379 .map_err(E::custom) 380 .map(Base64DecodedVal) 381 } 382 } 383 deserializer.deserialize_str(Base64DecodedValVisitor) 384 } 385 } 386 impl<'de> Deserialize<'de> for SentChallenge { 387 /// Deserializes `[u8]` or [`prim@str`] based on 388 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge). 389 /// 390 /// Specifically a `[u8]` or `str` is base64url-decoded and interpreted as a little-endian 391 /// `u128`. 392 /// 393 /// # Examples 394 /// 395 /// ``` 396 /// # use webauthn_rp::response::SentChallenge; 397 /// assert_eq!( 398 /// serde_json::from_slice::<SentChallenge>(br#""AAAAAAAAAAAAAAAAAAAAAA""#)?, 399 /// SentChallenge(0) 400 /// ); 401 /// # Ok::<_, serde_json::Error>(()) 402 ///``` 403 #[inline] 404 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 405 where 406 D: Deserializer<'de>, 407 { 408 /// `Visitor` for `SentChallenge`. 409 struct ChallengeVisitor; 410 impl Visitor<'_> for ChallengeVisitor { 411 type Value = SentChallenge; 412 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 413 formatter.write_str( 414 "base64 encoding of the 16-byte challenge in a URL safe way without padding", 415 ) 416 } 417 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E> 418 where 419 E: Error, 420 { 421 if v.len() == Challenge::BASE64_LEN { 422 let mut data = [0; 16]; 423 base64url_nopad::decode_buffer_exact(v, data.as_mut_slice()) 424 .map_err(E::custom) 425 .map(|()| SentChallenge::from_array(data)) 426 } else { 427 Err(E::invalid_value( 428 Unexpected::Bytes(v), 429 &"22 bytes encoded in base64url without padding", 430 )) 431 } 432 } 433 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 434 where 435 E: Error, 436 { 437 self.visit_bytes(v.as_bytes()) 438 } 439 } 440 deserializer.deserialize_bytes(ChallengeVisitor) 441 } 442 } 443 impl<'de: 'a, 'a> Deserialize<'de> for Origin<'a> { 444 /// Deserializes [`prim@str`] by borrowing the data when possible. 445 /// 446 /// # Examples 447 /// 448 /// ``` 449 /// # extern crate alloc; 450 /// # use alloc::borrow::Cow; 451 /// # use webauthn_rp::response::Origin; 452 /// let origin_borrowed = "https://example.com"; 453 /// let origin_owned = "\\\\https://example.com"; 454 /// assert!( 455 /// matches!(serde_json::from_str::<Origin<'_>>(format!("\"{origin_borrowed}\"").as_str())?.0, Cow::Borrowed(val) if val == origin_borrowed) 456 /// ); 457 /// assert!( 458 /// matches!(serde_json::from_str::<Origin<'_>>(format!("\"{origin_owned}\"").as_str())?.0, Cow::Owned(val) if *val.as_bytes() == origin_owned.as_bytes()[1..]) 459 /// ); 460 /// # Ok::<_, serde_json::Error>(()) 461 ///``` 462 #[inline] 463 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 464 where 465 D: Deserializer<'de>, 466 { 467 /// `Visitor` for `Origin`. 468 struct OriginVisitor<'b>(PhantomData<fn() -> &'b ()>); 469 impl<'d: 'b, 'b> Visitor<'d> for OriginVisitor<'b> { 470 type Value = Origin<'b>; 471 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 472 formatter.write_str("Origin") 473 } 474 fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E> 475 where 476 E: Error, 477 { 478 Ok(Origin(Cow::Borrowed(v))) 479 } 480 fn visit_string<E>(self, v: String) -> Result<Self::Value, E> 481 where 482 E: Error, 483 { 484 Ok(Origin(Cow::Owned(v))) 485 } 486 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 487 where 488 E: Error, 489 { 490 self.visit_string(v.to_owned()) 491 } 492 } 493 deserializer.deserialize_str(OriginVisitor(PhantomData)) 494 } 495 } 496 /// `trait` that returns an empty instance of `Self`. 497 pub(super) trait ClientExtensions: Sized { 498 /// Returns an empty instance of `Self`. 499 fn empty() -> Self; 500 } 501 /// Response for both registration and authentication ceremonies. 502 /// 503 /// [`Self::raw_id`] is always `Some` when `!RELAXED` or `!REG`. 504 /// 505 /// `RELAXED` and `REG` are used purely for deserialization purposes. 506 pub(super) struct PublicKeyCredential<const RELAXED: bool, const REG: bool, AuthResp, Ext> { 507 /// [`rawId`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-rawid). 508 pub id: Option<CredentialId<Vec<u8>>>, 509 /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response). 510 pub response: AuthResp, 511 /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment). 512 pub authenticator_attachment: AuthenticatorAttachment, 513 /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults). 514 pub client_extension_results: Ext, 515 } 516 /// Deserializes the value for type. 517 pub(crate) struct Type; 518 impl<'e> Deserialize<'e> for Type { 519 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 520 where 521 D: Deserializer<'e>, 522 { 523 /// `Visitor` for `Type`. 524 struct TypeVisitor; 525 impl Visitor<'_> for TypeVisitor { 526 type Value = Type; 527 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 528 formatter.write_str(PUBLIC_KEY) 529 } 530 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 531 where 532 E: Error, 533 { 534 if v == PUBLIC_KEY { 535 Ok(Type) 536 } else { 537 Err(E::invalid_value(Unexpected::Str(v), &PUBLIC_KEY)) 538 } 539 } 540 } 541 deserializer.deserialize_str(TypeVisitor) 542 } 543 } 544 /// `Visitor` for `PublicKeyCredential`. 545 /// 546 /// When `!RELAXED`, `REG` is ignored and all fields must exist and unknown fields are not allowed. 547 /// When `RELAXED`, unknown fields are ignored. 548 /// When `RELAXED` and `REG`, only `response` is required. 549 /// When `RELAXED` and `!REG`, only `id` and `response` are required. 550 struct PublicKeyCredentialVisitor<const RELAXED: bool, const REG: bool, R, E>( 551 pub PhantomData<fn() -> (R, E)>, 552 ); 553 impl<'d, const REL: bool, const REGI: bool, R, E> Visitor<'d> 554 for PublicKeyCredentialVisitor<REL, REGI, R, E> 555 where 556 R: Deserialize<'d>, 557 E: for<'a> Deserialize<'a> + ClientExtensions, 558 { 559 type Value = PublicKeyCredential<REL, REGI, R, E>; 560 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 561 formatter.write_str("PublicKeyCredential") 562 } 563 #[expect( 564 clippy::too_many_lines, 565 reason = "rather hide all the internal logic instead instead of moving into an outer scope" 566 )] 567 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 568 where 569 A: MapAccess<'d>, 570 { 571 /// `PublicKeyCredentialJSON` fields. 572 enum Field<const IGNORE_UNKNOWN: bool> { 573 /// `id`. 574 Id, 575 /// `type`. 576 Type, 577 /// `rawId`. 578 RawId, 579 /// `response`. 580 Response, 581 /// `authenticatorAttachment`. 582 AuthenticatorAttachment, 583 /// `clientExtensionResults`. 584 ClientExtensionResults, 585 /// Unknown field. 586 Other, 587 } 588 impl<'e, const IGNORE: bool> Deserialize<'e> for Field<IGNORE> { 589 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 590 where 591 D: Deserializer<'e>, 592 { 593 /// `Visitor` for `Field`. 594 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 595 impl<const IGN: bool> Visitor<'_> for FieldVisitor<IGN> { 596 type Value = Field<IGN>; 597 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 598 write!( 599 formatter, 600 "'{ID}', '{TYPE}', '{RAW_ID}', '{RESPONSE}', '{AUTHENTICATOR_ATTACHMENT}', or '{CLIENT_EXTENSION_RESULTS}'" 601 ) 602 } 603 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 604 where 605 E: Error, 606 { 607 match v { 608 ID => Ok(Field::Id), 609 TYPE => Ok(Field::Type), 610 RAW_ID => Ok(Field::RawId), 611 RESPONSE => Ok(Field::Response), 612 AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment), 613 CLIENT_EXTENSION_RESULTS => Ok(Field::ClientExtensionResults), 614 _ => { 615 if IGN { 616 Ok(Field::Other) 617 } else { 618 Err(E::unknown_field(v, REG_FIELDS)) 619 } 620 } 621 } 622 } 623 } 624 deserializer.deserialize_identifier(FieldVisitor::<IGNORE>) 625 } 626 } 627 let mut opt_id = None; 628 let mut typ = false; 629 let mut raw = None; 630 let mut resp = None; 631 let mut attach = None; 632 let mut ext = None; 633 while let Some(key) = map.next_key::<Field<REL>>()? { 634 match key { 635 Field::Id => { 636 if opt_id.is_some() { 637 return Err(Error::duplicate_field(ID)); 638 } 639 opt_id = map.next_value::<CredentialId<_>>().map(Some)?; 640 } 641 Field::Type => { 642 if typ { 643 return Err(Error::duplicate_field(TYPE)); 644 } 645 typ = map.next_value::<Type>().map(|_| true)?; 646 } 647 Field::RawId => { 648 if raw.is_some() { 649 return Err(Error::duplicate_field(RAW_ID)); 650 } 651 raw = map.next_value::<CredentialId<_>>().map(Some)?; 652 } 653 Field::Response => { 654 if resp.is_some() { 655 return Err(Error::duplicate_field(RESPONSE)); 656 } 657 resp = map.next_value::<R>().map(Some)?; 658 } 659 Field::AuthenticatorAttachment => { 660 if attach.is_some() { 661 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT)); 662 } 663 attach = map.next_value().map(Some)?; 664 } 665 Field::ClientExtensionResults => { 666 if ext.is_some() { 667 return Err(Error::duplicate_field(CLIENT_EXTENSION_RESULTS)); 668 } 669 ext = map.next_value::<Option<E>>().map(Some)?; 670 } 671 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 672 } 673 } 674 resp.ok_or_else(|| Error::missing_field(RESPONSE)) 675 .and_then(|response| { 676 opt_id.map_or_else( 677 || { 678 if REL && REGI { 679 Ok(None) 680 } else { 681 Err(Error::missing_field(ID)) 682 } 683 }, 684 |id| Ok(Some(id)), 685 ) 686 .and_then(|id| { 687 raw.map_or_else( 688 || { 689 if REL { 690 Ok(()) 691 } else { 692 Err(Error::missing_field(RAW_ID)) 693 } 694 }, 695 |raw_id| { 696 id.as_ref().map_or_else( 697 || Ok(()), 698 |i| { 699 if raw_id == i { 700 Ok(()) 701 } else { 702 Err(Error::invalid_value( 703 Unexpected::Bytes(raw_id.as_ref()), 704 &format!("{ID} and {RAW_ID} to match: {i:?}").as_str(), 705 )) 706 } 707 }, 708 ) 709 } 710 ) 711 .and_then(|()| { 712 ext.ok_or(false).and_then(|opt_ext| opt_ext.ok_or(true)).map_or_else( 713 |flag| { 714 if REL { 715 Ok(E::empty()) 716 } else if flag { 717 Err(Error::invalid_type(Unexpected::Other("null"), &format!("{CLIENT_EXTENSION_RESULTS} to be a map of allowed client extensions").as_str())) 718 } else { 719 Err(Error::missing_field(CLIENT_EXTENSION_RESULTS)) 720 } 721 }, 722 Ok 723 ) 724 .and_then(|client_extension_results| { 725 if typ || REL { 726 Ok(PublicKeyCredential { 727 id, 728 response, 729 authenticator_attachment: attach.flatten().unwrap_or(AuthenticatorAttachment::None), 730 client_extension_results, 731 }) 732 } else { 733 Err(Error::missing_field(TYPE)) 734 } 735 }) 736 }) 737 }) 738 }) 739 } 740 } 741 /// `"id"`. 742 const ID: &str = "id"; 743 /// `"type"`. 744 const TYPE: &str = "type"; 745 /// `"rawId"`. 746 const RAW_ID: &str = "rawId"; 747 /// `"response"`. 748 const RESPONSE: &str = "response"; 749 /// `"authenticatorAttachment"`. 750 const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment"; 751 /// `"clientExtensionResults"`. 752 const CLIENT_EXTENSION_RESULTS: &str = "clientExtensionResults"; 753 /// `"public-key"`. 754 const PUBLIC_KEY: &str = "public-key"; 755 /// Fields for `PublicKeyCredentialJSON`. 756 const REG_FIELDS: &[&str; 6] = &[ 757 ID, 758 TYPE, 759 RAW_ID, 760 RESPONSE, 761 AUTHENTICATOR_ATTACHMENT, 762 CLIENT_EXTENSION_RESULTS, 763 ]; 764 impl<'de, const REL: bool, const REGI: bool, R, E> Deserialize<'de> 765 for PublicKeyCredential<REL, REGI, R, E> 766 where 767 R: Deserialize<'de>, 768 E: for<'a> Deserialize<'a> + ClientExtensions, 769 { 770 /// Deserializes a `struct` based on 771 /// [`PublicKeyCredentialJSON`](https://www.w3.org/TR/webauthn-3/#typedefdef-publickeycredentialjson). 772 /// 773 /// `REL` iff unknown fields should be ignored and not cause an error. 774 /// `REGI` iff `Self` is from a registration ceremony. 775 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 776 where 777 D: Deserializer<'de>, 778 { 779 deserializer.deserialize_struct( 780 "PublicKeyCredential", 781 REG_FIELDS, 782 PublicKeyCredentialVisitor::<REL, REGI, _, _>(PhantomData), 783 ) 784 } 785 } 786 use super::UserHandle; 787 impl<const USER_LEN: usize> Serialize for AllAcceptedCredentialsOptions<'_, '_, USER_LEN> 788 where 789 UserHandle<USER_LEN>: Serialize, 790 { 791 /// Serializes `self` to conform with 792 /// [`AllAcceptedCredentialsOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-allacceptedcredentialsoptions). 793 /// 794 /// # Examples 795 /// 796 /// ``` 797 /// # use core::str::FromStr; 798 /// # #[cfg(feature = "bin")] 799 /// # use webauthn_rp::bin::Decode; 800 /// # use webauthn_rp::{ 801 /// # request::{register::{UserHandle, USER_HANDLE_MIN_LEN}, AsciiDomain, RpId}, 802 /// # response::{error::CredentialIdErr, AllAcceptedCredentialsOptions, CredentialId}, 803 /// # }; 804 /// /// Retrieves the `CredentialId`s associated with `user_id` from the database. 805 /// # #[cfg(all(feature = "bin", feature = "custom"))] 806 /// fn get_credential_ids(user_id: UserHandle<USER_HANDLE_MIN_LEN>) -> Result<Vec<CredentialId<Vec<u8>>>, CredentialIdErr> { 807 /// // ⋮ 808 /// # CredentialId::decode(vec![0; 16]).map(|cred_id| vec![cred_id]) 809 /// } 810 /// /// Retrieves the `UserHandle` from a session cookie. 811 /// # #[cfg(feature = "custom")] 812 /// fn get_user_handle() -> UserHandle<USER_HANDLE_MIN_LEN> { 813 /// // ⋮ 814 /// # [0].into() 815 /// } 816 /// # #[cfg(feature = "custom")] 817 /// let user_id = get_user_handle(); 818 /// # #[cfg(all(feature = "bin", feature = "custom"))] 819 /// assert_eq!( 820 /// serde_json::to_string(&AllAcceptedCredentialsOptions { 821 /// rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 822 /// user_id: &user_id, 823 /// all_accepted_credential_ids: get_credential_ids(user_id)?, 824 /// }) 825 /// .unwrap(), 826 /// r#"{"rpId":"example.com","userId":"AA","allAcceptedCredentialIds":["AAAAAAAAAAAAAAAAAAAAAA"]}"# 827 /// ); 828 /// # Ok::<_, webauthn_rp::AggErr>(()) 829 /// ``` 830 #[inline] 831 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 832 where 833 S: Serializer, 834 { 835 serializer 836 .serialize_struct("AllAcceptedCredentialsOptions", 3) 837 .and_then(|mut ser| { 838 ser.serialize_field("rpId", self.rp_id).and_then(|()| { 839 ser.serialize_field("userId", &self.user_id).and_then(|()| { 840 ser.serialize_field( 841 "allAcceptedCredentialIds", 842 &self.all_accepted_credential_ids, 843 ) 844 .and_then(|()| ser.end()) 845 }) 846 }) 847 }) 848 } 849 } 850 impl<const LEN: usize> Serialize for CurrentUserDetailsOptions<'_, '_, '_, '_, LEN> 851 where 852 UserHandle<LEN>: Serialize, 853 { 854 /// Serializes `self` to conform with 855 /// [`CurrentUserDetailsOptions`](https://www.w3.org/TR/webauthn-3/#dictdef-currentuserdetailsoptions). 856 /// 857 /// # Examples 858 /// 859 /// ``` 860 /// # use core::str::FromStr; 861 /// # #[cfg(feature = "bin")] 862 /// # use webauthn_rp::bin::Decode; 863 /// # use webauthn_rp::{ 864 /// # request::{register::{Nickname, PublicKeyCredentialUserEntity, UserHandle, USER_HANDLE_MIN_LEN, Username}, AsciiDomain, RpId}, 865 /// # response::CurrentUserDetailsOptions, 866 /// # AggErr, 867 /// # }; 868 /// /// Retrieves the `PublicKeyCredentialUserEntity` info associated with `user_id` from the database. 869 /// # #[cfg(feature = "bin")] 870 /// fn get_user_info(user_id: UserHandle<USER_HANDLE_MIN_LEN>) -> Result<(Username<'static>, Option<Nickname<'static>>), AggErr> { 871 /// // ⋮ 872 /// # Ok((Username::decode("foo").unwrap(), Some(Nickname::decode("foo").unwrap()))) 873 /// } 874 /// /// Retrieves the `UserHandle` from a session cookie. 875 /// # #[cfg(feature = "custom")] 876 /// fn get_user_handle() -> UserHandle<USER_HANDLE_MIN_LEN> { 877 /// // ⋮ 878 /// # [0].into() 879 /// } 880 /// # #[cfg(feature = "custom")] 881 /// let user_handle = get_user_handle(); 882 /// # #[cfg(all(feature = "bin", feature = "custom"))] 883 /// let (name, display_name) = get_user_info(user_handle)?; 884 /// # #[cfg(all(feature = "bin", feature = "custom"))] 885 /// assert_eq!( 886 /// serde_json::to_string(&CurrentUserDetailsOptions { 887 /// rp_id: &RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?), 888 /// user: PublicKeyCredentialUserEntity { name, id: &user_handle, display_name, }, 889 /// }) 890 /// .unwrap(), 891 /// r#"{"rpId":"example.com","userId":"AA","name":"foo","displayName":"foo"}"# 892 /// ); 893 /// # Ok::<_, AggErr>(()) 894 /// ``` 895 #[inline] 896 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 897 where 898 S: Serializer, 899 { 900 serializer 901 .serialize_struct("CurrentUserDetailsOptions", 4) 902 .and_then(|mut ser| { 903 ser.serialize_field("rpId", self.rp_id).and_then(|()| { 904 ser.serialize_field("userId", &self.user.id).and_then(|()| { 905 ser.serialize_field("name", &self.user.name).and_then(|()| { 906 ser.serialize_field("displayName", &self.user.display_name) 907 .and_then(|()| ser.end()) 908 }) 909 }) 910 }) 911 }) 912 } 913 } 914 /// JSON `null`. 915 pub(crate) struct Null; 916 impl<'de> Deserialize<'de> for Null { 917 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 918 where 919 D: Deserializer<'de>, 920 { 921 /// `Visitor` for `Null`. 922 struct NullVisitor; 923 impl Visitor<'_> for NullVisitor { 924 type Value = Null; 925 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 926 formatter.write_str("null") 927 } 928 fn visit_none<E>(self) -> Result<Self::Value, E> 929 where 930 E: Error, 931 { 932 Ok(Null) 933 } 934 } 935 deserializer.deserialize_option(NullVisitor) 936 } 937 } 938 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues). 939 pub(super) struct AuthenticationExtensionsPrfValues; 940 /// `Visitor` for `AuthenticationExtensionsPrfValues`. 941 /// 942 /// Unknown fields are ignored iff `RELAXED`.`first` must always exist if `second` does. 943 /// `first` and `second` must be `null` if they exist. `first` must exist iff `!RELAXED`. 944 pub(super) struct AuthenticationExtensionsPrfValuesVisitor<const RELAXED: bool>; 945 impl<'d, const R: bool> Visitor<'d> for AuthenticationExtensionsPrfValuesVisitor<R> { 946 type Value = AuthenticationExtensionsPrfValues; 947 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 948 formatter.write_str("AuthenticationExtensionsPrfValues") 949 } 950 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 951 where 952 A: MapAccess<'d>, 953 { 954 /// Fields. 955 enum Field<const IGNORE_UNKNOWN: bool> { 956 /// `first` field. 957 First, 958 /// `second` field. 959 Second, 960 /// Unknown field. 961 Other, 962 } 963 impl<'e, const I: bool> Deserialize<'e> for Field<I> { 964 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 965 where 966 D: Deserializer<'e>, 967 { 968 /// `Visitor` for `Field`. 969 /// 970 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`. 971 struct FieldVisitor<const IGNORE_UNKNOWN: bool>; 972 impl<const IG: bool> Visitor<'_> for FieldVisitor<IG> { 973 type Value = Field<IG>; 974 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 975 write!(formatter, "'{FIRST}' or '{SECOND}'") 976 } 977 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 978 where 979 E: Error, 980 { 981 match v { 982 FIRST => Ok(Field::First), 983 SECOND => Ok(Field::Second), 984 _ => { 985 if IG { 986 Ok(Field::Other) 987 } else { 988 Err(E::unknown_field(v, PRF_VALUES_FIELDS)) 989 } 990 } 991 } 992 } 993 } 994 deserializer.deserialize_identifier(FieldVisitor) 995 } 996 } 997 let mut first = None; 998 let mut second = None; 999 while let Some(key) = map.next_key::<Field<R>>()? { 1000 match key { 1001 Field::First => { 1002 if first.is_some() { 1003 return Err(Error::duplicate_field(FIRST)); 1004 } 1005 first = map.next_value::<Null>().map(Some)?; 1006 } 1007 Field::Second => { 1008 if second.is_some() { 1009 return Err(Error::duplicate_field(SECOND)); 1010 } 1011 second = map.next_value::<Null>().map(Some)?; 1012 } 1013 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 1014 } 1015 } 1016 if first.is_some() || (R && second.is_none()) { 1017 Ok(AuthenticationExtensionsPrfValues) 1018 } else { 1019 Err(Error::missing_field(FIRST)) 1020 } 1021 } 1022 } 1023 /// `"first"` 1024 const FIRST: &str = "first"; 1025 /// `"second"` 1026 const SECOND: &str = "second"; 1027 /// `AuthenticationExtensionsPrfValues` fields. 1028 pub(super) const PRF_VALUES_FIELDS: &[&str; 2] = &[FIRST, SECOND]; 1029 impl<'de> Deserialize<'de> for AuthenticationExtensionsPrfValues { 1030 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1031 where 1032 D: Deserializer<'de>, 1033 { 1034 deserializer.deserialize_struct( 1035 "AuthenticationExtensionsPrfValues", 1036 PRF_VALUES_FIELDS, 1037 AuthenticationExtensionsPrfValuesVisitor::<false>, 1038 ) 1039 } 1040 } 1041 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs). 1042 /// 1043 /// `RELAXED` iff unknown fields are ignored. 1044 /// 1045 /// `REGISTRATION` iff 1046 /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled) 1047 /// is required (and must not be `null`); otherwise it's forbidden. 1048 /// 1049 /// The contained `Option` is `Some` iff `REGISTRATION`. 1050 pub(super) struct AuthenticationExtensionsPrfOutputsHelper< 1051 const RELAXED: bool, 1052 const REGISTRATION: bool, 1053 Prf, 1054 >(pub Option<bool>, pub PhantomData<fn() -> Prf>); 1055 /// `Visitor` for `AuthenticationExtensionsPrfOutputs`. 1056 /// 1057 /// Unknown fields are ignored iff `RELAXED`.`enabled` must exist and not be `null` iff `REGISTRATION`. 1058 struct AuthenticationExtensionsPrfOutputsVisitor<const RELAXED: bool, const REGISTRATION: bool, Prf>( 1059 PhantomData<fn() -> Prf>, 1060 ); 1061 impl<'d, const REL: bool, const REG: bool, Prf> Visitor<'d> 1062 for AuthenticationExtensionsPrfOutputsVisitor<REL, REG, Prf> 1063 where 1064 Prf: for<'a> Deserialize<'a>, 1065 { 1066 type Value = AuthenticationExtensionsPrfOutputsHelper<REL, REG, Prf>; 1067 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1068 formatter.write_str("AuthenticationExtensionsPrfOutputs") 1069 } 1070 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error> 1071 where 1072 A: MapAccess<'d>, 1073 { 1074 /// Fields. 1075 enum Field<const IGNORE_UNKNOWN: bool, const REGI: bool> { 1076 /// `enabled` field. 1077 Enabled, 1078 /// `results` field. 1079 Results, 1080 /// Unknown field. 1081 Other, 1082 } 1083 impl<'e, const I: bool, const R: bool> Deserialize<'e> for Field<I, R> { 1084 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1085 where 1086 D: Deserializer<'e>, 1087 { 1088 /// `Visitor` for `Field`. 1089 /// 1090 /// Unknown fields are ignored iff `IGNORE_UNKNOWN`. 1091 /// `enabled` is allowed to exist iff `REGI`. 1092 struct FieldVisitor<const IGNORE_UNKNOWN: bool, const REGI: bool>; 1093 impl<const IG: bool, const RE: bool> Visitor<'_> for FieldVisitor<IG, RE> { 1094 type Value = Field<IG, RE>; 1095 fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { 1096 if RE { 1097 write!(formatter, "'{ENABLED}' or '{RESULTS}'") 1098 } else { 1099 write!(formatter, "'{RESULTS}'") 1100 } 1101 } 1102 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E> 1103 where 1104 E: Error, 1105 { 1106 match v { 1107 ENABLED => { 1108 if RE { 1109 Ok(Field::Enabled) 1110 } else { 1111 Err(E::unknown_field(v, PRF_AUTH_OUTPUTS_FIELDS)) 1112 } 1113 } 1114 RESULTS => Ok(Field::Results), 1115 _ => { 1116 if IG { 1117 Ok(Field::Other) 1118 } else if RE { 1119 Err(E::unknown_field(v, PRF_REG_OUTPUTS_FIELDS)) 1120 } else { 1121 Err(E::unknown_field(v, PRF_AUTH_OUTPUTS_FIELDS)) 1122 } 1123 } 1124 } 1125 } 1126 } 1127 deserializer.deserialize_identifier(FieldVisitor) 1128 } 1129 } 1130 let mut enabled = None; 1131 let mut results = None; 1132 while let Some(key) = map.next_key::<Field<REL, REG>>()? { 1133 match key { 1134 Field::Enabled => { 1135 if enabled.is_some() { 1136 return Err(Error::duplicate_field(ENABLED)); 1137 } 1138 enabled = map.next_value().map(Some)?; 1139 } 1140 Field::Results => { 1141 if results.is_some() { 1142 return Err(Error::duplicate_field(RESULTS)); 1143 } 1144 results = map.next_value::<Option<Prf>>().map(Some)?; 1145 } 1146 Field::Other => map.next_value::<IgnoredAny>().map(|_| ())?, 1147 } 1148 } 1149 if REG { 1150 enabled.ok_or_else(|| Error::missing_field(ENABLED)).and_then(|e| { 1151 if e || results.is_none() { 1152 Ok(()) 1153 } else { 1154 Err(Error::custom("prf must not have 'results', including a null 'results', if 'enabled' is false")) 1155 } 1156 }) 1157 } else { 1158 Ok(()) 1159 }.map(|()| AuthenticationExtensionsPrfOutputsHelper(enabled, PhantomData)) 1160 } 1161 } 1162 /// `"enabled"` 1163 const ENABLED: &str = "enabled"; 1164 /// `"results"` 1165 const RESULTS: &str = "results"; 1166 /// `AuthenticationExtensionsPrfOutputs` field during registration. 1167 const PRF_REG_OUTPUTS_FIELDS: &[&str; 2] = &[ENABLED, RESULTS]; 1168 /// `AuthenticationExtensionsPrfOutputs` field during authentication. 1169 const PRF_AUTH_OUTPUTS_FIELDS: &[&str; 1] = &[RESULTS]; 1170 impl<'de, const REL: bool, const REG: bool, Prf> Deserialize<'de> 1171 for AuthenticationExtensionsPrfOutputsHelper<REL, REG, Prf> 1172 where 1173 for<'a> Prf: Deserialize<'a>, 1174 { 1175 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 1176 where 1177 D: Deserializer<'de>, 1178 { 1179 deserializer.deserialize_struct( 1180 "AuthenticationExtensionsPrfOutputs", 1181 if REG { 1182 PRF_REG_OUTPUTS_FIELDS 1183 } else { 1184 PRF_AUTH_OUTPUTS_FIELDS 1185 }, 1186 AuthenticationExtensionsPrfOutputsVisitor::<REL, REG, Prf>(PhantomData), 1187 ) 1188 } 1189 }