wtx 0.45.0

A collection of different transport implementations and related tools focused primarily on web technologies.
Documentation
1
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
//! Structures of <https://www.w3.org/TR/webauthn-3>
//!
//! The existence of some elements does not necessarily mean that `WTX` support them.

use crate::{
  collection::{ArrayVectorU8, Vector},
  http::HttpError,
};

/// The possible ceremony types of a `WebAuthn` request.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClientDataJsonTy {
  /// webauthn.get
  #[cfg_attr(feature = "serde", serde(rename = "webauthn.get"))]
  Get,
  /// webauthn.create
  #[cfg_attr(feature = "serde", serde(rename = "webauthn.create"))]
  Create,
}

/// COSE algorithm identifiers used in `WebAuthn`.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Alg {
  /// ECDSA w/ SHA-256 on the P-256 curve.
  ES256,
  /// ECDSA w/ SHA-384 on the P-384 curve.
  ES384,
  /// ECDSA w/ SHA-512 on the P-521 curve.
  ES512,
  /// Edwards-curve Digital Signature Algorithm like Ed25519 or Ed448.
  EdDSA,
  /// RSASSA-PKCS1-v1_5 w/ SHA-256.
  RS256,
  /// RSASSA-PKCS1-v1_5 w/ SHA-384.
  RS384,
  /// RSASSA-PKCS1-v1_5 w/ SHA-512.
  RS512,
  /// RSASSA-PSS w/ SHA-256.
  PS256,
  /// RSASSA-PSS w/ SHA-384.
  PS384,
  /// RSASSA-PSS w/ SHA-512.
  PS512,
}

impl From<Alg> for i16 {
  #[inline]
  fn from(value: Alg) -> Self {
    match value {
      Alg::ES256 => -7,
      Alg::ES384 => -35,
      Alg::ES512 => -36,
      Alg::EdDSA => -8,
      Alg::RS256 => -257,
      Alg::RS384 => -258,
      Alg::RS512 => -259,
      Alg::PS256 => -37,
      Alg::PS384 => -38,
      Alg::PS512 => -39,
    }
  }
}

impl TryFrom<i16> for Alg {
  type Error = crate::Error;

  #[inline]
  fn try_from(value: i16) -> Result<Self, Self::Error> {
    Ok(match value {
      -7 => Self::ES256,
      -35 => Self::ES384,
      -36 => Self::ES512,
      -8 => Self::EdDSA,
      -257 => Self::RS256,
      -258 => Self::RS384,
      -259 => Self::RS512,
      -37 => Self::PS256,
      -38 => Self::PS384,
      -39 => Self::PS512,
      _ => return Err(HttpError::UnknownWebAuthnAlg.into()),
    })
  }
}

/// The Relying Party's preference for attestation conveyance.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AttestationConveyancePreference {
  /// The Relying Party wants to receive the attestation statement as generated by the
  /// authenticator.
  Direct,
  /// The Relying Party wants to receive an attestation statement that may include uniquely
  /// identifying information.
  Enterprise,
  /// The Relying Party prefers verifiable attestation, but allows the client to anonymize or
  /// substitute the attestation.
  Indirect,
  /// The Relying Party is not interested in authenticator attestation.
  None,
}

/// `WebAuthn` attestation statement formats.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AttestationFormat {
  /// Compact, extensible format for low‑resource authenticators.
  Packed,
  /// TPM‑based format; same structure as `Packed` but different raw data/signature.
  Tpm,
  /// Proprietary hardware attestation for Android "N" and later.
  AndroidKey,
  /// Attestation derived from the Android `SafetyNet` API.
  AndroidSafetyNet,
  /// Format used by FIDO U2F authenticators.
  FidoU2f,
  /// No attestation; used when the relying party opts out.
  None,
}

/// Whether the authenticator is a platform or cross-platform authenticator.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AuthenticatorAttachment {
  /// Roaming authenticators.
  CrossPlatform,
  /// Authenticators physically bound to the client device.
  Platform,
}

/// Hints to the client about how the authenticator communicates.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum AuthenticatorTransport {
  /// Bluetooth Low Energy.
  Ble,
  /// A combination of transports used for Cross-Device Authentication.
  Hybrid,
  /// Client-device-internal transport (i.e., platform authenticators).
  Internal,
  /// Near Field Communication.
  Nfc,
  /// Universal Serial Bus (USB).
  Usb,
}

