ser.rs (15738B)
1 use super::{ 2 Challenge, CredentialId, CredentialMediationRequirement, Hint, PrfInput, 3 PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement, 4 }; 5 use core::str; 6 use data_encoding::BASE64URL_NOPAD; 7 use serde::ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer}; 8 impl Serialize for CredentialMediationRequirement { 9 /// Serializes `self` to conform with 10 /// [`CredentialMediationRequirement`](https://www.w3.org/TR/credential-management-1/#enumdef-credentialmediationrequirement). 11 /// 12 /// # Examples 13 /// 14 /// ``` 15 /// # use webauthn_rp::request::CredentialMediationRequirement; 16 /// assert_eq!( 17 /// serde_json::to_string(&CredentialMediationRequirement::Silent)?, 18 /// r#""silent""# 19 /// ); 20 /// assert_eq!( 21 /// serde_json::to_string(&CredentialMediationRequirement::Optional)?, 22 /// r#""optional""# 23 /// ); 24 /// assert_eq!( 25 /// serde_json::to_string(&CredentialMediationRequirement::Conditional)?, 26 /// r#""conditional""# 27 /// ); 28 /// assert_eq!( 29 /// serde_json::to_string(&CredentialMediationRequirement::Required)?, 30 /// r#""required""# 31 /// ); 32 /// # Ok::<_, serde_json::Error>(()) 33 /// ``` 34 #[inline] 35 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 36 where 37 S: Serializer, 38 { 39 serializer.serialize_str(match *self { 40 Self::Silent => "silent", 41 Self::Optional => "optional", 42 Self::Conditional => "conditional", 43 Self::Required => "required", 44 }) 45 } 46 } 47 impl Serialize for Challenge { 48 /// Serializes `self` to conform with 49 /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-challenge). 50 /// 51 /// Specifically `self` is interpreted as a little-endian array of 16 bytes that is then transformed into a 52 /// base64url-encoded string. 53 /// 54 /// # Examples 55 /// 56 /// ``` 57 /// # use webauthn_rp::request::Challenge; 58 /// # // `Challenge::BASE64_LEN` is 22, but we add two for the quotes. 59 /// assert_eq!(serde_json::to_string(&Challenge::new())?.len(), 24); 60 /// # Ok::<_, serde_json::Error>(()) 61 /// ``` 62 #[expect( 63 clippy::little_endian_bytes, 64 reason = "SentChallenge::deserialize and Challenge::serialize need to be consistent across architectures" 65 )] 66 #[inline] 67 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 68 where 69 S: Serializer, 70 { 71 serializer.serialize_str(BASE64URL_NOPAD.encode_mut_str( 72 self.0.to_le_bytes().as_slice(), 73 [0; Self::BASE64_LEN].as_mut_slice(), 74 )) 75 } 76 } 77 impl Serialize for RpId { 78 /// Serializes `self` as a [`prim@str`]. 79 /// 80 /// # Examples 81 /// 82 /// ``` 83 /// # use webauthn_rp::request::{AsciiDomain, RpId}; 84 /// assert_eq!( 85 /// serde_json::to_string(&RpId::Domain(AsciiDomain::try_from("www.example.com".to_owned()).unwrap())).unwrap(), 86 /// r#""www.example.com""# 87 /// ); 88 /// assert_eq!( 89 /// serde_json::to_string(&RpId::Url("ssh:foo".parse().unwrap())).unwrap(), 90 /// r#""ssh:foo""# 91 /// ); 92 /// # Ok::<_, serde_json::Error>(()) 93 /// ``` 94 #[inline] 95 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 96 where 97 S: Serializer, 98 { 99 serializer.serialize_str(self.as_ref()) 100 } 101 } 102 impl<T> Serialize for PublicKeyCredentialDescriptor<T> 103 where 104 CredentialId<T>: Serialize, 105 { 106 /// Serializes `self` to conform with 107 /// [`PublicKeyCredentialDescriptorJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialdescriptorjson). 108 /// 109 /// # Examples 110 /// 111 /// ``` 112 /// # #[cfg(all(feature = "bin", feature = "custom"))] 113 /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr}; 114 /// # use webauthn_rp::{ 115 /// # request::PublicKeyCredentialDescriptor, 116 /// # response::{AuthTransports, CredentialId}, 117 /// # }; 118 /// /// Retrieves the `AuthTransports` associated with the unique `cred_id` 119 /// /// from the database. 120 /// # #[cfg(all(feature = "bin", feature = "custom"))] 121 /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> { 122 /// // ⋮ 123 /// # AuthTransports::decode(32) 124 /// } 125 /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is 126 /// // likely never needed since the `CredentialId` was originally sent from the client and is likely 127 /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`. 128 /// # #[cfg(all(feature = "bin", feature = "custom"))] 129 /// let id = CredentialId::try_from(vec![0; 16])?; 130 /// # #[cfg(all(feature = "bin", feature = "custom"))] 131 /// let transports = get_transports((&id).into())?; 132 /// # #[cfg(all(feature = "bin", feature = "custom"))] 133 /// assert_eq!( 134 /// serde_json::to_string(&PublicKeyCredentialDescriptor { id, transports }).unwrap(), 135 /// r#"{"type":"public-key","id":"AAAAAAAAAAAAAAAAAAAAAA","transports":["usb"]}"# 136 /// ); 137 /// # Ok::<_, webauthn_rp::AggErr>(()) 138 /// ``` 139 #[inline] 140 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 141 where 142 S: Serializer, 143 { 144 serializer 145 .serialize_struct("PublicKeyCredentialDescriptor", 3) 146 .and_then(|mut ser| { 147 ser.serialize_field("type", "public-key").and_then(|()| { 148 ser.serialize_field("id", &self.id).and_then(|()| { 149 ser.serialize_field("transports", &self.transports) 150 .and_then(|()| ser.end()) 151 }) 152 }) 153 }) 154 } 155 } 156 impl Serialize for UserVerificationRequirement { 157 /// Serializes `self` to conform with 158 /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement). 159 /// 160 /// # Examples 161 /// 162 /// ``` 163 /// # use webauthn_rp::request::UserVerificationRequirement; 164 /// assert_eq!( 165 /// serde_json::to_string(&UserVerificationRequirement::Required)?, 166 /// r#""required""# 167 /// ); 168 /// assert_eq!( 169 /// serde_json::to_string(&UserVerificationRequirement::Discouraged)?, 170 /// r#""discouraged""# 171 /// ); 172 /// assert_eq!( 173 /// serde_json::to_string(&UserVerificationRequirement::Preferred)?, 174 /// r#""preferred""# 175 /// ); 176 /// # Ok::<_, serde_json::Error>(()) 177 /// ``` 178 #[inline] 179 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 180 where 181 S: Serializer, 182 { 183 serializer.serialize_str(match *self { 184 Self::Required => "required", 185 Self::Discouraged => "discouraged", 186 Self::Preferred => "preferred", 187 }) 188 } 189 } 190 impl Serialize for Hint { 191 /// Serializes `self` to conform with 192 /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-hints). 193 /// 194 /// # Examples 195 /// 196 /// ``` 197 /// # use webauthn_rp::request::Hint; 198 /// assert_eq!( 199 /// serde_json::to_string(&Hint::None)?, 200 /// r#"[]"# 201 /// ); 202 /// assert_eq!( 203 /// serde_json::to_string(&Hint::SecurityKey)?, 204 /// r#"["security-key"]"# 205 /// ); 206 /// assert_eq!( 207 /// serde_json::to_string(&Hint::ClientDevice)?, 208 /// r#"["client-device"]"# 209 /// ); 210 /// assert_eq!( 211 /// serde_json::to_string(&Hint::Hybrid)?, 212 /// r#"["hybrid"]"# 213 /// ); 214 /// assert_eq!( 215 /// serde_json::to_string(&Hint::SecurityKeyClientDevice)?, 216 /// r#"["security-key","client-device"]"# 217 /// ); 218 /// assert_eq!( 219 /// serde_json::to_string(&Hint::ClientDeviceSecurityKey)?, 220 /// r#"["client-device","security-key"]"# 221 /// ); 222 /// assert_eq!( 223 /// serde_json::to_string(&Hint::SecurityKeyHybrid)?, 224 /// r#"["security-key","hybrid"]"# 225 /// ); 226 /// assert_eq!( 227 /// serde_json::to_string(&Hint::HybridSecurityKey)?, 228 /// r#"["hybrid","security-key"]"# 229 /// ); 230 /// assert_eq!( 231 /// serde_json::to_string(&Hint::ClientDeviceHybrid)?, 232 /// r#"["client-device","hybrid"]"# 233 /// ); 234 /// assert_eq!( 235 /// serde_json::to_string(&Hint::HybridClientDevice)?, 236 /// r#"["hybrid","client-device"]"# 237 /// ); 238 /// assert_eq!( 239 /// serde_json::to_string(&Hint::SecurityKeyClientDeviceHybrid)?, 240 /// r#"["security-key","client-device","hybrid"]"# 241 /// ); 242 /// assert_eq!( 243 /// serde_json::to_string(&Hint::SecurityKeyHybridClientDevice)?, 244 /// r#"["security-key","hybrid","client-device"]"# 245 /// ); 246 /// assert_eq!( 247 /// serde_json::to_string(&Hint::ClientDeviceSecurityKeyHybrid)?, 248 /// r#"["client-device","security-key","hybrid"]"# 249 /// ); 250 /// assert_eq!( 251 /// serde_json::to_string(&Hint::ClientDeviceHybridSecurityKey)?, 252 /// r#"["client-device","hybrid","security-key"]"# 253 /// ); 254 /// assert_eq!( 255 /// serde_json::to_string(&Hint::HybridSecurityKeyClientDevice)?, 256 /// r#"["hybrid","security-key","client-device"]"# 257 /// ); 258 /// assert_eq!( 259 /// serde_json::to_string(&Hint::HybridClientDeviceSecurityKey)?, 260 /// r#"["hybrid","client-device","security-key"]"# 261 /// ); 262 /// # Ok::<_, serde_json::Error>(()) 263 /// ``` 264 #[inline] 265 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 266 where 267 S: Serializer, 268 { 269 /// [`security-key`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhints-security-key). 270 const SECURITY_KEY: &str = "security-key"; 271 /// [`client-device`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhints-client-device). 272 const CLIENT_DEVICE: &str = "client-device"; 273 /// [`hybrid`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialhints-hybrid). 274 const HYBRID: &str = "hybrid"; 275 let count = match *self { 276 Self::None => 0, 277 Self::SecurityKey | Self::ClientDevice | Self::Hybrid => 1, 278 Self::SecurityKeyClientDevice 279 | Self::ClientDeviceSecurityKey 280 | Self::SecurityKeyHybrid 281 | Self::HybridSecurityKey 282 | Self::ClientDeviceHybrid 283 | Self::HybridClientDevice => 2, 284 Self::SecurityKeyClientDeviceHybrid 285 | Self::SecurityKeyHybridClientDevice 286 | Self::ClientDeviceSecurityKeyHybrid 287 | Self::ClientDeviceHybridSecurityKey 288 | Self::HybridSecurityKeyClientDevice 289 | Self::HybridClientDeviceSecurityKey => 3, 290 }; 291 serializer.serialize_seq(Some(count)).and_then(|mut ser| { 292 match *self { 293 Self::None => Ok(()), 294 Self::SecurityKey => ser.serialize_element(SECURITY_KEY), 295 Self::ClientDevice => ser.serialize_element(CLIENT_DEVICE), 296 Self::Hybrid => ser.serialize_element(HYBRID), 297 Self::SecurityKeyClientDevice => ser 298 .serialize_element(SECURITY_KEY) 299 .and_then(|()| ser.serialize_element(CLIENT_DEVICE)), 300 Self::ClientDeviceSecurityKey => ser 301 .serialize_element(CLIENT_DEVICE) 302 .and_then(|()| ser.serialize_element(SECURITY_KEY)), 303 Self::SecurityKeyHybrid => ser 304 .serialize_element(SECURITY_KEY) 305 .and_then(|()| ser.serialize_element(HYBRID)), 306 Self::HybridSecurityKey => ser 307 .serialize_element(HYBRID) 308 .and_then(|()| ser.serialize_element(SECURITY_KEY)), 309 Self::ClientDeviceHybrid => ser 310 .serialize_element(CLIENT_DEVICE) 311 .and_then(|()| ser.serialize_element(HYBRID)), 312 Self::HybridClientDevice => ser 313 .serialize_element(HYBRID) 314 .and_then(|()| ser.serialize_element(CLIENT_DEVICE)), 315 Self::SecurityKeyClientDeviceHybrid => { 316 ser.serialize_element(SECURITY_KEY).and_then(|()| { 317 ser.serialize_element(CLIENT_DEVICE) 318 .and_then(|()| ser.serialize_element(HYBRID)) 319 }) 320 } 321 Self::SecurityKeyHybridClientDevice => { 322 ser.serialize_element(SECURITY_KEY).and_then(|()| { 323 ser.serialize_element(HYBRID) 324 .and_then(|()| ser.serialize_element(CLIENT_DEVICE)) 325 }) 326 } 327 Self::ClientDeviceSecurityKeyHybrid => { 328 ser.serialize_element(CLIENT_DEVICE).and_then(|()| { 329 ser.serialize_element(SECURITY_KEY) 330 .and_then(|()| ser.serialize_element(HYBRID)) 331 }) 332 } 333 Self::ClientDeviceHybridSecurityKey => { 334 ser.serialize_element(CLIENT_DEVICE).and_then(|()| { 335 ser.serialize_element(HYBRID) 336 .and_then(|()| ser.serialize_element(SECURITY_KEY)) 337 }) 338 } 339 Self::HybridSecurityKeyClientDevice => { 340 ser.serialize_element(HYBRID).and_then(|()| { 341 ser.serialize_element(SECURITY_KEY) 342 .and_then(|()| ser.serialize_element(CLIENT_DEVICE)) 343 }) 344 } 345 Self::HybridClientDeviceSecurityKey => { 346 ser.serialize_element(HYBRID).and_then(|()| { 347 ser.serialize_element(CLIENT_DEVICE) 348 .and_then(|()| ser.serialize_element(SECURITY_KEY)) 349 }) 350 } 351 } 352 .and_then(|()| ser.end()) 353 }) 354 } 355 } 356 impl Serialize for PrfInput<'_, '_> { 357 /// Serializes `self` to conform with 358 /// [`AuthenticationExtensionsPRFValues`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfvalues). 359 /// 360 /// # Examples 361 /// 362 /// ``` 363 /// # use webauthn_rp::request::{PrfInput, ExtensionReq}; 364 /// assert_eq!( 365 /// serde_json::to_string(&PrfInput { 366 /// first: [0; 4].as_slice(), 367 /// second: Some([2; 1].as_slice()), 368 /// })?, 369 /// r#"{"first":"AAAAAA","second":"Ag"}"# 370 /// ); 371 /// # Ok::<_, serde_json::Error>(()) 372 /// ``` 373 #[expect( 374 clippy::arithmetic_side_effects, 375 reason = "comment justifies how overflow is not possible" 376 )] 377 #[inline] 378 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> 379 where 380 S: Serializer, 381 { 382 serializer 383 // The max value is 1 + 1 = 2, so overflow is not an issue. 384 .serialize_struct("PrfInput", 1 + usize::from(self.second.is_some())) 385 .and_then(|mut ser| { 386 ser.serialize_field("first", BASE64URL_NOPAD.encode(self.first).as_str()) 387 .and_then(|()| { 388 self.second 389 .as_ref() 390 .map_or(Ok(()), |second| { 391 ser.serialize_field( 392 "second", 393 BASE64URL_NOPAD.encode(second).as_str(), 394 ) 395 }) 396 .and_then(|()| ser.end()) 397 }) 398 }) 399 } 400 }