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 }