/// Hints to the client about the preferred UI experience.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PublicKeyCredentialHints {
  /// Indicates the RP prefers the user use a platform authenticator attached to the client device.
  ClientDevice,
  /// Indicates the RP prefers a cross-device flow.
  Hybrid,
  /// Indicates the RP prefers the user use a roaming hardware security key.
  SecurityKey,
}

/// Credential type.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PublicKeyCredentialType {
  /// Public Key
  #[cfg_attr(feature = "serde", serde(rename = "public-key"))]
  PublicKey,
}

/// The Relying Party's requirement for client-side discoverable credentials.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ResidentKeyRequirement {
  /// The RP prefers creating a server-side credential, but will accept a discoverable credential.
  Discouraged,
  /// The RP strongly prefers creating a client-side discoverable credential, but will accept a
  /// server-side credential.
  Preferred,
  /// The RP requires a client-side discoverable credential. The operation will fail if one cannot
  /// be created.
  Required,
}

/// The Relying Party's requirement for user verification.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UserVerificationRequirement {
  /// The RP does not want user verification employed during the operation.
  Discouraged,
  /// The RP prefers user verification if possible, but will accept a credential/assertion
  /// without it.
  Preferred,
  /// The RP requires user verification. The operation will fail if the authenticator cannot
  /// verify the user.
  Required,
}

/// This structure is returned by the authenticator during registration (creation) ceremonies.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct AttestationObject<B> {
  /// See [`AttestationFormat`].
  #[cfg_attr(feature = "serde", serde(rename = "fmt"))]
  pub f: AttestationFormat,
  /// See [`AttestationStatement`].
  pub att_stmt: AttestationStatement<B>,
  /// The authenticator data, which contains the new credential's public key and other flags.
  pub auth_data: B,
}

/// Contains the cryptographic proofs of the attestation.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, PartialEq)]
pub struct AttestationStatement<B> {
  /// The signature or proof data.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub sig: Option<B>,
  /// X.509 Certificate chain. Present for batch attestation.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub x5c: Option<ArrayVectorU8<B, 2>>,
}

/// Specifies requirements for the authenticator to be used.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct AuthenticatorSelectionCriteria {
  /// See [`AuthenticatorAttachment`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub authenticator_attachment: Option<AuthenticatorAttachment>,
  /// See [`ResidentKeyRequirement`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub resident_key: Option<ResidentKeyRequirement>,
  /// Retained for backwards compatibility.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub require_resident_key: Option<bool>,
  /// See [`UserVerificationRequirement`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub user_verification: Option<UserVerificationRequirement>,
}

/// Client extension inputs.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct AuthenticationExtensionsClientInputs<S> {
  /// FIDO `AppID` extension.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub appid: Option<S>,
  /// FIDO `AppID` exclude extension.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub appid_exclude: Option<S>,
  /// Request the `credProps` extension.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub cred_props: Option<bool>,
}

/// Client extension outputs returned by the authenticator/client after a `WebAuthn` ceremony.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct AuthenticationExtensionsClientOutputs {
  /// Result of the FIDO `AppID` extension.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub appid: Option<bool>,
  /// Result of the `appidExclude` extension.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub appid_exclude: Option<bool>,
  /// See [`CredentialPropertiesOutput`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub cred_props: Option<CredentialPropertiesOutput>,
}

/// The authenticator's response data for an authentication ceremony.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct AuthenticatorAssertionResponse<S> {
  /// The client data JSON, serialized as collected by the client.
  #[cfg_attr(feature = "serde", serde(rename = "clientDataJSON"))]
  pub client_data_json: S,
  /// The authenticator data.
  pub authenticator_data: S,
  /// The raw signature returned by the authenticator.
  pub signature: S,
  /// The user handle returned by the authenticator, if any.
  /// serialization.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub user_handle: Option<S>,
  /// Attestation object for the assertion, if requested.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub attestation_object: Option<S>,
}

