webauthn_rp

WebAuthn Level 3 RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

lib.rs (58779B)


      1 //! [![git]](https://git.philomathiclife.com/webauthn_rp/log.html) [![crates-io]](https://crates.io/crates/webauthn_rp) [![docs-rs]](crate)
      2 //!
      3 //! [git]: https://git.philomathiclife.com/git_badge.svg
      4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
      5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
      6 //!
      7 //! `webauthn_rp` is a library for _server-side_
      8 //! [Web Authentication (WebAuthn)](https://www.w3.org/TR/webauthn-3/#sctn-rp-operations) Relying Party
      9 //! (RP) operations.
     10 //!
     11 //! The purpose of a server-side RP library is to be modular so that any client can be used with it as a backend
     12 //! _including_ native applications—WebAuthn technically only covers web applications; however it's relatively easy
     13 //! to adapt to native applications as well. It achieves this by not assuming how data is sent to/from the client;
     14 //! having said that, there are pre-defined serialization formats for "common" deployments which can be used when
     15 //! [`serde`](#serde) is enabled.
     16 //!
     17 //! ## Cargo "features"
     18 //!
     19 //! [`custom`](#custom) or both [`bin`](#bin) and [`serde`](#serde) must be enabled; otherwise a [`compile_error`]
     20 //!  will occur.
     21 //!
     22 //! ### `bin`
     23 //!
     24 //! Enables binary (de)serialization via [`Encode`] and [`Decode`]. Since registered credentials will almost always
     25 //! have to be saved to persistent storage, _some_ form of (de)serialization is necessary. In the event `bin` is
     26 //! unsuitable or only partially suitable (e.g., human-readable output is desired), one will need to enable
     27 //! [`custom`](#custom) to allow construction of certain types (e.g., [`AuthenticatedCredential`]).
     28 //!
     29 //! If possible and desired, one may wish to save the data "directly" to avoid any potential temporary allocations.
     30 //! For example [`StaticState::encode`] will return a [`Vec`] containing hundreds (and possibly thousands in the
     31 //! extreme case) of bytes if the underlying public key is an RSA key. This additional allocation and copy of data
     32 //! is obviously avoided if [`StaticState`] is stored as a
     33 //! [composite type](https://www.postgresql.org/docs/current/rowtypes.html) or its fields are stored in separate
     34 //! columns when written to a relational database (RDB).
     35 //!
     36 //! ### `custom`
     37 //!
     38 //! Exposes functions (e.g., [`AuthenticatedCredential::new`]) that allows one to construct instances of types that
     39 //! cannot be constructed when [`bin`](#bin) or [`serde`](#serde) is not enabled.
     40 //!
     41 //! ### `serde`
     42 //!
     43 //! Enables (de)serialization of data sent to/from the client via [`serde`](https://docs.rs/serde/latest/serde/)
     44 //! based on the JSON-motivated definitions (e.g.,
     45 //! [`RegistrationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson)). Since
     46 //! data has to be sent to/from the client, _some_ form of (de)serialization is necessary. In the event `serde`
     47 //! is unsuitable or only partially suitable, one will need to enable [`custom`](#custom) to allow construction
     48 //! of certain types (e.g., [`Registration`]).
     49 //!
     50 //! Code is _strongly_ encouraged to rely on the [`Deserialize`] implementations as much as possible to reduce the
     51 //! chances of improperly deserializing the client data.
     52 //!
     53 //! Note that clients are free to send data in whatever form works best, so there is no requirement the
     54 //! JSON-motivated definitions are used even when JSON is sent. This is especially relevant since the JSON-motivated
     55 //! definitions were only added in [WebAuthn Level 3](https://www.w3.org/TR/webauthn-3/); thus many deployments only
     56 //! partially conform. Some specific deviations that may require partial customization of deserialization are the
     57 //! following:
     58 //!
     59 //! * [`ArrayBuffer`](https://webidl.spec.whatwg.org/#idl-ArrayBuffer)s encoded using something other than
     60 //!   base64url.
     61 //! * `ArrayBuffer`s that are encoded multiple times (including the use of different encodings each time).
     62 //! * Missing fields (e.g.,
     63 //!   [`transports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-transports)).
     64 //! * Different field names (e.g., `extensions` instead of
     65 //!   [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-clientextensionresults)).
     66 //!
     67 //! ### `serde_relaxed`
     68 //!
     69 //! Automatically enables [`serde`](#serde) in addition to "relaxed" [`Deserialize`] implementations
     70 //! (e.g., [`RegistrationRelaxed`]). Roughly "relaxed" translates to unknown fields being ignored and only
     71 //! the fields necessary for construction of the type are required. Case still matters, duplicate fields are still
     72 //! forbidden, and interrelated data validation is still performed when applicable. This can be useful when one
     73 //! wants to accommodate non-conforming clients or clients that implement older versions of the spec.
     74 //!
     75 //! ### `serializable_server_state`
     76 //!
     77 //! Automatically enables [`bin`](#bin) in addition to [`Encode`] and [`Decode`] implementations for
     78 //! [`RegistrationServerState`] and [`AuthenticationServerState`]. Less accurate [`SystemTime`] is used instead of
     79 //! [`Instant`] for timeout enforcement. This should be enabled if you don't desire to use in-memory collections to
     80 //! store the instances of those types.
     81 //!
     82 //! Note even when written to persistent storage, an application should still periodically remove expired ceremonies.
     83 //! If one is using a relational database (RDB); then one can achieve this by storing [`ServerState::sent_challenge`],
     84 //! the `Vec` returned from [`Encode::encode`], and [`ServerState::expiration`] and periodically remove all rows
     85 //! whose expiration exceeds the current date and time.
     86 //!
     87 //! ## Registration and authentication
     88 //!
     89 //! Both [registration](https://www.w3.org/TR/webauthn-3/#registration-ceremony) and
     90 //! [authentication](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) ceremonies rely on "challenges", and
     91 //! these challenges are inherently temporary. For this reason the data associated with challenge completion can
     92 //! often be stored in memory without concern for out-of-memory (OOM) conditions. There are several benefits to
     93 //! storing such data in memory:
     94 //!
     95 //! * No data manipulation
     96 //!     * By leveraging move semantics, the data sent to the client cannot be mutated once the ceremony begins.
     97 //! * Improved timeout enforcement
     98 //!     * By ensuring the same machine that started the ceremony is also used to finish the ceremony, deviation of
     99 //!       system clocks is not a concern. Additionally, allowing serialization requires the use of some form of
    100 //!       cross-platform "timestamp" (e.g., [Unix time](https://en.wikipedia.org/wiki/Unix_time)) which differ in
    101 //!       implementation (e.g., platforms implement leap seconds in different ways) and are often not monotonically
    102 //!       increasing. If data resides in memory, a monotonic [`Instant`] can be used instead.
    103 //!
    104 //! It is for those reasons data like [`RegistrationServerState`] are not serializable by default and require the
    105 //! use of in-memory collections (e.g., [`FixedCapHashSet`]). To better ensure OOM is not a concern, RPs should set
    106 //! reasonable timeouts. Since ceremonies can only be completed by moving data (e.g.,
    107 //! [`RegistrationServerState::verify`]), ceremony completion is guaranteed to free up the memory used—
    108 //! `RegistrationServerState` instances are only 48 bytes on `x86_64-unknown-linux-gnu` platforms. To avoid issues
    109 //! related to incomplete ceremonies, RPs can periodically iterate the collection for expired ceremonies and remove
    110 //! such data. Other techniques can be employed as well to mitigate OOM, but they are application specific and
    111 //! out-of-scope. If this is undesirable, one can enable [`serializable_server_state`](#serializable_server_state)
    112 //! so that `RegistrationServerState` and [`AuthenticationServerState`] implement [`Encode`] and [`Decode`]. Another
    113 //! reason one may need to store this information persistently is for load-balancing purposes where the server that
    114 //! started the ceremony is not guaranteed to be the server that finishes the ceremony.
    115 //!
    116 //! ## Supported signature algorithms
    117 //!
    118 //! The only supported signature algorithms are the following:
    119 //!
    120 //! * Ed25519 as defined in [RFC 8032 § 5.1](https://www.rfc-editor.org/rfc/rfc8032#section-5.1). This corresponds
    121 //!   to [`CoseAlgorithmIdentifier::Eddsa`].
    122 //! * ECDSA as defined in [SEC 1 Version 2.0 § 4.1](https://www.secg.org/sec1-v2.pdf#subsection.4.1) using SHA-256
    123 //!   as the hash function and NIST P-256 as defined in
    124 //!   [NIST SP 800-186 § 3.2.1.3](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf#%5B%7B%22num%22%3A229%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C70%2C275%2C0%5D)
    125 //!   for the underlying elliptic curve. This corresponds to [`CoseAlgorithmIdentifier::Es256`].
    126 //! * ECDSA as defined in SEC 1 Version 2.0 § 4.1 using SHA-384 as the hash function and NIST P-384 as defined in
    127 //!   [NIST SP 800-186 § 3.2.1.4](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf#%5B%7B%22num%22%3A232%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C70%2C264%2C0%5D)
    128 //!   for the underlying elliptic curve. This corresponds to [`CoseAlgorithmIdentifier::Es384`].
    129 //! * RSASSA-PKCS1-v1_5 as defined in [RFC 8017 § 8.2](https://www.rfc-editor.org/rfc/rfc8017#section-8.2) using
    130 //!   SHA-256 as the hash function. This corresponds to [`CoseAlgorithmIdentifier::Rs256`].
    131 //!
    132 //! ## Correctness of code
    133 //!
    134 //! This library more strictly adheres to the spec than many other similar libraries including but not limited to
    135 //! the following ways:
    136 //!
    137 //! * [CTAP2 canonical CBOR encoding form](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#ctap2-canonical-cbor-encoding-form).
    138 //! * `Deserialize` implementations requiring _exact_ conformance (e.g., not allowing unknown data).
    139 //! * More thorough interrelated data validation (e.g., all places a Credential ID exists must match).
    140 //! * Implement a lot of recommended (i.e., SHOULD) criteria (e.g.,
    141 //!   [User display names conforming to the Nickname Profile as defined in RFC 8266](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name)).
    142 //!
    143 //! Unfortunately like almost all software, this library has not been formally verified; however great care is
    144 //! employed in the following ways:
    145 //!
    146 //! * Leverage move semantics to prevent mutation of data once in a static state.
    147 //! * Ensure a great many invariants via types.
    148 //! * Reduce code duplication.
    149 //! * Reduce variable mutation allowing for simpler algebraic reasoning.
    150 //! * `panic`-free code[^note] (i.e., define true/total functions).
    151 //! * Ensure arithmetic "side effects" don't occur (e.g., overflow).
    152 //! * Aggressive use of compiler and [Clippy](https://doc.rust-lang.org/stable/clippy/lints.html) lints.
    153 //! * Unit tests for common cases, edge cases, and error cases.
    154 //!
    155 //! ## Cryptographic libraries
    156 //!
    157 //! This library does not rely on _any_ sensitive data (e.g., private keys) as only signature verification is
    158 //! ever performed. This means that the only thing that matters with the libraries used is their algorithmic
    159 //! correctness and not other normally essential aspects like susceptibility to side-channel attacks. While I
    160 //! personally believe the libraries that are used are at least as "secure" as alternatives even when dealing with
    161 //! sensitive data, one only needs to audit the correctness of the libraries to be confident in their use. In fact
    162 //! [`curve25519_dalek`](https://docs.rs/curve25519-dalek/latest/curve25519_dalek/#backends) has been formally
    163 //! verified when the [`fiat`](https://github.com/mit-plv/fiat-crypto) backend is used making it _objectively_
    164 //! better than many other libraries whose correctness has not been proven. Two additional benefits of the library
    165 //! choices are simpler APIs making it more likely their use is correct and better cross-platform compatibility.
    166 //!
    167 //! [^note]: `panic`s related to memory allocations or stack overflow are possible since such issues are not
    168 //!          formally guarded against.
    169 #![cfg_attr(docsrs, feature(doc_cfg))]
    170 #![deny(
    171     unknown_lints,
    172     future_incompatible,
    173     let_underscore,
    174     missing_docs,
    175     nonstandard_style,
    176     refining_impl_trait,
    177     rust_2018_compatibility,
    178     rust_2018_idioms,
    179     rust_2021_compatibility,
    180     rust_2024_compatibility,
    181     unsafe_code,
    182     unused,
    183     warnings,
    184     clippy::all,
    185     clippy::cargo,
    186     clippy::complexity,
    187     clippy::correctness,
    188     clippy::nursery,
    189     clippy::pedantic,
    190     clippy::perf,
    191     clippy::restriction,
    192     clippy::style,
    193     clippy::suspicious
    194 )]
    195 #![expect(
    196     clippy::arbitrary_source_item_ordering,
    197     clippy::blanket_clippy_restriction_lints,
    198     clippy::exhaustive_enums,
    199     clippy::exhaustive_structs,
    200     clippy::implicit_return,
    201     clippy::min_ident_chars,
    202     clippy::missing_trait_methods,
    203     clippy::multiple_crate_versions,
    204     clippy::pub_with_shorthand,
    205     clippy::pub_use,
    206     clippy::ref_patterns,
    207     clippy::self_named_module_files,
    208     clippy::single_call_fn,
    209     clippy::single_char_lifetime_names,
    210     clippy::unseparated_literal_suffix,
    211     reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
    212 )]
    213 #[cfg(not(any(feature = "custom", all(feature = "bin", feature = "serde"))))]
    214 compile_error!("'custom' must be enabled or both 'bin' and 'serde' must be enabled");
    215 #[cfg(feature = "serializable_server_state")]
    216 use crate::request::{
    217     auth::ser_server_state::{
    218         DecodeAuthenticationServerStateErr, EncodeAuthenticationServerStateErr,
    219     },
    220     register::ser_server_state::DecodeRegistrationServerStateErr,
    221 };
    222 #[cfg(any(feature = "bin", feature = "custom"))]
    223 use crate::response::error::CredentialIdErr;
    224 #[cfg(feature = "serde_relaxed")]
    225 use crate::response::ser_relaxed::SerdeJsonErr;
    226 #[cfg(feature = "bin")]
    227 use crate::{
    228     request::register::bin::{DecodeNicknameErr, DecodeUsernameErr},
    229     response::{
    230         bin::DecodeAuthTransportsErr,
    231         register::bin::{DecodeDynamicStateErr, DecodeStaticStateErr},
    232     },
    233 };
    234 use crate::{
    235     request::{
    236         auth::error::{RequestOptionsErr, SecondFactorErr},
    237         error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr},
    238         register::{
    239             error::{CreationOptionsErr, NicknameErr, UserHandleErr, UsernameErr},
    240             ResidentKeyRequirement, UserHandle,
    241         },
    242     },
    243     response::{
    244         auth::error::{AuthCeremonyErr, AuthenticatorDataErr as AuthAuthDataErr},
    245         error::CollectedClientDataErr,
    246         register::{
    247             error::{
    248                 AaguidErr, AttestationObjectErr, AuthenticatorDataErr as RegAuthDataErr,
    249                 RegCeremonyErr,
    250             },
    251             CredentialProtectionPolicy, DynamicState, Metadata, StaticState, UncompressedPubKey,
    252         },
    253         AuthTransports, CredentialId,
    254     },
    255 };
    256 #[cfg(doc)]
    257 use crate::{
    258     request::{
    259         auth::{AllowedCredential, AllowedCredentials},
    260         register::{CoseAlgorithmIdentifier, Nickname, Username},
    261         AsciiDomain, DomainOrigin, FixedCapHashSet, Port, PublicKeyCredentialDescriptor, RpId,
    262         Scheme, ServerState, Url,
    263     },
    264     response::{
    265         auth::{self, AuthenticatorAssertion},
    266         register::{
    267             self, Aaguid, Attestation, AttestationObject, AttestedCredentialData,
    268             AuthenticatorExtensionOutput, ClientExtensionsOutputs, CompressedPubKey,
    269             CredentialPropertiesOutput,
    270         },
    271         CollectedClientData, Flag,
    272     },
    273 };
    274 #[cfg(all(doc, feature = "bin"))]
    275 use bin::{Decode, Encode};
    276 #[cfg(doc)]
    277 use core::str::FromStr;
    278 use core::{
    279     convert,
    280     error::Error,
    281     fmt::{self, Display, Formatter},
    282 };
    283 #[cfg(all(doc, feature = "serde_relaxed"))]
    284 use response::register::ser_relaxed::RegistrationRelaxed;
    285 #[cfg(all(doc, feature = "serde"))]
    286 use serde::Deserialize;
    287 #[cfg(all(doc, feature = "serde_relaxed"))]
    288 use serde_json::de::{Deserializer, StreamDeserializer};
    289 #[cfg(feature = "serializable_server_state")]
    290 use std::time::SystemTimeError;
    291 #[cfg(doc)]
    292 use std::time::{Instant, SystemTime};
    293 /// Contains functionality to (de)serialize data to a data store.
    294 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    295 #[cfg(feature = "bin")]
    296 pub mod bin;
    297 /// Functionality for starting ceremonies.
    298 ///
    299 /// # What kind of credential should I create?
    300 ///
    301 /// Without partitioning the possibilities _too_ much, the following are possible authentication flows:
    302 ///
    303 /// | Label | Username | Password | Client-side credential | Authenticator-side user verification | Recommended |
    304 /// |-------|----------|----------|------------------------|--------------------------------------|:-----------:|
    305 /// | 1     | Yes      | Yes      | Required               | Yes                                  |          ❌ |
    306 /// | 2     | Yes      | Yes      | Required               | No                                   |          ❌ |
    307 /// | 3     | Yes      | Yes      | Optional               | Yes                                  |          ❌ |
    308 /// | <a name="label4">4</a>     | Yes      | Yes      | Optional               | No                                   |          ✅ |
    309 /// | 5     | Yes      | No       | Required               | Yes                                  |          ❌ |
    310 /// | 6     | Yes      | No       | Required               | No                                   |          ❌ |
    311 /// | <a name="label7">7</a>     | Yes      | No       | Optional               | Yes                                  |          ❔ |
    312 /// | 8     | Yes      | No       | Optional               | No                                   |          ❌ |
    313 /// | 9     | No       | Yes      | Required               | Yes                                  |          ❌ |
    314 /// | 10    | No       | Yes      | Required               | No                                   |          ❌ |
    315 /// | 11    | No       | Yes      | Optional               | Yes                                  |          ❌ |
    316 /// | 12    | No       | Yes      | Optional               | No                                   |          ❌ |
    317 /// | <a name="label13">13</a>    | No       | No       | Required               | Yes                                  |          ✅ |
    318 /// | 14    | No       | No       | Required               | No                                   |          ❌ |
    319 /// | 15    | No       | No       | Optional               | Yes                                  |          ❌ |
    320 /// | 16    | No       | No       | Optional               | No                                   |          ❌ |
    321 ///
    322 /// * All `Label`s with both `Password` and `Authenticator-side user verification` set to `Yes` are not recommended
    323 ///   since the verification done on the authenticator is likely the same "factor" as a password; thus it does not
    324 ///   add benefit but only serves as an annoyance to users.
    325 /// * All `Label`s with `Username` or `Password` set to `Yes` and `Client-side credential` set to `Required` are not
    326 ///   recommended since you may preclude authenticators that are storage constrained (e.g., security keys).
    327 /// * All `Label`s with `Username` set to `No` and `Client-side credential` set to `Optional` are not possible since
    328 ///   RPs would not have a way to identify the set of encrypted credentials to pass to the unknown user.
    329 /// * All `Label`s with `Password` and `Authenticator-side user verification` set to `No` are not recommended since
    330 ///   those are single-factor authentication schemes; thus anyone possessing the credential without also passing
    331 ///   some form of user verification (e.g., password) would authenticate.
    332 /// * [`Label 7`](#label7) is possible for RPs that are comfortable passing an encrypted credential to a potential user
    333 ///   without having that user first pass another form of authentication. For many RPs passing such information even
    334 ///   if encrypted is not desirable though.
    335 /// * [`Label 4`](#label4) is ideal as a single-factor flow incorporated within a wider multi-factor authentication (MFA)
    336 ///   setup. The easiest way to register such a credential is with
    337 ///   [`PublicKeyCredentialCreationOptions::second_factor`].
    338 /// * [`Label 13`](#label13) is ideal for passkey setups as it allows for pleasant UX where a user does not have to type a
    339 ///   username nor password while still being secured with MFA with one of the factors being based on public-key
    340 ///   cryptography which for many is the most secure form of single-factor authentication. The easiest way to register
    341 ///   such a credential is with [`PublicKeyCredentialCreationOptions::passkey`].
    342 ///
    343 /// Two other reasons one may prefer to construct client-side credentials is richer support for extensions (e.g.,
    344 /// [`largeBlobKey`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-largeBlobKey-extension)
    345 /// for CTAP 2.2 authenticators) and the ability to use both discoverable and nondiscoverable requests (i.e.,
    346 /// [`PublicKeyCredentialRequestOptions::allow_credentials`] is empty and not empty respectively). The former is not
    347 /// relevant for this library—at least currently—since the only extensions supported are applicable for both
    348 /// client-side and server-side credentials. The latter can be important especially if an RP wants the ability to
    349 /// seamlessly transition from a username and password scheme to a userless and passwordless one in the future.
    350 ///
    351 /// Note the table is purely informative. While helper functions
    352 /// (e.g., [`PublicKeyCredentialCreationOptions::passkey`]) only exist for [`Label 4`](#label4) and
    353 /// [`Label 13`](#label13), one can create any credential since all fields in [`PublicKeyCredentialCreationOptions`]
    354 /// and [`PublicKeyCredentialRequestOptions`] are accessible.
    355 pub mod request;
    356 /// Functionality for completing ceremonies.
    357 ///
    358 /// Read [`request`] for more information about what credentials one should create.
    359 pub mod response;
    360 #[doc(inline)]
    361 pub use crate::{
    362     request::{
    363         auth::{
    364             AuthenticationClientState, AuthenticationServerState, PublicKeyCredentialRequestOptions,
    365         },
    366         register::{
    367             PublicKeyCredentialCreationOptions, RegistrationClientState, RegistrationServerState,
    368         },
    369     },
    370     response::{auth::Authentication, register::Registration},
    371 };
    372 /// Error returned in [`RegCeremonyErr::Credential`] and [`AuthCeremonyErr::Credential`] as well as
    373 /// from [`AuthenticatedCredential::new`].
    374 #[derive(Clone, Copy, Debug)]
    375 pub enum CredentialErr {
    376     /// Variant when [`CredentialProtectionPolicy::UserVerificationRequired`], but
    377     /// [`DynamicState::user_verified`] is `false`.
    378     CredProtectUserVerificationRequiredWithoutUserVerified,
    379     /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)`, but
    380     /// [`DynamicState::user_verified`] is `false`.
    381     HmacSecretWithoutUserVerified,
    382     /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)`, but
    383     /// [`ClientExtensionsOutputs::prf`] is not `Some(AuthenticationExtensionsPRFOutputs { enabled: true })`.
    384     HmacSecretWithoutPrf,
    385     /// Variant when [`ClientExtensionsOutputs::prf`] is
    386     /// `Some(AuthenticationExtensionsPRFOutputs { enabled: true })`, but
    387     /// [`AuthenticatorExtensionOutput::hmac_secret`] is not `Some(true)`.
    388     PrfWithoutHmacSecret,
    389     /// Variant when [`ResidentKeyRequirement::Required`] was sent, but
    390     /// [`CredentialPropertiesOutput::rk`] is `Some(false)`.
    391     ResidentKeyRequiredServerCredentialCreated,
    392 }
    393 impl Display for CredentialErr {
    394     #[inline]
    395     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
    396         f.write_str(match *self {
    397             Self::CredProtectUserVerificationRequiredWithoutUserVerified => {
    398                 "credProtect requires user verification, but the user is not verified"
    399             }
    400             Self::HmacSecretWithoutUserVerified => {
    401                 "hmac-secret is enabled, but the user is not verified"
    402             }
    403             Self::HmacSecretWithoutPrf => "hmac-secret was enabled but prf was not",
    404             Self::PrfWithoutHmacSecret => "prf was enabled, but hmac-secret was not",
    405             Self::ResidentKeyRequiredServerCredentialCreated => {
    406                 "server-side credential was created, but a client-side credential is required"
    407             }
    408         })
    409     }
    410 }
    411 impl Error for CredentialErr {}
    412 /// Checks if the `static_state` and `dynamic_state` are valid for a credential.
    413 ///
    414 /// # Errors
    415 ///
    416 /// Errors iff `static_state` or `dynamic_state` are invalid.
    417 fn verify_static_and_dynamic_state<T>(
    418     static_state: &StaticState<T>,
    419     dynamic_state: DynamicState,
    420 ) -> Result<(), CredentialErr> {
    421     if dynamic_state.user_verified {
    422         Ok(())
    423     } else if matches!(
    424         static_state.extensions.cred_protect,
    425         CredentialProtectionPolicy::UserVerificationRequired
    426     ) {
    427         Err(CredentialErr::CredProtectUserVerificationRequiredWithoutUserVerified)
    428     } else if static_state.extensions.hmac_secret.unwrap_or_default() {
    429         Err(CredentialErr::HmacSecretWithoutUserVerified)
    430     } else {
    431         Ok(())
    432     }
    433 }
    434 /// Registered credential that needs to be saved server-side to perform future
    435 /// [authentication ceremonies](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) with
    436 /// [`AuthenticatedCredential`].
    437 ///
    438 /// When saving `RegisteredCredential` to persistent storage, one will almost always want to save the contained data
    439 /// separately. The reasons for this are the following:
    440 ///
    441 /// * [`CredentialId`]
    442 ///     * MUST be globally unique, and it will likely be easier to enforce such uniqueness when it's separate.
    443 ///     * Fetching the [`AuthenticatedCredential`] by [`Authentication::raw_id`] when completing the
    444 ///       authentication ceremony via [`AuthenticationServerState::verify`] will likely be easier than alternatives.
    445 /// * [`AuthTransports`]
    446 ///     * Fetching [`CredentialId`]s and associated `AuthTransports` by [`UserHandle`] will likely make credential
    447 ///       registration easier since one should set [`PublicKeyCredentialCreationOptions::exclude_credentials`] to
    448 ///       the [`PublicKeyCredentialDescriptor`]s belonging to a `UserHandle` in order to avoid accidentally
    449 ///       overwriting an existing credential on the authenticator.
    450 ///     * Fetching `CredentialId`s and associated `AuthTransports` by `UserHandle` will likely make starting
    451 ///       authentication ceremonies easier for non-discoverable requests (i.e., setting
    452 ///       [`PublicKeyCredentialRequestOptions::allow_credentials`] to a non-empty [`AllowedCredentials`]).
    453 /// * [`UserHandle`]
    454 ///     * Fetching the [`AuthenticatedCredential`] by [`Authentication::raw_id`] must also coincide with
    455 ///       verifying the associated `UserHandle` matches [`AuthenticatorAssertion::user_handle`] when `Some`.
    456 ///     * Fetching [`CredentialId`]s and associated [`AuthTransports`] by `UserHandle` will likely make credential
    457 ///       registration easier since one should set [`PublicKeyCredentialCreationOptions::exclude_credentials`] to
    458 ///       the [`PublicKeyCredentialDescriptor`]s belonging to a `UserHandle` in order to avoid accidentally
    459 ///       overwriting an existing credential on the authenticator.
    460 ///     * Fetching `CredentialId`s and associated `AuthTransports` by `UserHandle` will likely make starting
    461 ///       authentication ceremonies easier for non-discoverable requests (i.e., setting
    462 ///       [`PublicKeyCredentialRequestOptions::allow_credentials`] to a non-empty [`AllowedCredentials`]).
    463 /// * [`DynamicState`]
    464 ///     * `DynamicState` is the only part that is ever updated after a successful authentication ceremony
    465 ///       via [`AuthenticationServerState::verify`]. It being separate allows for smaller and quicker updates.
    466 /// * [`Metadata`]
    467 ///     * Informative data that is never used during authentication ceremonies; consequently, one may wish to
    468 ///       not even save this information.
    469 /// * [`StaticState`]
    470 ///     * All other data exists as part of `StaticState`.
    471 ///
    472 /// It is for those reasons that `RegisteredCredential` does not implement [`Encode`] or [`Decode`]; instead its parts
    473 /// do.
    474 ///
    475 /// Note that [`RpId`] and user information other than the `UserHandle` are not stored in `RegisteredCredential`.
    476 /// RPs that wish to store such information must do so on their own. Since user information is likely the same
    477 /// for a given `UserHandle` and `RpId` is likely static, it makes little sense to store such information
    478 /// automatically. Types like [`Username`] implement `Encode` and `Decode` to assist such a thing.
    479 ///
    480 /// When registering a credential, [`AttestedCredentialData::aaguid`], [`AttestedCredentialData::credential_id`],
    481 /// and [`AttestedCredentialData::credential_public_key`] will be the sources for [`Metadata::aaguid`],
    482 /// [`Self::id`], and [`StaticState::credential_public_key`] respectively. Additionally, there must be some way for
    483 /// the RP to know what `UserHandle` the [`Registration`] is associated with (e.g., a session cookie); thus the
    484 /// source of [`Self::user_id`] is the `UserHandle` passed to [`RegistrationServerState::verify`].
    485 ///
    486 /// The only way to create this is via `RegistrationServerState::verify`.
    487 #[derive(Debug)]
    488 pub struct RegisteredCredential<'reg, 'user> {
    489     /// The credential ID.
    490     ///
    491     /// For client-side credentials, this is a unique identifier; but for server-side
    492     /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information).
    493     id: CredentialId<&'reg [u8]>,
    494     /// Hints for how the client might communicate with the authenticator containing the credential.
    495     transports: AuthTransports,
    496     /// The identifier for the user.
    497     ///
    498     /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e.,
    499     /// multiple [`CredentialId`]s will often exist for the same `UserHandle`).
    500     user_id: UserHandle<&'user [u8]>,
    501     /// Immutable state returned during registration.
    502     static_state: StaticState<UncompressedPubKey<'reg>>,
    503     /// State that can change during authentication ceremonies.
    504     dynamic_state: DynamicState,
    505     /// Metadata.
    506     metadata: Metadata<'reg>,
    507 }
    508 impl<'reg, 'user> RegisteredCredential<'reg, 'user> {
    509     /// The credential ID.
    510     ///
    511     /// For client-side credentials, this is a unique identifier; but for server-side
    512     /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information).
    513     #[inline]
    514     #[must_use]
    515     pub const fn id(&self) -> CredentialId<&'reg [u8]> {
    516         self.id
    517     }
    518     /// Hints for how the client might communicate with the authenticator containing the credential.
    519     #[inline]
    520     #[must_use]
    521     pub const fn transports(&self) -> AuthTransports {
    522         self.transports
    523     }
    524     /// The identifier for the user.
    525     ///
    526     /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e.,
    527     /// multiple [`CredentialId`]s will often exist for the same `UserHandle`).
    528     #[inline]
    529     #[must_use]
    530     pub const fn user_id(&self) -> UserHandle<&'user [u8]> {
    531         self.user_id
    532     }
    533     /// Immutable state returned during registration.
    534     #[inline]
    535     #[must_use]
    536     pub const fn static_state(&self) -> StaticState<UncompressedPubKey<'reg>> {
    537         self.static_state
    538     }
    539     /// State that can change during authentication ceremonies.
    540     #[inline]
    541     #[must_use]
    542     pub const fn dynamic_state(&self) -> DynamicState {
    543         self.dynamic_state
    544     }
    545     /// Metadata.
    546     #[inline]
    547     #[must_use]
    548     pub const fn metadata(&self) -> Metadata<'reg> {
    549         self.metadata
    550     }
    551     /// Constructs a `RegisteredCredential` based on the passed arguments.
    552     ///
    553     /// # Errors
    554     ///
    555     /// Errors iff the passed arguments are invalid. Read [`CredentialErr`]
    556     /// for more information.
    557     #[inline]
    558     fn new<'a: 'reg, 'b: 'user>(
    559         id: CredentialId<&'a [u8]>,
    560         transports: AuthTransports,
    561         user_id: UserHandle<&'b [u8]>,
    562         static_state: StaticState<UncompressedPubKey<'a>>,
    563         dynamic_state: DynamicState,
    564         metadata: Metadata<'a>,
    565     ) -> Result<Self, CredentialErr> {
    566         verify_static_and_dynamic_state(&static_state, dynamic_state).and_then(|()| {
    567             // `verify_static_and_dynamic_state` already ensures that
    568             // `hmac-secret` is not `Some(true)` when `!dynamic_state.user_verified`;
    569             // thus we only need to check that one is not enabled without the other.
    570             if static_state.extensions.hmac_secret.unwrap_or_default() {
    571                 if metadata
    572                     .client_extension_results
    573                     .prf
    574                     .is_some_and(|prf| prf.enabled)
    575                 {
    576                     Ok(())
    577                 } else {
    578                     Err(CredentialErr::HmacSecretWithoutPrf)
    579                 }
    580             } else if metadata
    581                 .client_extension_results
    582                 .prf
    583                 .is_some_and(|prf| prf.enabled)
    584             {
    585                 Err(CredentialErr::PrfWithoutHmacSecret)
    586             } else {
    587                 Ok(())
    588             }
    589             .and_then(|()| {
    590                 if !matches!(metadata.resident_key, ResidentKeyRequirement::Required)
    591                     || metadata
    592                         .client_extension_results
    593                         .cred_props
    594                         .as_ref()
    595                         .is_none_or(|props| props.rk.is_none_or(convert::identity))
    596                 {
    597                     Ok(Self {
    598                         id,
    599                         transports,
    600                         user_id,
    601                         static_state,
    602                         dynamic_state,
    603                         metadata,
    604                     })
    605                 } else {
    606                     Err(CredentialErr::ResidentKeyRequiredServerCredentialCreated)
    607                 }
    608             })
    609         })
    610     }
    611     /// Returns the contained data consuming `self`.
    612     #[expect(
    613         clippy::type_complexity,
    614         reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable"
    615     )]
    616     #[inline]
    617     #[must_use]
    618     pub const fn into_parts(
    619         self,
    620     ) -> (
    621         CredentialId<&'reg [u8]>,
    622         AuthTransports,
    623         UserHandle<&'user [u8]>,
    624         StaticState<UncompressedPubKey<'reg>>,
    625         DynamicState,
    626         Metadata<'reg>,
    627     ) {
    628         (
    629             self.id,
    630             self.transports,
    631             self.user_id,
    632             self.static_state,
    633             self.dynamic_state,
    634             self.metadata,
    635         )
    636     }
    637     /// Returns the contained data.
    638     #[expect(
    639         clippy::type_complexity,
    640         reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable"
    641     )]
    642     #[inline]
    643     #[must_use]
    644     pub const fn as_parts(
    645         &self,
    646     ) -> (
    647         CredentialId<&'reg [u8]>,
    648         AuthTransports,
    649         UserHandle<&'user [u8]>,
    650         StaticState<UncompressedPubKey<'reg>>,
    651         DynamicState,
    652         Metadata<'reg>,
    653     ) {
    654         (
    655             self.id,
    656             self.transports,
    657             self.user_id,
    658             self.static_state,
    659             self.dynamic_state,
    660             self.metadata,
    661         )
    662     }
    663 }
    664 /// Credential used in authentication ceremonies.
    665 ///
    666 /// Similar to [`RegisteredCredential`] except designed to only contain the necessary data to complete
    667 /// authentication ceremonies. In particular there is no [`AuthTransports`] or [`Metadata`],
    668 /// [`StaticState::credential_public_key`] is [`CompressedPubKey`] that can own or borrow its data, [`Self::id`] is
    669 /// based on the [`CredentialId`] passed to [`Self::new`] which itself must be from [`Authentication::raw_id`], and
    670 /// [`Self::user_id`] is based on the [`UserHandle`] passed to [`Self::new`] which itself must be the value in
    671 /// persistent storage associated with the `CredentialId`. When [`AuthenticatorAssertion::user_handle`] is `Some`,
    672 /// this can be used for `Self::user_id` so long as it matches the value in persistent storage. Note it MUST be
    673 /// `Some` when using discoverable requests (i.e., [`PublicKeyCredentialRequestOptions::allow_credentials`] is
    674 /// empty); and when using non-discoverable requests (i.e., `PublicKeyCredentialRequestOptions::allow_credentials`
    675 /// is non-empty), one should already have the user handle (e.g., in a session cookie) which can also be used.
    676 ///
    677 /// Note `PublicKey` should be `CompressedPubKey` for this to be useful.
    678 ///
    679 /// The only way to create this is via `Self::new`.
    680 #[derive(Debug)]
    681 pub struct AuthenticatedCredential<'cred, 'user, PublicKey> {
    682     /// The credential ID.
    683     ///
    684     /// For client-side credentials, this is a unique identifier; but for server-side
    685     /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information).
    686     id: CredentialId<&'cred [u8]>,
    687     /// The identifier for the user.
    688     ///
    689     /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e.,
    690     /// multiple [`CredentialId`]s will often exist for the same `UserHandle`).
    691     user_id: UserHandle<&'user [u8]>,
    692     /// Immutable state returned during registration.
    693     static_state: StaticState<PublicKey>,
    694     /// State that can change during authentication ceremonies.
    695     dynamic_state: DynamicState,
    696 }
    697 impl<'cred, 'user, PublicKey> AuthenticatedCredential<'cred, 'user, PublicKey> {
    698     /// The credential ID.
    699     ///
    700     /// For client-side credentials, this is a unique identifier; but for server-side
    701     /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information).
    702     #[inline]
    703     #[must_use]
    704     pub const fn id(&self) -> CredentialId<&'cred [u8]> {
    705         self.id
    706     }
    707     /// The identifier for the user.
    708     ///
    709     /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e.,
    710     /// multiple [`CredentialId`]s will often exist for the same `UserHandle`).
    711     #[inline]
    712     #[must_use]
    713     pub const fn user_id(&self) -> UserHandle<&'user [u8]> {
    714         self.user_id
    715     }
    716     /// Immutable state returned during registration.
    717     #[inline]
    718     #[must_use]
    719     pub const fn static_state(&self) -> &StaticState<PublicKey> {
    720         &self.static_state
    721     }
    722     /// State that can change during authentication ceremonies.
    723     #[inline]
    724     #[must_use]
    725     pub const fn dynamic_state(&self) -> DynamicState {
    726         self.dynamic_state
    727     }
    728     /// Constructs an `AuthenticatedCredential` based on the passed arguments.
    729     ///
    730     /// # Errors
    731     ///
    732     /// Errors iff the passed arguments are invalid. Read [`CredentialErr`]
    733     /// for more information.
    734     #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))]
    735     #[cfg(any(feature = "bin", feature = "custom"))]
    736     #[inline]
    737     pub fn new<'a: 'cred, 'b: 'user>(
    738         id: CredentialId<&'a [u8]>,
    739         user_id: UserHandle<&'b [u8]>,
    740         static_state: StaticState<PublicKey>,
    741         dynamic_state: DynamicState,
    742     ) -> Result<Self, CredentialErr> {
    743         verify_static_and_dynamic_state(&static_state, dynamic_state).map(|()| Self {
    744             id,
    745             user_id,
    746             static_state,
    747             dynamic_state,
    748         })
    749     }
    750     /// Returns the contained data consuming `self`.
    751     #[expect(
    752         clippy::type_complexity,
    753         reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable"
    754     )]
    755     #[inline]
    756     #[must_use]
    757     pub fn into_parts(
    758         self,
    759     ) -> (
    760         CredentialId<&'cred [u8]>,
    761         UserHandle<&'user [u8]>,
    762         StaticState<PublicKey>,
    763         DynamicState,
    764     ) {
    765         (self.id, self.user_id, self.static_state, self.dynamic_state)
    766     }
    767     /// Returns the contained data.
    768     #[expect(
    769         clippy::type_complexity,
    770         reason = "type aliases with bounds are even more problematic at least until lazy_type_alias is stable"
    771     )]
    772     #[inline]
    773     #[must_use]
    774     pub const fn as_parts(
    775         &self,
    776     ) -> (
    777         CredentialId<&'cred [u8]>,
    778         UserHandle<&'user [u8]>,
    779         &StaticState<PublicKey>,
    780         DynamicState,
    781     ) {
    782         (
    783             self.id,
    784             self.user_id,
    785             self.static_state(),
    786             self.dynamic_state,
    787         )
    788     }
    789 }
    790 /// Convenience aggregate error that rolls up all errors into one.
    791 #[derive(Debug)]
    792 pub enum AggErr {
    793     /// Variant when [`AsciiDomain::try_from`] errors.
    794     AsciiDomain(AsciiDomainErr),
    795     /// Variant when [`Url::from_str`] errors.
    796     Url(UrlErr),
    797     /// Variant when [`Scheme::try_from`] errors.
    798     Scheme(SchemeParseErr),
    799     /// Variant when [`DomainOrigin::try_from`] errors.
    800     DomainOrigin(DomainOriginParseErr),
    801     /// Variant when [`Port::from_str`] errors.
    802     Port(PortParseErr),
    803     /// Variant when [`PublicKeyCredentialRequestOptions::start_ceremony`] errors.
    804     RequestOptions(RequestOptionsErr),
    805     /// Variant when [`PublicKeyCredentialRequestOptions::second_factor`] errors.
    806     SecondFactor(SecondFactorErr),
    807     /// Variant when [`PublicKeyCredentialCreationOptions::start_ceremony`] errors.
    808     CreationOptions(CreationOptionsErr),
    809     /// Variant when [`Nickname::try_from`] errors.
    810     Nickname(NicknameErr),
    811     /// Variant when [`UserHandle::rand`] or [`UserHandle::decode`] error.
    812     UserHandle(UserHandleErr),
    813     /// Variant when [`Username::try_from`] errors.
    814     Username(UsernameErr),
    815     /// Variant when [`RegistrationServerState::verify`] errors.
    816     RegCeremony(RegCeremonyErr),
    817     /// Variant when [`AuthenticationServerState::verify`] errors.
    818     AuthCeremony(AuthCeremonyErr),
    819     /// Variant when [`AttestationObject::try_from`] errors.
    820     AttestationObject(AttestationObjectErr),
    821     /// Variant when [`register::AuthenticatorData::try_from`] errors.
    822     RegAuthenticatorData(RegAuthDataErr),
    823     /// Variant when [`auth::AuthenticatorData::try_from`] errors.
    824     AuthAuthenticatorData(AuthAuthDataErr),
    825     /// Variant when [`CollectedClientData::from_client_data_json`] errors.
    826     CollectedClientData(CollectedClientDataErr),
    827     /// Variant when [`CollectedClientData::from_client_data_json_relaxed`] errors or any of the [`Deserialize`]
    828     /// implementations error when relying on [`Deserializer`] or [`StreamDeserializer`].
    829     #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))]
    830     #[cfg(feature = "serde_relaxed")]
    831     SerdeJson(SerdeJsonErr),
    832     /// Variant when [`Aaguid::try_from`] errors.
    833     Aaguid(AaguidErr),
    834     /// Variant when [`AuthTransports::decode`] errors.
    835     #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    836     #[cfg(feature = "bin")]
    837     DecodeAuthTransports(DecodeAuthTransportsErr),
    838     /// Variant when [`StaticState::decode`] errors.
    839     #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    840     #[cfg(feature = "bin")]
    841     DecodeStaticState(DecodeStaticStateErr),
    842     /// Variant when [`DynamicState::decode`] errors.
    843     #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    844     #[cfg(feature = "bin")]
    845     DecodeDynamicState(DecodeDynamicStateErr),
    846     /// Variant when [`Nickname::decode`] errors.
    847     #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    848     #[cfg(feature = "bin")]
    849     DecodeNickname(DecodeNicknameErr),
    850     /// Variant when [`Username::decode`] errors.
    851     #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    852     #[cfg(feature = "bin")]
    853     DecodeUsername(DecodeUsernameErr),
    854     /// Variant when [`RegistrationServerState::decode`] errors.
    855     #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
    856     #[cfg(feature = "serializable_server_state")]
    857     DecodeRegistrationServerState(DecodeRegistrationServerStateErr),
    858     /// Variant when [`AuthenticationServerState::decode`] errors.
    859     #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
    860     #[cfg(feature = "serializable_server_state")]
    861     DecodeAuthenticationServerState(DecodeAuthenticationServerStateErr),
    862     /// Variant when [`RegistrationServerState::encode`] errors.
    863     #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
    864     #[cfg(feature = "serializable_server_state")]
    865     EncodeRegistrationServerState(SystemTimeError),
    866     /// Variant when [`AuthenticationServerState::encode`] errors.
    867     #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
    868     #[cfg(feature = "serializable_server_state")]
    869     EncodeAuthenticationServerState(EncodeAuthenticationServerStateErr),
    870     /// Variant when [`AuthenticatedCredential::new`] errors.
    871     #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))]
    872     #[cfg(any(feature = "bin", feature = "custom"))]
    873     Credential(CredentialErr),
    874     /// Variant when [`CredentialId::try_from`] or [`CredentialId::decode`] errors.
    875     #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))]
    876     #[cfg(any(feature = "bin", feature = "custom"))]
    877     CredentialId(CredentialIdErr),
    878 }
    879 impl From<AsciiDomainErr> for AggErr {
    880     #[inline]
    881     fn from(value: AsciiDomainErr) -> Self {
    882         Self::AsciiDomain(value)
    883     }
    884 }
    885 impl From<UrlErr> for AggErr {
    886     #[inline]
    887     fn from(value: UrlErr) -> Self {
    888         Self::Url(value)
    889     }
    890 }
    891 impl From<SchemeParseErr> for AggErr {
    892     #[inline]
    893     fn from(value: SchemeParseErr) -> Self {
    894         Self::Scheme(value)
    895     }
    896 }
    897 impl From<DomainOriginParseErr> for AggErr {
    898     #[inline]
    899     fn from(value: DomainOriginParseErr) -> Self {
    900         Self::DomainOrigin(value)
    901     }
    902 }
    903 impl From<PortParseErr> for AggErr {
    904     #[inline]
    905     fn from(value: PortParseErr) -> Self {
    906         Self::Port(value)
    907     }
    908 }
    909 impl From<RequestOptionsErr> for AggErr {
    910     #[inline]
    911     fn from(value: RequestOptionsErr) -> Self {
    912         Self::RequestOptions(value)
    913     }
    914 }
    915 impl From<SecondFactorErr> for AggErr {
    916     #[inline]
    917     fn from(value: SecondFactorErr) -> Self {
    918         Self::SecondFactor(value)
    919     }
    920 }
    921 impl From<CreationOptionsErr> for AggErr {
    922     #[inline]
    923     fn from(value: CreationOptionsErr) -> Self {
    924         Self::CreationOptions(value)
    925     }
    926 }
    927 impl From<NicknameErr> for AggErr {
    928     #[inline]
    929     fn from(value: NicknameErr) -> Self {
    930         Self::Nickname(value)
    931     }
    932 }
    933 impl From<UserHandleErr> for AggErr {
    934     #[inline]
    935     fn from(value: UserHandleErr) -> Self {
    936         Self::UserHandle(value)
    937     }
    938 }
    939 impl From<UsernameErr> for AggErr {
    940     #[inline]
    941     fn from(value: UsernameErr) -> Self {
    942         Self::Username(value)
    943     }
    944 }
    945 impl From<RegCeremonyErr> for AggErr {
    946     #[inline]
    947     fn from(value: RegCeremonyErr) -> Self {
    948         Self::RegCeremony(value)
    949     }
    950 }
    951 impl From<AuthCeremonyErr> for AggErr {
    952     #[inline]
    953     fn from(value: AuthCeremonyErr) -> Self {
    954         Self::AuthCeremony(value)
    955     }
    956 }
    957 impl From<AttestationObjectErr> for AggErr {
    958     #[inline]
    959     fn from(value: AttestationObjectErr) -> Self {
    960         Self::AttestationObject(value)
    961     }
    962 }
    963 impl From<RegAuthDataErr> for AggErr {
    964     #[inline]
    965     fn from(value: RegAuthDataErr) -> Self {
    966         Self::RegAuthenticatorData(value)
    967     }
    968 }
    969 impl From<AuthAuthDataErr> for AggErr {
    970     #[inline]
    971     fn from(value: AuthAuthDataErr) -> Self {
    972         Self::AuthAuthenticatorData(value)
    973     }
    974 }
    975 impl From<CollectedClientDataErr> for AggErr {
    976     #[inline]
    977     fn from(value: CollectedClientDataErr) -> Self {
    978         Self::CollectedClientData(value)
    979     }
    980 }
    981 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))]
    982 #[cfg(feature = "serde_relaxed")]
    983 impl From<SerdeJsonErr> for AggErr {
    984     #[inline]
    985     fn from(value: SerdeJsonErr) -> Self {
    986         Self::SerdeJson(value)
    987     }
    988 }
    989 impl From<AaguidErr> for AggErr {
    990     #[inline]
    991     fn from(value: AaguidErr) -> Self {
    992         Self::Aaguid(value)
    993     }
    994 }
    995 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
    996 #[cfg(feature = "bin")]
    997 impl From<DecodeAuthTransportsErr> for AggErr {
    998     #[inline]
    999     fn from(value: DecodeAuthTransportsErr) -> Self {
   1000         Self::DecodeAuthTransports(value)
   1001     }
   1002 }
   1003 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
   1004 #[cfg(feature = "bin")]
   1005 impl From<DecodeStaticStateErr> for AggErr {
   1006     #[inline]
   1007     fn from(value: DecodeStaticStateErr) -> Self {
   1008         Self::DecodeStaticState(value)
   1009     }
   1010 }
   1011 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
   1012 #[cfg(feature = "bin")]
   1013 impl From<DecodeDynamicStateErr> for AggErr {
   1014     #[inline]
   1015     fn from(value: DecodeDynamicStateErr) -> Self {
   1016         Self::DecodeDynamicState(value)
   1017     }
   1018 }
   1019 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
   1020 #[cfg(feature = "bin")]
   1021 impl From<DecodeNicknameErr> for AggErr {
   1022     #[inline]
   1023     fn from(value: DecodeNicknameErr) -> Self {
   1024         Self::DecodeNickname(value)
   1025     }
   1026 }
   1027 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))]
   1028 #[cfg(feature = "bin")]
   1029 impl From<DecodeUsernameErr> for AggErr {
   1030     #[inline]
   1031     fn from(value: DecodeUsernameErr) -> Self {
   1032         Self::DecodeUsername(value)
   1033     }
   1034 }
   1035 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
   1036 #[cfg(feature = "serializable_server_state")]
   1037 impl From<DecodeRegistrationServerStateErr> for AggErr {
   1038     #[inline]
   1039     fn from(value: DecodeRegistrationServerStateErr) -> Self {
   1040         Self::DecodeRegistrationServerState(value)
   1041     }
   1042 }
   1043 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
   1044 #[cfg(feature = "serializable_server_state")]
   1045 impl From<DecodeAuthenticationServerStateErr> for AggErr {
   1046     #[inline]
   1047     fn from(value: DecodeAuthenticationServerStateErr) -> Self {
   1048         Self::DecodeAuthenticationServerState(value)
   1049     }
   1050 }
   1051 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
   1052 #[cfg(feature = "serializable_server_state")]
   1053 impl From<SystemTimeError> for AggErr {
   1054     #[inline]
   1055     fn from(value: SystemTimeError) -> Self {
   1056         Self::EncodeRegistrationServerState(value)
   1057     }
   1058 }
   1059 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))]
   1060 #[cfg(feature = "serializable_server_state")]
   1061 impl From<EncodeAuthenticationServerStateErr> for AggErr {
   1062     #[inline]
   1063     fn from(value: EncodeAuthenticationServerStateErr) -> Self {
   1064         Self::EncodeAuthenticationServerState(value)
   1065     }
   1066 }
   1067 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))]
   1068 #[cfg(any(feature = "bin", feature = "custom"))]
   1069 impl From<CredentialErr> for AggErr {
   1070     #[inline]
   1071     fn from(value: CredentialErr) -> Self {
   1072         Self::Credential(value)
   1073     }
   1074 }
   1075 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))]
   1076 #[cfg(any(feature = "bin", feature = "custom"))]
   1077 impl From<CredentialIdErr> for AggErr {
   1078     #[inline]
   1079     fn from(value: CredentialIdErr) -> Self {
   1080         Self::CredentialId(value)
   1081     }
   1082 }
   1083 impl Display for AggErr {
   1084     #[inline]
   1085     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1086         match *self {
   1087             Self::AsciiDomain(err) => err.fmt(f),
   1088             Self::Url(err) => err.fmt(f),
   1089             Self::Scheme(err) => err.fmt(f),
   1090             Self::DomainOrigin(ref err) => err.fmt(f),
   1091             Self::Port(ref err) => err.fmt(f),
   1092             Self::RequestOptions(err) => err.fmt(f),
   1093             Self::SecondFactor(err) => err.fmt(f),
   1094             Self::CreationOptions(err) => err.fmt(f),
   1095             Self::Nickname(err) => err.fmt(f),
   1096             Self::UserHandle(err) => err.fmt(f),
   1097             Self::Username(err) => err.fmt(f),
   1098             Self::RegCeremony(ref err) => err.fmt(f),
   1099             Self::AuthCeremony(ref err) => err.fmt(f),
   1100             Self::AttestationObject(err) => err.fmt(f),
   1101             Self::RegAuthenticatorData(err) => err.fmt(f),
   1102             Self::AuthAuthenticatorData(err) => err.fmt(f),
   1103             Self::CollectedClientData(ref err) => err.fmt(f),
   1104             #[cfg(feature = "serde_relaxed")]
   1105             Self::SerdeJson(ref err) => err.fmt(f),
   1106             Self::Aaguid(err) => err.fmt(f),
   1107             #[cfg(feature = "bin")]
   1108             Self::DecodeAuthTransports(err) => err.fmt(f),
   1109             #[cfg(feature = "bin")]
   1110             Self::DecodeStaticState(err) => err.fmt(f),
   1111             #[cfg(feature = "bin")]
   1112             Self::DecodeDynamicState(err) => err.fmt(f),
   1113             #[cfg(feature = "bin")]
   1114             Self::DecodeNickname(err) => err.fmt(f),
   1115             #[cfg(feature = "bin")]
   1116             Self::DecodeUsername(err) => err.fmt(f),
   1117             #[cfg(feature = "serializable_server_state")]
   1118             Self::DecodeRegistrationServerState(err) => err.fmt(f),
   1119             #[cfg(feature = "serializable_server_state")]
   1120             Self::DecodeAuthenticationServerState(err) => err.fmt(f),
   1121             #[cfg(feature = "serializable_server_state")]
   1122             Self::EncodeRegistrationServerState(ref err) => err.fmt(f),
   1123             #[cfg(feature = "serializable_server_state")]
   1124             Self::EncodeAuthenticationServerState(ref err) => err.fmt(f),
   1125             #[cfg(any(feature = "bin", feature = "custom"))]
   1126             Self::Credential(err) => err.fmt(f),
   1127             #[cfg(any(feature = "bin", feature = "custom"))]
   1128             Self::CredentialId(err) => err.fmt(f),
   1129         }
   1130     }
   1131 }
   1132 impl Error for AggErr {}
   1133 /// Calculates the number of bytes needed to encode an input of length `n` bytes into base64url.
   1134 ///
   1135 /// `Some` is returned iff the encoded length does not exceed [`isize::MAX`].
   1136 #[expect(
   1137     clippy::arithmetic_side_effects,
   1138     clippy::as_conversions,
   1139     clippy::cast_possible_wrap,
   1140     clippy::cast_sign_loss,
   1141     clippy::integer_division,
   1142     clippy::integer_division_remainder_used,
   1143     reason = "proof and comment justifies their correctness"
   1144 )]
   1145 const fn base64url_nopad_len(n: usize) -> Option<usize> {
   1146     // 256^n is the number of distinct values of the input. Let the base64 encoding in a URL safe
   1147     // way without padding of the input be O. There are 64 possible values each byte in O can be; thus we must find
   1148     // the minimum nonnegative integer m such that:
   1149     // 64^m = (2^6)^m = 2^(6m) >= 256^n = (2^8)^n = 2^(8n)
   1150     // <==>
   1151     // lg(2^(6m)) = 6m >= lg(2^(8n)) = 8n   lg is defined on all positive reals which 2^(6m) and 2^(8n) are
   1152     // <==>
   1153     // m >= 8n/6 = 4n/3
   1154     // Clearly that corresponds to m = ⌈4n/3⌉; thus:
   1155 
   1156     let (quot, rem) = (n / 3, n % 3);
   1157     // Actual construction of the encoded output requires the allocation to take no more than `isize::MAX`
   1158     // bytes; thus we must detect overflow of it and not `usize::MAX`.
   1159     // isize::MAX = usize::MAX / 2 >= usize::MAX / 3; thus this `as` conversion is lossless.
   1160     match (quot as isize).checked_mul(4) {
   1161         // If multiplying by 4 caused overflow, then multiplying by 4 and adding by 3 would also.
   1162         None => None,
   1163         // This won't overflow since this maxes at `isize::MAX` since
   1164         // `n` <= ⌊3*isize::MAX/4⌋; thus `quot` <= ⌊isize::MAX/4⌋.
   1165         // `n` can be partitioned into 4 possibilities:
   1166         // (1) n ≡ 0 (mod 4) = 4quot + 0 <= ⌊3*isize::MAX/4⌋
   1167         // (2) n ≡ 1 (mod 4) = 4quot + 1 <= ⌊3*isize::MAX/4⌋
   1168         // (3) n ≡ 2 (mod 4) = 4quot + 2 <= ⌊3*isize::MAX/4⌋
   1169         // (4) n ≡ 3 (mod 4) = 4quot + 3 <= ⌊3*isize::MAX/4⌋
   1170         // For (1), rem is 0; thus 4quot + 0 = `n` which is fine.
   1171         // (2) is not possible per the proof in `webauthn_rp::base64url_nopad_decode_len`.
   1172         // For (3), rem is 2; thus 4quot + 3 = n - 2 + 3 = n + 1 <= ⌊3*isize::MAX/4⌋ + 1 <= isize::MAX for
   1173         // isize::MAX > 0. Clearly `isize::MAX > 0`; otherwise we couldn't allocate anything.
   1174         // For (4), rem is 3; thus 4quot + 4 = n - 3 + 4 = n + 1 <= ⌊3*isize::MAX/4⌋ + 1 <= isize::MAX for
   1175         // isize::MAX > 0. Clearly `isize::MAX > 0`; otherwise we couldn't allocate anything.
   1176         //
   1177         // `val >= 0`; thus we can cast it to `usize` via `as` in a lossless way.
   1178         // Thus this is free from overflow, underflow, and a lossy conversion.
   1179         Some(val) => Some(val as usize + (4 * rem).div_ceil(3)),
   1180     }
   1181 }
   1182 /// Calculates the number of bytes a base64url-encoded input of length `n` bytes will be decoded into.
   1183 ///
   1184 /// `Some` is returned iff `n` is a valid input length.
   1185 ///
   1186 /// Note `n` must not only be a valid length mathematically but also represent a possible allocation of that
   1187 /// many bytes. Since only allocations <= [`isize::MAX`] are possible, this will always return `None` when
   1188 /// `n > isize::MAX`.
   1189 #[cfg(feature = "serde")]
   1190 #[expect(
   1191     clippy::arithmetic_side_effects,
   1192     clippy::as_conversions,
   1193     clippy::integer_division_remainder_used,
   1194     reason = "proof and comment justifies their correctness"
   1195 )]
   1196 const fn base64url_nopad_decode_len(n: usize) -> Option<usize> {
   1197     // 64^n is the number of distinct values of the input. Let the decoded output be O.
   1198     // There are 256 possible values each byte in O can be; thus we must find
   1199     // the maximum nonnegative integer m such that:
   1200     // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n)
   1201     // <==>
   1202     // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n   lg is defined on all positive reals which 2^(8m) and 2^(6n) are
   1203     // <==>
   1204     // m <= 6n/8 = 3n/4
   1205     // Clearly that corresponds to m = ⌊3n/4⌋.
   1206     //
   1207     // There are three partitions for m:
   1208     // (1) m ≡ 0 (mod 3) = 3i
   1209     // (2) m ≡ 1 (mod 3) = 3i + 1
   1210     // (3) m ≡ 2 (mod 3) = 3i + 2
   1211     //
   1212     // From `crate::base64url_nopad_len`, we know that the encoded length, n, of an input of length m is n = ⌈4m/3⌉.
   1213     // The encoded length of (1) is thus n = ⌈4(3i)/3⌉ = ⌈4i⌉ = 4i ≡ 0 (mod 4).
   1214     // The encoded length of (2) is thus n = ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + 2 ≡ 2 (mod 4).
   1215     // The encoded length of (3) is thus n = ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + 3 ≡ 3 (mod 4).
   1216     //
   1217     // Thus if n ≡ 1 (mod 4), it is never a valid length.
   1218     //
   1219     // Let n be the length of a possible encoded output of an input of length m.
   1220     // We know from above that n ≢ 1 (mod 4), this leaves three possibilities:
   1221     // (1) n ≡ 0 (mod 4) = 4i
   1222     // (2) n ≡ 2 (mod 4) = 4i + 2
   1223     // (3) n ≡ 3 (mod 4) = 4i + 3
   1224     //
   1225     // For (1) an input of length 3i is the inverse since ⌈4(3i)/3⌉ = 4i.
   1226     // For (2) an input of length 3i + 1 is the inverse since ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + 2.
   1227     // For (3) an input of length 3i + 2 is the inverse since ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + 3.
   1228     //
   1229     // Consequently n is a valid length of an encoded output iff n ≢ 1 (mod 4).
   1230 
   1231     // `isize::MAX >= 0 >= usize::MIN`; thus this conversion is lossless.
   1232     if n % 4 == 1 || n > isize::MAX as usize {
   1233         None
   1234     } else {
   1235         let (quot, rem) = (n >> 2u8, n % 4);
   1236         // 4quot + rem = n
   1237         // rem <= 3
   1238         // 3rem <= 9
   1239         // 3rem/4 <= 2
   1240         // 3quot + 3rem/4 <= 4quot + rem
   1241         // Thus no operation causes overflow or underflow.
   1242         Some((3 * quot) + ((3 * rem) >> 2u8))
   1243     }
   1244 }