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