/// The authenticator's response data for a registration ceremony.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct AuthenticatorAttestationResponse<S> {
  /// The client data JSON, serialized as collected by the client.
  #[cfg_attr(feature = "serde", serde(rename = "clientDataJSON"))]
  pub client_data_json: S,
  /// The attestation object containing the new credential's public key, credential ID, and
  /// attestation statement.
  pub attestation_object: S,
  /// The authenticator data contained within the attestation object, provided for convenience.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub authenticator_data: Option<S>,
  /// The transports that the authenticator is believed to support.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub transports: Option<ArrayVectorU8<AuthenticatorTransport, 5>>,
  /// The DER-encoded `SubjectPublicKeyInfo` of the new credential, provided for convenience.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub public_key: Option<S>,
  /// The COSE algorithm identifier of the new credential's key pair.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub public_key_algorithm: Option<i64>,
}

/// The collected client data JSON.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, PartialEq)]
pub struct ClientDataJson<S> {
  /// The challenge originally passed to the create/get method.
  pub challenge: S,
  /// The origin of the caller.
  pub origin: S,
  /// See [`ClientDataJsonTy`].
  #[cfg_attr(feature = "serde", serde(rename = "type"))]
  pub ty: ClientDataJsonTy,
}

/// A public key represented in COSE (CBOR Object Signing and Encryption) format.
#[derive(Debug, PartialEq)]
pub struct CoseKey<B> {
  /// Key type.
  pub kty: i32,
  /// Algorithm.
  pub alg: Alg,
  /// Curve of EC2 keys.
  pub crv: i32,
  /// The X coordinate of EC2 keys.
  pub x: B,
  /// The Y coordinate (EC2 only). Absent for OKP keys.
  pub y: Option<B>,
}

/// Output of the `credProps` extension.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, PartialEq)]
pub struct CredentialPropertiesOutput {
  /// Whether the credential was created as a client-side discoverable credential.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub rk: Option<bool>,
}

/// A `PublicKeyCredential` object as returned by `navigator.credentials.create()` or
/// `navigator.credentials.get()`.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredential<R, S> {
  /// The credential identifier, base64url-encoded.
  pub id: S,
  /// The raw credential identifier bytes, base64url-encoded.
  pub raw_id: S,
  /// The response from the authenticator.
  pub response: R,
  /// See [`AuthenticatorAttachment`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub authenticator_attachment: Option<AuthenticatorAttachment>,
  /// See [`AuthenticationExtensionsClientOutputs`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub client_extension_results: Option<AuthenticationExtensionsClientOutputs>,
  /// See [`PublicKeyCredentialType`].
  #[cfg_attr(feature = "serde", serde(rename = "type"))]
  pub ty: PublicKeyCredentialType,
}

/// Options for credential creation.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredentialCreationOptions<B, S> {
  /// Information about the Relying Party.
  pub rp: PublicKeyCredentialRpEntity<S>,
  /// Information about the user account.
  pub user: PublicKeyCredentialUserEntity<B, S>,
  /// A challenge generated by the server (at least 16 random bytes).
  pub challenge: B,
  /// Acceptable credential parameters, ordered by RP preference.
  pub pub_key_cred_params: ArrayVectorU8<PublicKeyCredentialParameters, 4>,
  /// Time (in milliseconds) the caller is willing to wait.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub timeout: Option<u64>,
  /// Credentials to exclude. Prevents re-registration of existing authenticators.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub exclude_credentials: Option<Vector<PublicKeyCredentialDescriptor<B>>>,
  /// Criteria for selecting an appropriate authenticator.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub authenticator_selection: Option<AuthenticatorSelectionCriteria>,
  /// Hints to guide the client UI.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub hints: Option<ArrayVectorU8<PublicKeyCredentialHints, 3>>,
  /// The RP's attestation preference.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub attestation: Option<AttestationConveyancePreference>,
  /// Acceptable attestation statement formats, ordered by RP preference.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub attestation_formats: Option<ArrayVectorU8<AttestationFormat, 7>>,
  /// Client extension inputs.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub extensions: Option<AuthenticationExtensionsClientInputs<S>>,
}

/// Identifies a specific credential to the client.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredentialDescriptor<B> {
  /// See [`PublicKeyCredentialType`].
  #[cfg_attr(feature = "serde", serde(rename = "type"))]
  pub ty: PublicKeyCredentialType,
  /// The credential ID. Encoded as base64url in JSON serialization.
  pub id: B,
  /// See [`AuthenticatorTransport`].
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub transports: Option<ArrayVectorU8<AuthenticatorTransport, 5>>,
}

/// Specifies the acceptable credential type and signing algorithm.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredentialParameters {
  /// See [`PublicKeyCredentialType`].
  #[cfg_attr(feature = "serde", serde(rename = "type"))]
  pub ty: PublicKeyCredentialType,
  /// See [`Alg`].
  pub alg: Alg,
}

/// Options for credential assertion.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredentialRequestOptions<B, S> {
  /// A challenge generated by the server (at least 16 random bytes).
  pub challenge: B,
  /// Time (in milliseconds) the caller is willing to wait.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub timeout: Option<u64>,
  /// The RP identifier. If omitted, the client uses the document's effective domain.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub rp_id: Option<S>,
  /// Credentials acceptable for this authentication ceremony.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub allow_credentials: Option<Vector<PublicKeyCredentialDescriptor<B>>>,
  /// The RP's user-verification requirement.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub user_verification: Option<UserVerificationRequirement>,
  /// Hints to guide the client UI.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub hints: Option<ArrayVectorU8<PublicKeyCredentialHints, 3>>,
  /// Acceptable attestation statement formats.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub attestation: Option<AttestationConveyancePreference>,
  /// Acceptable attestation statement formats, ordered by RP preference.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub attestation_formats: Option<ArrayVectorU8<AttestationFormat, 7>>,
  /// Client extension inputs.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub extensions: Option<AuthenticationExtensionsClientInputs<S>>,
}

/// Describes the Relying Party responsible for the credential.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredentialRpEntity<S> {
  /// A unique identifier for the RP. Defaults to the document's origin effective domain if omitted.
  #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
  pub id: Option<S>,
  /// A human-readable name for the RP.
  pub name: S,
}

/// Describes the user account the credential is associated with.
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
#[derive(Debug, PartialEq)]
pub struct PublicKeyCredentialUserEntity<B, S> {
  /// An opaque user handle (max 64 bytes). Must NOT contain PII.
  pub id: B,
  /// A human-readable name for the user.
  pub name: S,
  /// A human-friendly display name for the user.
  pub display_name: S,
}

#[cfg(feature = "serde")]
mod serde_local {
  use crate::http::web_authn::{Alg, CoseKey};
  use core::{fmt::Formatter, marker::PhantomData};
  use serde::{
    Deserialize, Deserializer,
    de::{Error as _, IgnoredAny, MapAccess, Visitor},
  };

  impl serde::Serialize for Alg {
    #[inline]
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
      S: serde::Serializer,
    {
      serializer.serialize_i16((*self).into())
    }
  }

  impl<'de> Deserialize<'de> for Alg {
    #[inline]
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
      D: Deserializer<'de>,
    {
      Self::try_from(i16::deserialize(deserializer)?).map_err(D::Error::custom)
    }
  }

  impl<'de, B> Deserialize<'de> for CoseKey<B>
  where
    B: Deserialize<'de>,
  {
    #[inline]
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
      D: Deserializer<'de>,
    {
      struct LocalVisitor<B>(PhantomData<B>);

      impl<'de, B> Visitor<'de> for LocalVisitor<B>
      where
        B: Deserialize<'de>,
      {
        type Value = CoseKey<B>;

        fn expecting(&self, formatter: &mut Formatter<'_>) -> core::fmt::Result {
          formatter.write_str("a COSE key map with integer keys")
        }

        fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
        where
          A: MapAccess<'de>,
        {
          let mut kty = None;
          let mut alg = None;
          let mut crv = None;
          let mut x = None;
          let mut y = None;
          while let Some(key) = map.next_key::<i64>()? {
            match key {
              -3 => y = Some(map.next_value::<B>()?),
              -2 => x = Some(map.next_value::<B>()?),
              -1 => crv = Some(map.next_value()?),
              1 => kty = Some(map.next_value()?),
              3 => alg = Some(map.next_value()?),
              _ => {
                let _ = map.next_value::<IgnoredAny>()?;
              }
            }
          }
          Ok(CoseKey {
            kty: kty.ok_or_else(|| A::Error::missing_field("kty"))?,
            alg: alg.ok_or_else(|| A::Error::missing_field("alg"))?,
            crv: crv.ok_or_else(|| A::Error::missing_field("crv"))?,
            x: x.ok_or_else(|| A::Error::missing_field("x"))?,
            y,
          })
        }
      }
      deserializer.deserialize_map(LocalVisitor(PhantomData))
    }
  }
}