lib.rs (72902B)
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 //! ## `webauthn_rp` in action 18 //! 19 //! ```no_run 20 //! use core::convert; 21 //! use webauthn_rp::{ 22 //! AuthenticatedCredential64, DiscoverableAuthentication64, DiscoverableAuthenticationServerState, 23 //! DiscoverableCredentialRequestOptions, CredentialCreationOptions64, RegisteredCredential64, 24 //! Registration, RegistrationServerState64, 25 //! hash::hash_set::FixedCapHashSet, 26 //! request::{ 27 //! AsciiDomainStatic, PublicKeyCredentialDescriptor, RpId, 28 //! auth::AuthenticationVerificationOptions, 29 //! register::{ 30 //! Nickname, PublicKeyCredentialUserEntity64, RegistrationVerificationOptions, 31 //! UserHandle64, Username, 32 //! }, 33 //! }, 34 //! response::{ 35 //! CredentialId, 36 //! auth::error::AuthCeremonyErr, 37 //! register::{CompressedPubKeyOwned, DynamicState, error::RegCeremonyErr}, 38 //! }, 39 //! }; 40 //! # #[cfg(feature = "serde")] 41 //! use serde::de::{Deserialize, Deserializer}; 42 //! # #[cfg(feature = "serde_relaxed")] 43 //! use serde_json::Error as JsonErr; 44 //! /// The RP ID our application uses. 45 //! const RP_ID: &RpId = &RpId::StaticDomain(AsciiDomainStatic::new("example.com").unwrap()); 46 //! /// The registration verification options. 47 //! const REG_OPTS: &RegistrationVerificationOptions::<'static, 'static, &'static str, &'static str> = &RegistrationVerificationOptions::new(); 48 //! /// The authentication verification options. 49 //! const AUTH_OPTS: &AuthenticationVerificationOptions::<'static, 'static, &'static str, &'static str> = &AuthenticationVerificationOptions::new(); 50 //! /// Error we return in our application when a function fails. 51 //! enum AppErr { 52 //! /// WebAuthn registration ceremony failed. 53 //! RegCeremony(RegCeremonyErr), 54 //! /// WebAuthn authentication ceremony failed. 55 //! AuthCeremony(AuthCeremonyErr), 56 //! /// Unable to insert a WebAuthn ceremony. 57 //! WebAuthnCeremonyCreation, 58 //! /// WebAuthn ceremony does not exist; thus the ceremony could not be completed. 59 //! MissingWebAuthnCeremony, 60 //! /// General error related to JSON deserialization. 61 //! # #[cfg(feature = "serde_relaxed")] 62 //! Json(JsonErr), 63 //! /// No account exists associated with a particular `UserHandle64`. 64 //! NoAccount, 65 //! /// No credential exists associated with a particular `CredentialId`. 66 //! NoCredential, 67 //! /// `CredentialId` exists but the associated `UserHandle64` does not match. 68 //! CredentialUserIdMismatch, 69 //! } 70 //! # #[cfg(feature = "serde_relaxed")] 71 //! impl From<JsonErr> for AppErr { 72 //! fn from(value: JsonErr) -> Self { 73 //! Self::Json(value) 74 //! } 75 //! } 76 //! impl From<RegCeremonyErr> for AppErr { 77 //! fn from(value: RegCeremonyErr) -> Self { 78 //! Self::RegCeremony(value) 79 //! } 80 //! } 81 //! impl From<AuthCeremonyErr> for AppErr { 82 //! fn from(value: AuthCeremonyErr) -> Self { 83 //! Self::AuthCeremony(value) 84 //! } 85 //! } 86 //! /// First-time account creation. 87 //! /// 88 //! /// This gets sent from the user after an account is created on their side. The registration ceremony 89 //! /// still has to be successfully completed for the account to be created server side. In the event of an error, 90 //! /// the user should delete the created passkey since it won't be usable. 91 //! struct AccountReg<'a, 'b> { 92 //! registration: Registration, 93 //! user_name: Username<'a>, 94 //! user_display_name: Nickname<'b>, 95 //! } 96 //! # #[cfg(feature = "serde")] 97 //! impl<'de: 'a + 'b, 'a, 'b> Deserialize<'de> for AccountReg<'a, 'b> { 98 //! fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> 99 //! where 100 //! D: Deserializer<'de>, 101 //! { 102 //! // ⋮ 103 //! # panic!(""); 104 //! } 105 //! } 106 //! /// Starts account creation. 107 //! /// 108 //! /// This only makes sense for greenfield deployments since account information (e.g., user name) would likely 109 //! /// already exist otherwise. This is similar to credential creation except a random `UserHandle64` is generated and 110 //! /// will be used for subsequent credential registrations. 111 //! # #[cfg(feature = "serde_relaxed")] 112 //! fn start_account_creation( 113 //! reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, 114 //! ) -> Result<Vec<u8>, AppErr> { 115 //! let user_id = UserHandle64::new(); 116 //! let (server, client) = 117 //! CredentialCreationOptions64::first_passkey_with_blank_user_info( 118 //! RP_ID, &user_id, 119 //! ) 120 //! .start_ceremony() 121 //! .unwrap_or_else(|_e| { 122 //! unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") 123 //! }); 124 //! if reg_ceremonies.insert_remove_all_expired(server).is_some_and(convert::identity) 125 //! { 126 //! Ok(serde_json::to_vec(&client) 127 //! .unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState64::serialize"))) 128 //! } else { 129 //! Err(AppErr::WebAuthnCeremonyCreation) 130 //! } 131 //! } 132 //! /// Finishes account creation. 133 //! /// 134 //! /// Pending a successful registration ceremony, a new account associated with the randomly generated 135 //! /// `UserHandle64` will be created with a corresponding passkey entry. This passkey will be used to 136 //! /// log into the application. 137 //! /// 138 //! /// Note if this errors, then the user should be notified to delete the passkey created on their 139 //! /// authenticator. 140 //! # #[cfg(feature = "serde_relaxed")] 141 //! fn finish_account_creation( 142 //! reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, 143 //! client_data: &[u8], 144 //! ) -> Result<(), AppErr> { 145 //! let account = serde_json::from_slice::<AccountReg<'_, '_>>(client_data)?; 146 //! insert_account( 147 //! &account, 148 //! reg_ceremonies 149 //! // `Registration::challenge_relaxed` is available iff `serde_relaxed` is enabled. 150 //! .take(&account.registration.challenge_relaxed()?) 151 //! .ok_or(AppErr::MissingWebAuthnCeremony)? 152 //! .verify( 153 //! RP_ID, 154 //! &account.registration, 155 //! REG_OPTS, 156 //! )?, 157 //! ) 158 //! } 159 //! /// Starts passkey registration. 160 //! /// 161 //! /// This is used for _existing_ accounts where the user is already logged in and wants to register another 162 //! /// passkey. This is similar to account creation except we already have the user entity info and we need to 163 //! /// fetch the registered `PublicKeyCredentialDescriptor`s to avoid accidentally overwriting a passkey on 164 //! /// the authenticator. 165 //! # #[cfg(feature = "serde_relaxed")] 166 //! fn start_cred_registration( 167 //! user_id: &UserHandle64, 168 //! reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, 169 //! ) -> Result<Vec<u8>, AppErr> { 170 //! let (entity, creds) = select_user_info(user_id)?.ok_or(AppErr::NoAccount)?; 171 //! let (server, client) = CredentialCreationOptions64::passkey(RP_ID, entity, creds) 172 //! .start_ceremony() 173 //! .unwrap_or_else(|_e| { 174 //! unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") 175 //! }); 176 //! if reg_ceremonies.insert_remove_all_expired(server).is_some_and(convert::identity) 177 //! { 178 //! Ok(serde_json::to_vec(&client) 179 //! .unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState64::serialize"))) 180 //! } else { 181 //! Err(AppErr::WebAuthnCeremonyCreation) 182 //! } 183 //! } 184 //! /// Finishes passkey registration. 185 //! /// 186 //! /// Pending a successful registration ceremony, a new credential associated with the `UserHandle64` 187 //! /// will be created. This passkey can then be used to log into the application just like any other registered 188 //! /// passkey. 189 //! /// 190 //! /// Note if this errors, then the user should be notified to delete the passkey created on their 191 //! /// authenticator. 192 //! # #[cfg(feature = "serde_relaxed")] 193 //! fn finish_cred_registration( 194 //! reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState64>, 195 //! client_data: &[u8], 196 //! ) -> Result<(), AppErr> { 197 //! // `Registration::from_json_custom` is available iff `serde_relaxed` is enabled. 198 //! let registration = Registration::from_json_custom(client_data)?; 199 //! insert_credential( 200 //! reg_ceremonies 201 //! // `Registration::challenge_relaxed` is available iff `serde_relaxed` is enabled. 202 //! .take(®istration.challenge_relaxed()?) 203 //! .ok_or(AppErr::MissingWebAuthnCeremony)? 204 //! .verify( 205 //! RP_ID, 206 //! ®istration, 207 //! REG_OPTS, 208 //! )?, 209 //! ) 210 //! } 211 //! /// Starts the passkey authentication ceremony. 212 //! # #[cfg(feature = "serde_relaxed")] 213 //! fn start_auth( 214 //! auth_ceremonies: &mut FixedCapHashSet<DiscoverableAuthenticationServerState>, 215 //! ) -> Result<Vec<u8>, AppErr> { 216 //! let (server, client) = DiscoverableCredentialRequestOptions::passkey(RP_ID) 217 //! .start_ceremony() 218 //! .unwrap_or_else(|_e| { 219 //! unreachable!("we don't manually mutate the options and we assume the server clock is functioning; thus this won't error") 220 //! }); 221 //! if auth_ceremonies.insert_remove_all_expired(server).is_some_and(convert::identity) 222 //! { 223 //! Ok(serde_json::to_vec(&client).unwrap_or_else(|_e| { 224 //! unreachable!("bug in DiscoverableAuthenticationClientState::serialize") 225 //! })) 226 //! } else { 227 //! Err(AppErr::WebAuthnCeremonyCreation) 228 //! } 229 //! } 230 //! /// Finishes the passkey authentication ceremony. 231 //! # #[cfg(feature = "serde_relaxed")] 232 //! fn finish_auth( 233 //! auth_ceremonies: &mut FixedCapHashSet<DiscoverableAuthenticationServerState>, 234 //! client_data: &[u8], 235 //! ) -> Result<(), AppErr> { 236 //! // `DiscoverableAuthentication64::from_json_custom` is available iff `serde_relaxed` is enabled. 237 //! let authentication = 238 //! DiscoverableAuthentication64::from_json_custom(client_data)?; 239 //! let mut cred = select_credential( 240 //! authentication.raw_id(), 241 //! authentication.response().user_handle(), 242 //! )? 243 //! .ok_or(AppErr::NoCredential)?; 244 //! if auth_ceremonies 245 //! // `DiscoverableAuthentication64::challenge_relaxed` is available iff `serde_relaxed` is enabled. 246 //! .take(&authentication.challenge_relaxed()?) 247 //! .ok_or(AppErr::MissingWebAuthnCeremony)? 248 //! .verify( 249 //! RP_ID, 250 //! &authentication, 251 //! &mut cred, 252 //! AUTH_OPTS, 253 //! )? 254 //! { 255 //! update_credential(cred.id(), cred.dynamic_state()) 256 //! } else { 257 //! Ok(()) 258 //! } 259 //! } 260 //! /// Writes `account` and `cred` to storage. 261 //! /// 262 //! /// # Errors 263 //! /// 264 //! /// Errors iff writing `account` or `cred` errors, there already exists a credential using the same 265 //! /// `CredentialId`, or there already exists an account using the same `UserHandle64`. 266 //! fn insert_account( 267 //! account: &AccountReg<'_, '_>, 268 //! cred: RegisteredCredential64<'_>, 269 //! ) -> Result<(), AppErr> { 270 //! // ⋮ 271 //! # Ok(()) 272 //! } 273 //! /// Fetches the user info and registered credentials associated with `user_id`. 274 //! /// 275 //! /// # Errors 276 //! /// 277 //! /// Errors iff fetching the data errors. 278 //! fn select_user_info( 279 //! user_id: &UserHandle64, 280 //! ) -> Result< 281 //! Option<( 282 //! PublicKeyCredentialUserEntity64<'static, 'static, '_>, 283 //! Vec<PublicKeyCredentialDescriptor<Vec<u8>>>, 284 //! )>, 285 //! AppErr, 286 //! > { 287 //! // ⋮ 288 //! # Ok(None) 289 //! } 290 //! /// Writes `cred` to storage. 291 //! /// 292 //! /// # Errors 293 //! /// 294 //! /// Errors iff writing `cred` errors, there already exists a credential using the same `CredentialId`, 295 //! /// or there does not exist an account under the `UserHandle64`. 296 //! fn insert_credential( 297 //! cred: RegisteredCredential64<'_>, 298 //! ) -> Result<(), AppErr> { 299 //! // ⋮ 300 //! # Ok(()) 301 //! } 302 //! /// Fetches the `AuthenticatedCredential` associated with `cred_id` ensuring `user_id` matches the 303 //! /// `UserHandle64` associated with the account. 304 //! /// 305 //! /// # Errors 306 //! /// 307 //! /// Errors iff fetching the data errors or the `user_id` does not match the stored `UserHandle64`. 308 //! fn select_credential<'cred, 'user>( 309 //! cred_id: CredentialId<&'cred [u8]>, 310 //! user_id: &'user UserHandle64, 311 //! ) -> Result< 312 //! Option< 313 //! AuthenticatedCredential64< 314 //! 'cred, 315 //! 'user, 316 //! CompressedPubKeyOwned, 317 //! >, 318 //! >, 319 //! AppErr, 320 //! > { 321 //! // ⋮ 322 //! # Ok(None) 323 //! } 324 //! /// Overwrites the current `DynamicState` associated with `cred_id` with `dynamic_state`. 325 //! /// 326 //! /// # Errors 327 //! /// 328 //! /// Errors iff writing errors or `cred_id` does not exist. 329 //! fn update_credential( 330 //! cred_id: CredentialId<&[u8]>, 331 //! dynamic_state: DynamicState, 332 //! ) -> Result<(), AppErr> { 333 //! // ⋮ 334 //! # Ok(()) 335 //! } 336 //! ``` 337 //! 338 //! ## Cargo "features" 339 //! 340 //! [`custom`](#custom) or both [`bin`](#bin) and [`serde`](#serde) must be enabled; otherwise a [`compile_error`] 341 //! will occur. 342 //! 343 //! ### `bin` 344 //! 345 //! Enables binary (de)serialization via [`Encode`] and [`Decode`]. Since registered credentials will almost always 346 //! have to be saved to persistent storage, _some_ form of (de)serialization is necessary. In the event `bin` is 347 //! unsuitable or only partially suitable (e.g., human-readable output is desired), one will need to enable 348 //! [`custom`](#custom) to allow construction of certain types (e.g., [`AuthenticatedCredential`]). 349 //! 350 //! If possible and desired, one may wish to save the data "directly" to avoid any potential temporary allocations. 351 //! For example [`StaticState::encode`] will return a [`Vec`] containing hundreds (and possibly thousands in the 352 //! extreme case) of bytes if the underlying public key is an RSA key. This additional allocation and copy of data 353 //! is obviously avoided if [`StaticState`] is stored as a 354 //! [composite type](https://www.postgresql.org/docs/current/rowtypes.html) or its fields are stored in separate 355 //! columns when written to a relational database (RDB). 356 //! 357 //! ### `custom` 358 //! 359 //! Exposes functions (e.g., [`AuthenticatedCredential::new`]) that allows one to construct instances of types that 360 //! cannot be constructed when [`bin`](#bin) or [`serde`](#serde) is not enabled. 361 //! 362 //! ### `serde` 363 //! 364 //! This feature _strictly_ adheres to the JSON-motivated definitions. You _will_ encounter clients that send data 365 //! that cannot be deserialized using this feature. For many [`serde_relaxed`](#serde_relaxed) should be used 366 //! instead. 367 //! 368 //! Enables (de)serialization of data sent to/from the client via [`serde`](https://docs.rs/serde/latest/serde/) 369 //! based on the JSON-motivated definitions (e.g., 370 //! [`RegistrationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson)). Since 371 //! data has to be sent to/from the client, _some_ form of (de)serialization is necessary. In the event `serde` 372 //! is unsuitable or only partially suitable, one will need to enable [`custom`](#custom) to allow construction 373 //! of certain types (e.g., [`Registration`]). 374 //! 375 //! Code is _strongly_ encouraged to rely on the [`Deserialize`] implementations as much as possible to reduce the 376 //! chances of improperly deserializing the client data. 377 //! 378 //! Note that clients are free to send data in whatever form works best, so there is no requirement the 379 //! JSON-motivated definitions are used even when JSON is sent. This is especially relevant since the JSON-motivated 380 //! definitions were only added in [WebAuthn Level 3](https://www.w3.org/TR/webauthn-3/); thus many deployments only 381 //! partially conform. Some specific deviations that may require partial customization of deserialization are the 382 //! following: 383 //! 384 //! * [`ArrayBuffer`](https://webidl.spec.whatwg.org/#idl-ArrayBuffer)s encoded using something other than 385 //! base64url. 386 //! * `ArrayBuffer`s that are encoded multiple times (including the use of different encodings each time). 387 //! * Missing fields (e.g., 388 //! [`transports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-transports)). 389 //! * Different field names (e.g., `extensions` instead of 390 //! [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-clientextensionresults)). 391 //! 392 //! ### `serde_relaxed` 393 //! 394 //! Automatically enables [`serde`](#serde) in addition to "relaxed" [`Deserialize`] implementations 395 //! (e.g., [`RegistrationRelaxed`]). Roughly "relaxed" translates to unknown fields being ignored and only 396 //! the fields necessary for construction of the type are required. Case still matters, duplicate fields are still 397 //! forbidden, and interrelated data validation is still performed when applicable. This can be useful when one 398 //! wants to accommodate non-conforming clients or clients that implement older versions of the spec. 399 //! 400 //! ### `serializable_server_state` 401 //! 402 //! Automatically enables [`bin`](#bin) in addition to [`Encode`] and [`Decode`] implementations for 403 //! [`RegistrationServerState`], [`DiscoverableAuthenticationServerState`], and 404 //! [`NonDiscoverableAuthenticationServerState`]. Less accurate [`SystemTime`] is used instead of [`Instant`] for 405 //! timeout enforcement. This should be enabled if you don't desire to use in-memory collections to store the instances 406 //! of those types. 407 //! 408 //! Note even when written to persistent storage, an application should still periodically remove expired ceremonies. 409 //! If one is using a relational database (RDB); then one can achieve this by storing [`SentChallenge`], 410 //! the `Vec` returned from [`Encode::encode`], and [`TimedCeremony::expiration`] and periodically remove all rows 411 //! whose expiration exceeds the current date and time. 412 //! 413 //! ## Registration and authentication 414 //! 415 //! Both [registration](https://www.w3.org/TR/webauthn-3/#registration-ceremony) and 416 //! [authentication](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) ceremonies rely on "challenges", and 417 //! these challenges are inherently temporary. For this reason the data associated with challenge completion can 418 //! often be stored in memory without concern for out-of-memory (OOM) conditions. There are several benefits to 419 //! storing such data in memory: 420 //! 421 //! * No data manipulation 422 //! * By leveraging move semantics, the data sent to the client cannot be mutated once the ceremony begins. 423 //! * Improved timeout enforcement 424 //! * By ensuring the same machine that started the ceremony is also used to finish the ceremony, deviation of 425 //! system clocks is not a concern. Additionally, allowing serialization requires the use of some form of 426 //! cross-platform "timestamp" (e.g., [Unix time](https://en.wikipedia.org/wiki/Unix_time)) which differ in 427 //! implementation (e.g., platforms implement leap seconds in different ways) and are often not monotonically 428 //! increasing. If data resides in memory, a monotonic [`Instant`] can be used instead. 429 //! 430 //! It is for those reasons data like [`RegistrationServerState`] are not serializable by default and require the 431 //! use of in-memory collections (e.g., [`FixedCapHashSet`]). To better ensure OOM is not a concern, RPs should set 432 //! reasonable timeouts. Since ceremonies can only be completed by moving data (e.g., 433 //! [`RegistrationServerState::verify`]), ceremony completion is guaranteed to free up the memory used— 434 //! `RegistrationServerState` instances are as small as 48 bytes on `x86_64-unknown-linux-gnu` platforms. To avoid 435 //! issues related to incomplete ceremonies, RPs can periodically iterate the collection for expired ceremonies and 436 //! remove such data. Other techniques can be employed as well to mitigate OOM, but they are application specific 437 //! and out-of-scope. If this is undesirable, one can enable [`serializable_server_state`](#serializable_server_state) 438 //! so that `RegistrationServerState`, [`DiscoverableAuthenticationServerState`], and 439 //! [`NonDiscoverableAuthenticationServerState`] implement [`Encode`] and [`Decode`]. Another reason one may need to 440 //! store this information persistently is for load-balancing purposes where the server that started the ceremony is 441 //! not guaranteed to be the server that finishes the ceremony. 442 //! 443 //! ## Supported signature algorithms 444 //! 445 //! The only supported signature algorithms are the following: 446 //! 447 //! * Ed25519 as defined in [RFC 8032 § 5.1](https://www.rfc-editor.org/rfc/rfc8032#section-5.1). This corresponds 448 //! to [`CoseAlgorithmIdentifier::Eddsa`]. 449 //! * ECDSA as defined in [SEC 1 Version 2.0 § 4.1](https://www.secg.org/sec1-v2.pdf#subsection.4.1) using SHA-256 450 //! as the hash function and NIST P-256 as defined in 451 //! [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) 452 //! for the underlying elliptic curve. This corresponds to [`CoseAlgorithmIdentifier::Es256`]. 453 //! * 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 454 //! [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) 455 //! for the underlying elliptic curve. This corresponds to [`CoseAlgorithmIdentifier::Es384`]. 456 //! * RSASSA-PKCS1-v1_5 as defined in [RFC 8017 § 8.2](https://www.rfc-editor.org/rfc/rfc8017#section-8.2) using 457 //! SHA-256 as the hash function. This corresponds to [`CoseAlgorithmIdentifier::Rs256`]. 458 //! 459 //! ## Correctness of code 460 //! 461 //! This library more strictly adheres to the spec than many other similar libraries including but not limited to 462 //! the following ways: 463 //! 464 //! * [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). 465 //! * `Deserialize` implementations requiring _exact_ conformance (e.g., not allowing unknown data). 466 //! * More thorough interrelated data validation (e.g., all places a Credential ID exists must match). 467 //! * Implement a lot of recommended (i.e., SHOULD) criteria (e.g., 468 //! [User display names conforming to the Nickname Profile as defined in RFC 8266](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name)). 469 //! 470 //! Unfortunately like almost all software, this library has not been formally verified; however great care is 471 //! employed in the following ways: 472 //! 473 //! * Leverage move semantics to prevent mutation of data once in a static state. 474 //! * Ensure a great many invariants via types. 475 //! * Reduce code duplication. 476 //! * Reduce variable mutation allowing for simpler algebraic reasoning. 477 //! * `panic`-free code[^note] (i.e., define true/total functions). 478 //! * Ensure arithmetic "side effects" don't occur (e.g., overflow). 479 //! * Aggressive use of compiler and [Clippy](https://doc.rust-lang.org/stable/clippy/lints.html) lints. 480 //! * Unit tests for common cases, edge cases, and error cases. 481 //! 482 //! ## Cryptographic libraries 483 //! 484 //! This library does not rely on _any_ sensitive data (e.g., private keys) as only signature verification is 485 //! ever performed. This means that the only thing that matters with the libraries used is their algorithmic 486 //! correctness and not other normally essential aspects like susceptibility to side-channel attacks. While I 487 //! personally believe the libraries that are used are at least as "secure" as alternatives even when dealing with 488 //! sensitive data, one only needs to audit the correctness of the libraries to be confident in their use. In fact 489 //! [`curve25519_dalek`](https://docs.rs/curve25519-dalek/latest/curve25519_dalek/#backends) has been formally 490 //! verified when the [`fiat`](https://github.com/mit-plv/fiat-crypto) backend is used making it _objectively_ 491 //! better than many other libraries whose correctness has not been proven. Two additional benefits of the library 492 //! choices are simpler APIs making it more likely their use is correct and better cross-platform compatibility. 493 //! 494 //! [^note]: `panic`s related to memory allocations or stack overflow are possible since such issues are not 495 //! formally guarded against. 496 #![expect( 497 clippy::multiple_crate_versions, 498 reason = "RustCrypto hasn't updated rand yet" 499 )] 500 #![cfg_attr(docsrs, feature(doc_cfg))] 501 #[cfg(not(any(feature = "custom", all(feature = "bin", feature = "serde"))))] 502 compile_error!("'custom' must be enabled or both 'bin' and 'serde' must be enabled"); 503 #[cfg(feature = "serializable_server_state")] 504 use crate::request::{ 505 auth::ser_server_state::{ 506 DecodeDiscoverableAuthenticationServerStateErr, 507 DecodeNonDiscoverableAuthenticationServerStateErr, 508 EncodeNonDiscoverableAuthenticationServerStateErr, 509 }, 510 register::ser_server_state::DecodeRegistrationServerStateErr, 511 }; 512 #[cfg(any(feature = "bin", feature = "custom"))] 513 use crate::response::error::CredentialIdErr; 514 #[cfg(feature = "serde_relaxed")] 515 use crate::response::ser_relaxed::SerdeJsonErr; 516 #[cfg(doc)] 517 use crate::{ 518 hash::hash_set::FixedCapHashSet, 519 request::{ 520 AsciiDomain, DomainOrigin, Port, PublicKeyCredentialDescriptor, RpId, Scheme, 521 TimedCeremony, Url, 522 auth::{AllowedCredential, AllowedCredentials, PublicKeyCredentialRequestOptions}, 523 register::{ 524 CoseAlgorithmIdentifier, Nickname, PublicKeyCredentialCreationOptions, 525 PublicKeyCredentialUserEntity, UserHandle16, UserHandle64, Username, 526 }, 527 }, 528 response::{ 529 CollectedClientData, Flag, SentChallenge, 530 auth::{self, Authentication, DiscoverableAuthenticatorAssertion}, 531 register::{ 532 self, Aaguid, Attestation, AttestationObject, AttestedCredentialData, 533 AuthenticatorExtensionOutput, ClientExtensionsOutputs, CompressedPubKey, 534 CredentialPropertiesOutput, 535 }, 536 }, 537 }; 538 #[cfg(feature = "bin")] 539 use crate::{ 540 request::register::bin::{DecodeNicknameErr, DecodeUsernameErr}, 541 response::{ 542 bin::DecodeAuthTransportsErr, 543 register::bin::{DecodeDynamicStateErr, DecodeStaticStateErr}, 544 }, 545 }; 546 use crate::{ 547 request::{ 548 auth::error::{InvalidTimeout, SecondFactorErr}, 549 error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr}, 550 register::{ 551 ResidentKeyRequirement, USER_HANDLE_MAX_LEN, UserHandle, 552 error::{CreationOptionsErr, NicknameErr, UsernameErr}, 553 }, 554 }, 555 response::{ 556 AuthTransports, CredentialId, 557 auth::error::{AuthCeremonyErr, AuthenticatorDataErr as AuthAuthDataErr}, 558 error::CollectedClientDataErr, 559 register::{ 560 CredentialProtectionPolicy, DynamicState, Metadata, StaticState, UncompressedPubKey, 561 error::{ 562 AaguidErr, AttestationObjectErr, AuthenticatorDataErr as RegAuthDataErr, 563 RegCeremonyErr, 564 }, 565 }, 566 }, 567 }; 568 #[cfg(all(doc, feature = "bin"))] 569 use bin::{Decode, Encode}; 570 #[cfg(doc)] 571 use core::str::FromStr; 572 use core::{ 573 convert, 574 error::Error, 575 fmt::{self, Display, Formatter}, 576 ops::Not, 577 }; 578 #[cfg(all(doc, feature = "serde_relaxed"))] 579 use response::register::ser_relaxed::RegistrationRelaxed; 580 #[cfg(all(doc, feature = "serde"))] 581 use serde::Deserialize; 582 #[cfg(all(doc, feature = "serde_relaxed"))] 583 use serde_json::de::{Deserializer, StreamDeserializer}; 584 #[cfg(feature = "serializable_server_state")] 585 use std::time::SystemTimeError; 586 #[cfg(doc)] 587 use std::time::{Instant, SystemTime}; 588 /// Contains functionality to (de)serialize data to a data store. 589 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 590 #[cfg(feature = "bin")] 591 pub mod bin; 592 /// Contains functionality for fixed-capacity hash maps and sets. 593 pub mod hash; 594 /// Functionality for starting ceremonies. 595 /// 596 /// # What kind of credential should I create? 597 /// 598 /// Without partitioning the possibilities _too_ much, the following are possible authentication flows: 599 /// 600 /// | Label | Username | Password | Client-side credential | Authenticator-side user verification | Recommended | 601 /// |-------|----------|----------|------------------------|--------------------------------------|:-----------:| 602 /// | 1 | Yes | Yes | Required | Yes | ❌ | 603 /// | 2 | Yes | Yes | Required | No | ❌ | 604 /// | 3 | Yes | Yes | Optional | Yes | ❌ | 605 /// | <a name="label4">4</a> | Yes | Yes | Optional | No | ✅ | 606 /// | 5 | Yes | No | Required | Yes | ❌ | 607 /// | 6 | Yes | No | Required | No | ❌ | 608 /// | <a name="label7">7</a> | Yes | No | Optional | Yes | ❔ | 609 /// | 8 | Yes | No | Optional | No | ❌ | 610 /// | 9 | No | Yes | Required | Yes | ❌ | 611 /// | 10 | No | Yes | Required | No | ❌ | 612 /// | 11 | No | Yes | Optional | Yes | ❌ | 613 /// | 12 | No | Yes | Optional | No | ❌ | 614 /// | <a name="label13">13</a> | No | No | Required | Yes | ✅ | 615 /// | 14 | No | No | Required | No | ❌ | 616 /// | 15 | No | No | Optional | Yes | ❌ | 617 /// | 16 | No | No | Optional | No | ❌ | 618 /// 619 /// * All `Label`s with both `Password` and `Authenticator-side user verification` set to `Yes` are not recommended 620 /// since the verification done on the authenticator is likely the same "factor" as a password; thus it does not 621 /// add benefit but only serves as an annoyance to users. 622 /// * All `Label`s with `Username` or `Password` set to `Yes` and `Client-side credential` set to `Required` are not 623 /// recommended since you may preclude authenticators that are storage constrained (e.g., security keys). 624 /// * All `Label`s with `Username` set to `No` and `Client-side credential` set to `Optional` are not possible since 625 /// RPs would not have a way to identify the set of encrypted credentials to pass to the unknown user. 626 /// * All `Label`s with `Password` and `Authenticator-side user verification` set to `No` are not recommended since 627 /// those are single-factor authentication schemes; thus anyone possessing the credential without also passing 628 /// some form of user verification (e.g., password) would authenticate. 629 /// * [`Label 7`](#label7) is possible for RPs that are comfortable passing an encrypted credential to a potential user 630 /// without having that user first pass another form of authentication. For many RPs passing such information even 631 /// if encrypted is not desirable though. 632 /// * [`Label 4`](#label4) is ideal as a single-factor flow incorporated within a wider multi-factor authentication (MFA) 633 /// setup. The easiest way to register such a credential is with 634 /// [`CredentialCreationOptions::second_factor`]. 635 /// * [`Label 13`](#label13) is ideal for passkey setups as it allows for pleasant UX where a user does not have to type a 636 /// username nor password while still being secured with MFA with one of the factors being based on public-key 637 /// cryptography which for many is the most secure form of single-factor authentication. The easiest way to register 638 /// such a credential is with [`CredentialCreationOptions::passkey`]. 639 /// 640 /// Two other reasons one may prefer to construct client-side credentials is richer support for extensions (e.g., 641 /// [`largeBlobKey`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-largeBlobKey-extension) 642 /// for CTAP 2.2 authenticators) and the ability to use both discoverable and nondiscoverable requests. The former is not 643 /// relevant for this library—at least currently—since the only extensions supported are applicable for both 644 /// client-side and server-side credentials. The latter can be important especially if an RP wants the ability to 645 /// seamlessly transition from a username and password scheme to a userless and passwordless one in the future. 646 /// 647 /// Note the table is purely informative. While helper functions 648 /// (e.g., [`CredentialCreationOptions::passkey`]) only exist for [`Label 4`](#label4) and 649 /// [`Label 13`](#label13), one can create any credential since all fields in [`CredentialCreationOptions`] 650 /// and [`PublicKeyCredentialRequestOptions`] are accessible. 651 pub mod request; 652 /// Functionality for completing ceremonies. 653 /// 654 /// Read [`request`] for more information about what credentials one should create. 655 pub mod response; 656 #[doc(inline)] 657 pub use crate::{ 658 request::{ 659 auth::{ 660 DiscoverableAuthenticationClientState, DiscoverableAuthenticationServerState, 661 DiscoverableCredentialRequestOptions, NonDiscoverableAuthenticationClientState, 662 NonDiscoverableAuthenticationServerState, NonDiscoverableCredentialRequestOptions, 663 }, 664 register::{ 665 CredentialCreationOptions, CredentialCreationOptions16, CredentialCreationOptions64, 666 RegistrationClientState, RegistrationClientState16, RegistrationClientState64, 667 RegistrationServerState, RegistrationServerState16, RegistrationServerState64, 668 }, 669 }, 670 response::{ 671 auth::{ 672 DiscoverableAuthentication, DiscoverableAuthentication16, DiscoverableAuthentication64, 673 NonDiscoverableAuthentication, NonDiscoverableAuthentication16, 674 NonDiscoverableAuthentication64, 675 }, 676 register::Registration, 677 }, 678 }; 679 /// Error returned in [`RegCeremonyErr::Credential`] and [`AuthenticatedCredential::new`]. 680 #[derive(Clone, Copy, Debug)] 681 pub enum CredentialErr { 682 /// Variant when [`CredentialProtectionPolicy::UserVerificationRequired`], but 683 /// [`DynamicState::user_verified`] is `false`. 684 CredProtectUserVerificationRequiredWithoutUserVerified, 685 /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)` and 686 /// [`DynamicState::user_verified`] is `false`. 687 HmacSecretWithoutUserVerified, 688 /// Variant when [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(true)`, but 689 /// [`ClientExtensionsOutputs::prf`] is `Some(AuthenticationExtensionsPRFOutputs { enabled: false })` 690 /// or `AuthenticatorExtensionOutput::hmac_secret` is `Some`, but 691 /// `ClientExtensionsOutputs::prf` is `None`. 692 HmacSecretWithoutPrf, 693 /// Variant when [`ClientExtensionsOutputs::prf`] is 694 /// `Some(AuthenticationExtensionsPRFOutputs { enabled: true })`, but 695 /// [`AuthenticatorExtensionOutput::hmac_secret`] is `Some(false)`. 696 PrfWithoutHmacSecret, 697 /// Variant when [`ResidentKeyRequirement::Required`] was sent, but 698 /// [`CredentialPropertiesOutput::rk`] is `Some(false)`. 699 ResidentKeyRequiredServerCredentialCreated, 700 } 701 impl Display for CredentialErr { 702 #[inline] 703 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 704 f.write_str(match *self { 705 Self::CredProtectUserVerificationRequiredWithoutUserVerified => { 706 "credProtect requires user verification, but the user is not verified" 707 } 708 Self::HmacSecretWithoutUserVerified => { 709 "hmac-secret was enabled, but the user is not verified" 710 } 711 Self::HmacSecretWithoutPrf => "hmac-secret was enabled but prf was not", 712 Self::PrfWithoutHmacSecret => "prf was enabled, but hmac-secret was not", 713 Self::ResidentKeyRequiredServerCredentialCreated => { 714 "server-side credential was created, but a client-side credential is required" 715 } 716 }) 717 } 718 } 719 impl Error for CredentialErr {} 720 /// Checks if the `static_state` and `dynamic_state` are valid. 721 /// 722 /// # Errors 723 /// 724 /// Errors iff `static_state` or `dynamc_state` are invalid. 725 fn verify_static_and_dynamic_state<T>( 726 static_state: &StaticState<T>, 727 dynamic_state: DynamicState, 728 ) -> Result<(), CredentialErr> { 729 if dynamic_state.user_verified { 730 Ok(()) 731 } else if matches!( 732 static_state.extensions.cred_protect, 733 CredentialProtectionPolicy::UserVerificationRequired 734 ) { 735 Err(CredentialErr::CredProtectUserVerificationRequiredWithoutUserVerified) 736 } else if static_state 737 .extensions 738 .hmac_secret 739 .is_some_and(convert::identity) 740 { 741 Err(CredentialErr::HmacSecretWithoutUserVerified) 742 } else { 743 Ok(()) 744 } 745 .and_then(|()| { 746 static_state.client_extension_results.prf.map_or_else( 747 || { 748 if static_state.extensions.hmac_secret.is_none() { 749 Ok(()) 750 } else { 751 Err(CredentialErr::HmacSecretWithoutPrf) 752 } 753 }, 754 |prf| { 755 if prf.enabled { 756 if static_state.extensions.hmac_secret.is_some_and(Not::not) { 757 Err(CredentialErr::PrfWithoutHmacSecret) 758 } else { 759 Ok(()) 760 } 761 } else if static_state 762 .extensions 763 .hmac_secret 764 .is_some_and(convert::identity) 765 { 766 Err(CredentialErr::HmacSecretWithoutPrf) 767 } else { 768 Ok(()) 769 } 770 }, 771 ) 772 }) 773 } 774 /// Registered credential that needs to be saved server-side to perform future 775 /// [authentication ceremonies](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) with 776 /// [`AuthenticatedCredential`]. 777 /// 778 /// When saving `RegisteredCredential` to persistent storage, one will almost always want to save the contained data 779 /// separately. The reasons for this are the following: 780 /// 781 /// * [`CredentialId`] 782 /// * MUST be globally unique, and it will likely be easier to enforce such uniqueness when it's separate. 783 /// * Fetching the [`AuthenticatedCredential`] by [`Authentication::raw_id`] when completing the 784 /// authentication ceremony via [`DiscoverableAuthenticationServerState::verify`] or 785 /// [`NonDiscoverableAuthenticationServerState::verify`] will likely be easier than alternatives. 786 /// * [`AuthTransports`] 787 /// * Fetching [`CredentialId`]s and associated `AuthTransports` by [`UserHandle`] will likely make credential 788 /// registration easier since one should set [`PublicKeyCredentialCreationOptions::exclude_credentials`] to 789 /// the [`PublicKeyCredentialDescriptor`]s belonging to a `UserHandle` in order to avoid accidentally 790 /// overwriting an existing credential on the authenticator. 791 /// * Fetching `CredentialId`s and associated `AuthTransports` by `UserHandle` will likely make starting 792 /// authentication ceremonies easier for [`NonDiscoverableCredentialRequestOptions`]. 793 /// * [`UserHandle`] 794 /// * Fetching the [`AuthenticatedCredential`] by [`DiscoverableAuthentication::raw_id`] must also coincide with 795 /// verifying the associated `UserHandle` matches [`DiscoverableAuthenticatorAssertion::user_handle`]. 796 /// * Fetching [`CredentialId`]s and associated [`AuthTransports`] by `UserHandle` will likely make credential 797 /// registration easier since one should set [`PublicKeyCredentialCreationOptions::exclude_credentials`] to 798 /// the [`PublicKeyCredentialDescriptor`]s belonging to a `UserHandle` in order to avoid accidentally 799 /// overwriting an existing credential on the authenticator. 800 /// * Fetching `CredentialId`s and associated `AuthTransports` by `UserHandle` will likely make starting 801 /// authentication ceremonies easier for [`NonDiscoverableCredentialRequestOptions`]. 802 /// * [`DynamicState`] 803 /// * `DynamicState` is the only part that is ever updated after a successful authentication ceremony 804 /// via [`DiscoverableAuthenticationServerState::verify`] or 805 /// [`NonDiscoverableAuthenticationServerState::verify`]. It being separate allows for smaller and quicker 806 /// updates. 807 /// * [`Metadata`] 808 /// * Informative data that is never used during authentication ceremonies; consequently, one may wish to 809 /// not even save this information. 810 /// * [`StaticState`] 811 /// * All other data exists as part of `StaticState`. 812 /// 813 /// It is for those reasons that `RegisteredCredential` does not implement [`Encode`] or [`Decode`]; instead its parts 814 /// do. 815 /// 816 /// Note that [`RpId`] and user information other than the `UserHandle` are not stored in `RegisteredCredential`. 817 /// RPs that wish to store such information must do so on their own. Since user information is likely the same 818 /// for a given `UserHandle` and `RpId` is likely static, it makes little sense to store such information 819 /// automatically. Types like [`Username`] implement `Encode` and `Decode` to assist such a thing. 820 /// 821 /// When registering a credential, [`AttestedCredentialData::aaguid`], [`AttestedCredentialData::credential_id`], 822 /// and [`AttestedCredentialData::credential_public_key`] will be the sources for [`Metadata::aaguid`], 823 /// [`Self::id`], and [`StaticState::credential_public_key`] respectively. The [`PublicKeyCredentialUserEntity::id`] 824 /// associated with the [`CredentialCreationOptions`] used to create the `RegisteredCredential` via 825 /// [`RegistrationServerState::verify`] will be the source for [`Self::user_id`]. 826 /// 827 /// The only way to create this is via `RegistrationServerState::verify`. 828 #[derive(Debug)] 829 pub struct RegisteredCredential<'reg, const USER_LEN: usize> { 830 /// The credential ID. 831 /// 832 /// For client-side credentials, this is a unique identifier; but for server-side 833 /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information). 834 id: CredentialId<&'reg [u8]>, 835 /// Hints for how the client might communicate with the authenticator containing the credential. 836 transports: AuthTransports, 837 /// The identifier for the user. 838 /// 839 /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e., 840 /// multiple [`CredentialId`]s will often exist for the same `UserHandle`). 841 user_id: UserHandle<USER_LEN>, 842 /// Immutable state returned during registration. 843 static_state: StaticState<UncompressedPubKey<'reg>>, 844 /// State that can change during authentication ceremonies. 845 dynamic_state: DynamicState, 846 /// Metadata. 847 metadata: Metadata<'reg>, 848 } 849 impl<'reg, const USER_LEN: usize> RegisteredCredential<'reg, USER_LEN> { 850 /// The credential ID. 851 /// 852 /// For client-side credentials, this is a unique identifier; but for server-side 853 /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information). 854 #[inline] 855 #[must_use] 856 pub const fn id(&self) -> CredentialId<&'reg [u8]> { 857 self.id 858 } 859 /// Hints for how the client might communicate with the authenticator containing the credential. 860 #[inline] 861 #[must_use] 862 pub const fn transports(&self) -> AuthTransports { 863 self.transports 864 } 865 /// The identifier for the user. 866 /// 867 /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e., 868 /// multiple [`CredentialId`]s will often exist for the same `UserHandle`). 869 #[inline] 870 #[must_use] 871 pub const fn user_id(&self) -> &UserHandle<USER_LEN> { 872 &self.user_id 873 } 874 /// Immutable state returned during registration. 875 #[inline] 876 #[must_use] 877 pub const fn static_state(&self) -> StaticState<UncompressedPubKey<'reg>> { 878 self.static_state 879 } 880 /// State that can change during authentication ceremonies. 881 #[inline] 882 #[must_use] 883 pub const fn dynamic_state(&self) -> DynamicState { 884 self.dynamic_state 885 } 886 /// Metadata. 887 #[inline] 888 #[must_use] 889 pub const fn metadata(&self) -> Metadata<'reg> { 890 self.metadata 891 } 892 /// Constructs a `RegisteredCredential` based on the passed arguments. 893 /// 894 /// # Errors 895 /// 896 /// Errors iff the passed arguments are invalid. Read [`CredentialErr`] 897 /// for more information. 898 #[inline] 899 fn new<'a: 'reg>( 900 id: CredentialId<&'a [u8]>, 901 transports: AuthTransports, 902 user_id: UserHandle<USER_LEN>, 903 static_state: StaticState<UncompressedPubKey<'a>>, 904 dynamic_state: DynamicState, 905 metadata: Metadata<'a>, 906 ) -> Result<Self, CredentialErr> { 907 verify_static_and_dynamic_state(&static_state, dynamic_state).and_then(|()| { 908 if !matches!(metadata.resident_key, ResidentKeyRequirement::Required) 909 || metadata 910 .client_extension_results 911 .cred_props 912 .as_ref() 913 .is_none_or(|props| props.rk.is_none_or(convert::identity)) 914 { 915 Ok(Self { 916 id, 917 transports, 918 user_id, 919 static_state, 920 dynamic_state, 921 metadata, 922 }) 923 } else { 924 Err(CredentialErr::ResidentKeyRequiredServerCredentialCreated) 925 } 926 }) 927 } 928 /// Returns the contained data consuming `self`. 929 #[inline] 930 #[must_use] 931 pub const fn into_parts( 932 self, 933 ) -> ( 934 CredentialId<&'reg [u8]>, 935 AuthTransports, 936 UserHandle<USER_LEN>, 937 StaticState<UncompressedPubKey<'reg>>, 938 DynamicState, 939 Metadata<'reg>, 940 ) { 941 ( 942 self.id, 943 self.transports, 944 self.user_id, 945 self.static_state, 946 self.dynamic_state, 947 self.metadata, 948 ) 949 } 950 /// Returns the contained data. 951 #[inline] 952 #[must_use] 953 pub const fn as_parts( 954 &self, 955 ) -> ( 956 CredentialId<&'reg [u8]>, 957 AuthTransports, 958 &UserHandle<USER_LEN>, 959 StaticState<UncompressedPubKey<'reg>>, 960 DynamicState, 961 Metadata<'reg>, 962 ) { 963 ( 964 self.id, 965 self.transports, 966 &self.user_id, 967 self.static_state, 968 self.dynamic_state, 969 self.metadata, 970 ) 971 } 972 } 973 /// `RegisteredCredential` based on a [`UserHandle64`]. 974 pub type RegisteredCredential64<'reg> = RegisteredCredential<'reg, USER_HANDLE_MAX_LEN>; 975 /// `RegisteredCredential` based on a [`UserHandle16`]. 976 pub type RegisteredCredential16<'reg> = RegisteredCredential<'reg, 16>; 977 /// Credential used in authentication ceremonies. 978 /// 979 /// Similar to [`RegisteredCredential`] except designed to only contain the necessary data to complete 980 /// authentication ceremonies. In particular there is no [`AuthTransports`] or [`Metadata`], 981 /// [`StaticState::credential_public_key`] is [`CompressedPubKey`] that can own or borrow its data, [`Self::id`] is 982 /// based on the [`CredentialId`] passed to [`Self::new`] which itself must be from [`Authentication::raw_id`], and 983 /// [`Self::user_id`] is based on the [`UserHandle`] passed to [`Self::new`] which itself must be the value in 984 /// persistent storage associated with the `CredentialId`. 985 /// 986 /// When [`DiscoverableAuthentication`] is used, one can use [`DiscoverableAuthenticatorAssertion::user_handle`] 987 /// for `Self::user_id` so long as it matches the value in persistent storage. 988 /// 989 /// Note `PublicKey` should be `CompressedPubKey` for this to be useful. 990 /// 991 /// The only way to create this is via `Self::new`. 992 #[derive(Debug)] 993 pub struct AuthenticatedCredential<'cred, 'user, const USER_LEN: usize, PublicKey> { 994 /// The credential ID. 995 /// 996 /// For client-side credentials, this is a unique identifier; but for server-side 997 /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information). 998 id: CredentialId<&'cred [u8]>, 999 /// The identifier for the user. 1000 /// 1001 /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e., 1002 /// multiple [`CredentialId`]s will often exist for the same `UserHandle`). 1003 user_id: &'user UserHandle<USER_LEN>, 1004 /// Immutable state returned during registration. 1005 static_state: StaticState<PublicKey>, 1006 /// State that can change during authentication ceremonies. 1007 dynamic_state: DynamicState, 1008 } 1009 impl<'cred, 'user, const USER_LEN: usize, PublicKey> 1010 AuthenticatedCredential<'cred, 'user, USER_LEN, PublicKey> 1011 { 1012 /// The credential ID. 1013 /// 1014 /// For client-side credentials, this is a unique identifier; but for server-side 1015 /// credentials, this _is_ the credential (i.e., the encrypted private key and necessary information). 1016 #[inline] 1017 #[must_use] 1018 pub const fn id(&self) -> CredentialId<&'cred [u8]> { 1019 self.id 1020 } 1021 /// The identifier for the user. 1022 /// 1023 /// Unlike [`Self::id`] which is globally unique for an RP, this is unique up to "user" (i.e., 1024 /// multiple [`CredentialId`]s will often exist for the same `UserHandle`). 1025 #[inline] 1026 #[must_use] 1027 pub const fn user_id(&self) -> &'user UserHandle<USER_LEN> { 1028 self.user_id 1029 } 1030 /// Immutable state returned during registration. 1031 #[inline] 1032 #[must_use] 1033 pub const fn static_state(&self) -> &StaticState<PublicKey> { 1034 &self.static_state 1035 } 1036 /// State that can change during authentication ceremonies. 1037 #[inline] 1038 #[must_use] 1039 pub const fn dynamic_state(&self) -> DynamicState { 1040 self.dynamic_state 1041 } 1042 /// Constructs an `AuthenticatedCredential` based on the passed arguments. 1043 /// 1044 /// # Errors 1045 /// 1046 /// Errors iff the passed arguments are invalid. Read [`CredentialErr`] 1047 /// for more information. 1048 #[expect(single_use_lifetimes, reason = "false positive")] 1049 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))] 1050 #[cfg(any(feature = "bin", feature = "custom"))] 1051 #[inline] 1052 pub fn new<'a: 'cred, 'b: 'user>( 1053 id: CredentialId<&'a [u8]>, 1054 user_id: &'b UserHandle<USER_LEN>, 1055 static_state: StaticState<PublicKey>, 1056 dynamic_state: DynamicState, 1057 ) -> Result<Self, CredentialErr> { 1058 verify_static_and_dynamic_state(&static_state, dynamic_state).map(|()| Self { 1059 id, 1060 user_id, 1061 static_state, 1062 dynamic_state, 1063 }) 1064 } 1065 /// Returns the contained data consuming `self`. 1066 #[inline] 1067 #[must_use] 1068 pub fn into_parts( 1069 self, 1070 ) -> ( 1071 CredentialId<&'cred [u8]>, 1072 &'user UserHandle<USER_LEN>, 1073 StaticState<PublicKey>, 1074 DynamicState, 1075 ) { 1076 (self.id, self.user_id, self.static_state, self.dynamic_state) 1077 } 1078 /// Returns the contained data. 1079 #[inline] 1080 #[must_use] 1081 pub const fn as_parts( 1082 &self, 1083 ) -> ( 1084 CredentialId<&'cred [u8]>, 1085 &'user UserHandle<USER_LEN>, 1086 &StaticState<PublicKey>, 1087 DynamicState, 1088 ) { 1089 ( 1090 self.id, 1091 self.user_id, 1092 self.static_state(), 1093 self.dynamic_state, 1094 ) 1095 } 1096 } 1097 /// `AuthenticatedCredential` based on a [`UserHandle64`]. 1098 pub type AuthenticatedCredential64<'cred, 'user, PublicKey> = 1099 AuthenticatedCredential<'cred, 'user, USER_HANDLE_MAX_LEN, PublicKey>; 1100 /// `AuthenticatedCredential` based on a [`UserHandle16`]. 1101 pub type AuthenticatedCredential16<'cred, 'user, PublicKey> = 1102 AuthenticatedCredential<'cred, 'user, 16, PublicKey>; 1103 /// Convenience aggregate error that rolls up all errors into one. 1104 #[derive(Debug)] 1105 pub enum AggErr { 1106 /// Variant when [`AsciiDomain::try_from`] errors. 1107 AsciiDomain(AsciiDomainErr), 1108 /// Variant when [`Url::from_str`] errors. 1109 Url(UrlErr), 1110 /// Variant when [`Scheme::try_from`] errors. 1111 Scheme(SchemeParseErr), 1112 /// Variant when [`DomainOrigin::try_from`] errors. 1113 DomainOrigin(DomainOriginParseErr), 1114 /// Variant when [`Port::from_str`] errors. 1115 Port(PortParseErr), 1116 /// Variant when [`DiscoverableCredentialRequestOptions::start_ceremony`] or 1117 /// [`NonDiscoverableCredentialRequestOptions::start_ceremony`] 1118 /// error. 1119 InvalidTimeout(InvalidTimeout), 1120 /// Variant when [`NonDiscoverableCredentialRequestOptions::second_factor`] errors. 1121 SecondFactor(SecondFactorErr), 1122 /// Variant when [`CredentialCreationOptions::start_ceremony`] errors. 1123 CreationOptions(CreationOptionsErr), 1124 /// Variant when [`Nickname::try_from`] errors. 1125 Nickname(NicknameErr), 1126 /// Variant when [`Username::try_from`] errors. 1127 Username(UsernameErr), 1128 /// Variant when [`RegistrationServerState::verify`] errors. 1129 RegCeremony(RegCeremonyErr), 1130 /// Variant when [`DiscoverableAuthenticationServerState::verify`] or. 1131 /// [`NonDiscoverableAuthenticationServerState::verify`] error. 1132 AuthCeremony(AuthCeremonyErr), 1133 /// Variant when [`AttestationObject::try_from`] errors. 1134 AttestationObject(AttestationObjectErr), 1135 /// Variant when [`register::AuthenticatorData::try_from`] errors. 1136 RegAuthenticatorData(RegAuthDataErr), 1137 /// Variant when [`auth::AuthenticatorData::try_from`] errors. 1138 AuthAuthenticatorData(AuthAuthDataErr), 1139 /// Variant when [`CollectedClientData::from_client_data_json`] errors. 1140 CollectedClientData(CollectedClientDataErr), 1141 /// Variant when [`CollectedClientData::from_client_data_json_relaxed`] errors or any of the [`Deserialize`] 1142 /// implementations error when relying on [`Deserializer`] or [`StreamDeserializer`]. 1143 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 1144 #[cfg(feature = "serde_relaxed")] 1145 SerdeJson(SerdeJsonErr), 1146 /// Variant when [`Aaguid::try_from`] errors. 1147 Aaguid(AaguidErr), 1148 /// Variant when [`AuthTransports::decode`] errors. 1149 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1150 #[cfg(feature = "bin")] 1151 DecodeAuthTransports(DecodeAuthTransportsErr), 1152 /// Variant when [`StaticState::decode`] errors. 1153 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1154 #[cfg(feature = "bin")] 1155 DecodeStaticState(DecodeStaticStateErr), 1156 /// Variant when [`DynamicState::decode`] errors. 1157 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1158 #[cfg(feature = "bin")] 1159 DecodeDynamicState(DecodeDynamicStateErr), 1160 /// Variant when [`Nickname::decode`] errors. 1161 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1162 #[cfg(feature = "bin")] 1163 DecodeNickname(DecodeNicknameErr), 1164 /// Variant when [`Username::decode`] errors. 1165 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1166 #[cfg(feature = "bin")] 1167 DecodeUsername(DecodeUsernameErr), 1168 /// Variant when [`RegistrationServerState::decode`] errors. 1169 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1170 #[cfg(feature = "serializable_server_state")] 1171 DecodeRegistrationServerState(DecodeRegistrationServerStateErr), 1172 /// Variant when [`DiscoverableAuthenticationServerState::decode`] errors. 1173 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1174 #[cfg(feature = "serializable_server_state")] 1175 DecodeDiscoverableAuthenticationServerState(DecodeDiscoverableAuthenticationServerStateErr), 1176 /// Variant when [`NonDiscoverableAuthenticationServerState::decode`] errors. 1177 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1178 #[cfg(feature = "serializable_server_state")] 1179 DecodeNonDiscoverableAuthenticationServerState( 1180 DecodeNonDiscoverableAuthenticationServerStateErr, 1181 ), 1182 /// Variant when [`RegistrationServerState::encode`] errors. 1183 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1184 #[cfg(feature = "serializable_server_state")] 1185 EncodeRegistrationServerState(SystemTimeError), 1186 /// Variant when [`DiscoverableAuthenticationServerState::encode`] errors. 1187 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1188 #[cfg(feature = "serializable_server_state")] 1189 EncodeDiscoverableAuthenticationServerState(SystemTimeError), 1190 /// Variant when [`NonDiscoverableAuthenticationServerState::encode`] errors. 1191 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1192 #[cfg(feature = "serializable_server_state")] 1193 EncodeNonDiscoverableAuthenticationServerState( 1194 EncodeNonDiscoverableAuthenticationServerStateErr, 1195 ), 1196 /// Variant when [`AuthenticatedCredential::new`] errors. 1197 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))] 1198 #[cfg(any(feature = "bin", feature = "custom"))] 1199 Credential(CredentialErr), 1200 /// Variant when [`CredentialId::try_from`] or [`CredentialId::decode`] errors. 1201 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))] 1202 #[cfg(any(feature = "bin", feature = "custom"))] 1203 CredentialId(CredentialIdErr), 1204 } 1205 impl From<AsciiDomainErr> for AggErr { 1206 #[inline] 1207 fn from(value: AsciiDomainErr) -> Self { 1208 Self::AsciiDomain(value) 1209 } 1210 } 1211 impl From<UrlErr> for AggErr { 1212 #[inline] 1213 fn from(value: UrlErr) -> Self { 1214 Self::Url(value) 1215 } 1216 } 1217 impl From<SchemeParseErr> for AggErr { 1218 #[inline] 1219 fn from(value: SchemeParseErr) -> Self { 1220 Self::Scheme(value) 1221 } 1222 } 1223 impl From<DomainOriginParseErr> for AggErr { 1224 #[inline] 1225 fn from(value: DomainOriginParseErr) -> Self { 1226 Self::DomainOrigin(value) 1227 } 1228 } 1229 impl From<PortParseErr> for AggErr { 1230 #[inline] 1231 fn from(value: PortParseErr) -> Self { 1232 Self::Port(value) 1233 } 1234 } 1235 impl From<InvalidTimeout> for AggErr { 1236 #[inline] 1237 fn from(value: InvalidTimeout) -> Self { 1238 Self::InvalidTimeout(value) 1239 } 1240 } 1241 impl From<SecondFactorErr> for AggErr { 1242 #[inline] 1243 fn from(value: SecondFactorErr) -> Self { 1244 Self::SecondFactor(value) 1245 } 1246 } 1247 impl From<CreationOptionsErr> for AggErr { 1248 #[inline] 1249 fn from(value: CreationOptionsErr) -> Self { 1250 Self::CreationOptions(value) 1251 } 1252 } 1253 impl From<NicknameErr> for AggErr { 1254 #[inline] 1255 fn from(value: NicknameErr) -> Self { 1256 Self::Nickname(value) 1257 } 1258 } 1259 impl From<UsernameErr> for AggErr { 1260 #[inline] 1261 fn from(value: UsernameErr) -> Self { 1262 Self::Username(value) 1263 } 1264 } 1265 impl From<RegCeremonyErr> for AggErr { 1266 #[inline] 1267 fn from(value: RegCeremonyErr) -> Self { 1268 Self::RegCeremony(value) 1269 } 1270 } 1271 impl From<AuthCeremonyErr> for AggErr { 1272 #[inline] 1273 fn from(value: AuthCeremonyErr) -> Self { 1274 Self::AuthCeremony(value) 1275 } 1276 } 1277 impl From<AttestationObjectErr> for AggErr { 1278 #[inline] 1279 fn from(value: AttestationObjectErr) -> Self { 1280 Self::AttestationObject(value) 1281 } 1282 } 1283 impl From<RegAuthDataErr> for AggErr { 1284 #[inline] 1285 fn from(value: RegAuthDataErr) -> Self { 1286 Self::RegAuthenticatorData(value) 1287 } 1288 } 1289 impl From<AuthAuthDataErr> for AggErr { 1290 #[inline] 1291 fn from(value: AuthAuthDataErr) -> Self { 1292 Self::AuthAuthenticatorData(value) 1293 } 1294 } 1295 impl From<CollectedClientDataErr> for AggErr { 1296 #[inline] 1297 fn from(value: CollectedClientDataErr) -> Self { 1298 Self::CollectedClientData(value) 1299 } 1300 } 1301 #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] 1302 #[cfg(feature = "serde_relaxed")] 1303 impl From<SerdeJsonErr> for AggErr { 1304 #[inline] 1305 fn from(value: SerdeJsonErr) -> Self { 1306 Self::SerdeJson(value) 1307 } 1308 } 1309 impl From<AaguidErr> for AggErr { 1310 #[inline] 1311 fn from(value: AaguidErr) -> Self { 1312 Self::Aaguid(value) 1313 } 1314 } 1315 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1316 #[cfg(feature = "bin")] 1317 impl From<DecodeAuthTransportsErr> for AggErr { 1318 #[inline] 1319 fn from(value: DecodeAuthTransportsErr) -> Self { 1320 Self::DecodeAuthTransports(value) 1321 } 1322 } 1323 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1324 #[cfg(feature = "bin")] 1325 impl From<DecodeStaticStateErr> for AggErr { 1326 #[inline] 1327 fn from(value: DecodeStaticStateErr) -> Self { 1328 Self::DecodeStaticState(value) 1329 } 1330 } 1331 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1332 #[cfg(feature = "bin")] 1333 impl From<DecodeDynamicStateErr> for AggErr { 1334 #[inline] 1335 fn from(value: DecodeDynamicStateErr) -> Self { 1336 Self::DecodeDynamicState(value) 1337 } 1338 } 1339 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1340 #[cfg(feature = "bin")] 1341 impl From<DecodeNicknameErr> for AggErr { 1342 #[inline] 1343 fn from(value: DecodeNicknameErr) -> Self { 1344 Self::DecodeNickname(value) 1345 } 1346 } 1347 #[cfg_attr(docsrs, doc(cfg(feature = "bin")))] 1348 #[cfg(feature = "bin")] 1349 impl From<DecodeUsernameErr> for AggErr { 1350 #[inline] 1351 fn from(value: DecodeUsernameErr) -> Self { 1352 Self::DecodeUsername(value) 1353 } 1354 } 1355 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1356 #[cfg(feature = "serializable_server_state")] 1357 impl From<DecodeRegistrationServerStateErr> for AggErr { 1358 #[inline] 1359 fn from(value: DecodeRegistrationServerStateErr) -> Self { 1360 Self::DecodeRegistrationServerState(value) 1361 } 1362 } 1363 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1364 #[cfg(feature = "serializable_server_state")] 1365 impl From<DecodeDiscoverableAuthenticationServerStateErr> for AggErr { 1366 #[inline] 1367 fn from(value: DecodeDiscoverableAuthenticationServerStateErr) -> Self { 1368 Self::DecodeDiscoverableAuthenticationServerState(value) 1369 } 1370 } 1371 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1372 #[cfg(feature = "serializable_server_state")] 1373 impl From<DecodeNonDiscoverableAuthenticationServerStateErr> for AggErr { 1374 #[inline] 1375 fn from(value: DecodeNonDiscoverableAuthenticationServerStateErr) -> Self { 1376 Self::DecodeNonDiscoverableAuthenticationServerState(value) 1377 } 1378 } 1379 #[cfg_attr(docsrs, doc(cfg(feature = "serializable_server_state")))] 1380 #[cfg(feature = "serializable_server_state")] 1381 impl From<EncodeNonDiscoverableAuthenticationServerStateErr> for AggErr { 1382 #[inline] 1383 fn from(value: EncodeNonDiscoverableAuthenticationServerStateErr) -> Self { 1384 Self::EncodeNonDiscoverableAuthenticationServerState(value) 1385 } 1386 } 1387 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))] 1388 #[cfg(any(feature = "bin", feature = "custom"))] 1389 impl From<CredentialErr> for AggErr { 1390 #[inline] 1391 fn from(value: CredentialErr) -> Self { 1392 Self::Credential(value) 1393 } 1394 } 1395 #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))] 1396 #[cfg(any(feature = "bin", feature = "custom"))] 1397 impl From<CredentialIdErr> for AggErr { 1398 #[inline] 1399 fn from(value: CredentialIdErr) -> Self { 1400 Self::CredentialId(value) 1401 } 1402 } 1403 impl Display for AggErr { 1404 #[inline] 1405 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 1406 match *self { 1407 Self::AsciiDomain(err) => err.fmt(f), 1408 Self::Url(err) => err.fmt(f), 1409 Self::Scheme(err) => err.fmt(f), 1410 Self::DomainOrigin(ref err) => err.fmt(f), 1411 Self::Port(ref err) => err.fmt(f), 1412 Self::InvalidTimeout(err) => err.fmt(f), 1413 Self::SecondFactor(err) => err.fmt(f), 1414 Self::CreationOptions(err) => err.fmt(f), 1415 Self::Nickname(err) => err.fmt(f), 1416 Self::Username(err) => err.fmt(f), 1417 Self::RegCeremony(ref err) => err.fmt(f), 1418 Self::AuthCeremony(ref err) => err.fmt(f), 1419 Self::AttestationObject(err) => err.fmt(f), 1420 Self::RegAuthenticatorData(err) => err.fmt(f), 1421 Self::AuthAuthenticatorData(err) => err.fmt(f), 1422 Self::CollectedClientData(ref err) => err.fmt(f), 1423 #[cfg(feature = "serde_relaxed")] 1424 Self::SerdeJson(ref err) => err.fmt(f), 1425 Self::Aaguid(err) => err.fmt(f), 1426 #[cfg(feature = "bin")] 1427 Self::DecodeAuthTransports(err) => err.fmt(f), 1428 #[cfg(feature = "bin")] 1429 Self::DecodeStaticState(err) => err.fmt(f), 1430 #[cfg(feature = "bin")] 1431 Self::DecodeDynamicState(err) => err.fmt(f), 1432 #[cfg(feature = "bin")] 1433 Self::DecodeNickname(err) => err.fmt(f), 1434 #[cfg(feature = "bin")] 1435 Self::DecodeUsername(err) => err.fmt(f), 1436 #[cfg(feature = "serializable_server_state")] 1437 Self::DecodeRegistrationServerState(err) => err.fmt(f), 1438 #[cfg(feature = "serializable_server_state")] 1439 Self::DecodeDiscoverableAuthenticationServerState(err) => err.fmt(f), 1440 #[cfg(feature = "serializable_server_state")] 1441 Self::DecodeNonDiscoverableAuthenticationServerState(err) => err.fmt(f), 1442 #[cfg(feature = "serializable_server_state")] 1443 Self::EncodeRegistrationServerState(ref err) => err.fmt(f), 1444 #[cfg(feature = "serializable_server_state")] 1445 Self::EncodeDiscoverableAuthenticationServerState(ref err) => err.fmt(f), 1446 #[cfg(feature = "serializable_server_state")] 1447 Self::EncodeNonDiscoverableAuthenticationServerState(ref err) => err.fmt(f), 1448 #[cfg(any(feature = "bin", feature = "custom"))] 1449 Self::Credential(err) => err.fmt(f), 1450 #[cfg(any(feature = "bin", feature = "custom"))] 1451 Self::CredentialId(err) => err.fmt(f), 1452 } 1453 } 1454 } 1455 impl Error for AggErr {} 1456 /// Calculates the number of bytes needed to encode an input of length `n` bytes into base64url. 1457 /// 1458 /// `Some` is returned iff the encoded length does not exceed [`isize::MAX`]. 1459 #[expect( 1460 clippy::arithmetic_side_effects, 1461 clippy::as_conversions, 1462 clippy::cast_possible_wrap, 1463 clippy::cast_sign_loss, 1464 clippy::integer_division, 1465 clippy::integer_division_remainder_used, 1466 reason = "proof and comment justifies their correctness" 1467 )] 1468 const fn base64url_nopad_len(n: usize) -> Option<usize> { 1469 // 256^n is the number of distinct values of the input. Let the base64 encoding in a URL safe 1470 // way without padding of the input be O. There are 64 possible values each byte in O can be; thus we must find 1471 // the minimum nonnegative integer m such that: 1472 // 64^m = (2^6)^m = 2^(6m) >= 256^n = (2^8)^n = 2^(8n) 1473 // <==> 1474 // lg(2^(6m)) = 6m >= lg(2^(8n)) = 8n lg is defined on all positive reals which 2^(6m) and 2^(8n) are 1475 // <==> 1476 // m >= 8n/6 = 4n/3 1477 // Clearly that corresponds to m = ⌈4n/3⌉; thus: 1478 1479 let (quot, rem) = (n / 3, n % 3); 1480 // Actual construction of the encoded output requires the allocation to take no more than `isize::MAX` 1481 // bytes; thus we must detect overflow of it and not `usize::MAX`. 1482 // isize::MAX = usize::MAX / 2 >= usize::MAX / 3; thus this `as` conversion is lossless. 1483 match (quot as isize).checked_mul(4) { 1484 // If multiplying by 4 caused overflow, then multiplying by 4 and adding by 3 would also. 1485 None => None, 1486 // This won't overflow since this maxes at `isize::MAX` since 1487 // `n` <= ⌊3*isize::MAX/4⌋; thus `quot` <= ⌊isize::MAX/4⌋. 1488 // `n` can be partitioned into 4 possibilities: 1489 // (1) n ≡ 0 (mod 4) = 4quot + 0 1490 // (2) n ≡ 1 (mod 4) = 4quot + 1 1491 // (3) n ≡ 2 (mod 4) = 4quot + 2 1492 // (4) n ≡ 3 (mod 4) = 4quot + 3 1493 // For (1), rem is 0; thus 4quot + 0 = `n` which is fine. 1494 // For (2), rem is 1; thus 4quot + 2 = n - 1 + 2 = n + 1 <= ⌊3*isize::MAX/4⌋ + 1 <= isize::MAX for 1495 // isize::MAX > 0. Clearly `isize::MAX > 0`; otherwise we couldn't allocate anything. 1496 // For (3), rem is 2; thus 4quot + 3 = n - 2 + 3 = n + 1 <= ⌊3*isize::MAX/4⌋ + 1 <= isize::MAX for 1497 // isize::MAX > 0. Clearly `isize::MAX > 0`; otherwise we couldn't allocate anything. 1498 // For (4), rem is 3; thus 4quot + 4 = n - 3 + 4 = n + 1 <= ⌊3*isize::MAX/4⌋ + 1 <= isize::MAX for 1499 // isize::MAX > 0. Clearly `isize::MAX > 0`; otherwise we couldn't allocate anything. 1500 // 1501 // `val >= 0`; thus we can cast it to `usize` via `as` in a lossless way. 1502 // Thus this is free from overflow, underflow, and a lossy conversion. 1503 Some(val) => Some(val as usize + (4 * rem).div_ceil(3)), 1504 } 1505 } 1506 /// Calculates the number of bytes a base64url-encoded input of length `n` bytes will be decoded into. 1507 /// 1508 /// `Some` is returned iff `n` is a valid input length. 1509 /// 1510 /// Note `n` must not only be a valid length mathematically but also represent a possible allocation of that 1511 /// many bytes. Since only allocations <= [`isize::MAX`] are possible, this will always return `None` when 1512 /// `n > isize::MAX`. 1513 #[cfg(feature = "serde")] 1514 #[expect( 1515 clippy::arithmetic_side_effects, 1516 clippy::as_conversions, 1517 clippy::integer_division_remainder_used, 1518 reason = "proof and comment justifies their correctness" 1519 )] 1520 const fn base64url_nopad_decode_len(n: usize) -> Option<usize> { 1521 // 64^n is the number of distinct values of the input. Let the decoded output be O. 1522 // There are 256 possible values each byte in O can be; thus we must find 1523 // the maximum nonnegative integer m such that: 1524 // 256^m = (2^8)^m = 2^(8m) <= 64^n = (2^6)^n = 2^(6n) 1525 // <==> 1526 // lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n lg is defined on all positive reals which 2^(8m) and 2^(6n) are 1527 // <==> 1528 // m <= 6n/8 = 3n/4 1529 // Clearly that corresponds to m = ⌊3n/4⌋. 1530 // 1531 // There are three partitions for m: 1532 // (1) m ≡ 0 (mod 3) = 3i 1533 // (2) m ≡ 1 (mod 3) = 3i + 1 1534 // (3) m ≡ 2 (mod 3) = 3i + 2 1535 // 1536 // From `crate::base64url_nopad_len`, we know that the encoded length, n, of an input of length m is n = ⌈4m/3⌉. 1537 // The encoded length of (1) is thus n = ⌈4(3i)/3⌉ = ⌈4i⌉ = 4i ≡ 0 (mod 4). 1538 // The encoded length of (2) is thus n = ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + 2 ≡ 2 (mod 4). 1539 // The encoded length of (3) is thus n = ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + 3 ≡ 3 (mod 4). 1540 // 1541 // Thus if n ≡ 1 (mod 4), it is never a valid length. 1542 // 1543 // Let n be the length of a possible encoded output of an input of length m. 1544 // We know from above that n ≢ 1 (mod 4), this leaves three possibilities: 1545 // (1) n ≡ 0 (mod 4) = 4i 1546 // (2) n ≡ 2 (mod 4) = 4i + 2 1547 // (3) n ≡ 3 (mod 4) = 4i + 3 1548 // 1549 // For (1) an input of length 3i is the inverse since ⌈4(3i)/3⌉ = 4i. 1550 // For (2) an input of length 3i + 1 is the inverse since ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + 2. 1551 // For (3) an input of length 3i + 2 is the inverse since ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + 3. 1552 // 1553 // Consequently n is a valid length of an encoded output iff n ≢ 1 (mod 4). 1554 1555 // `isize::MAX >= 0 >= usize::MIN`; thus this conversion is lossless. 1556 if n % 4 == 1 || n > isize::MAX as usize { 1557 None 1558 } else { 1559 let (quot, rem) = (n >> 2u8, n % 4); 1560 // 4quot + rem = n 1561 // rem <= 3 1562 // 3rem <= 9 1563 // 3rem/4 <= 2 1564 // 3quot + 3rem/4 <= 4quot + rem 1565 // Thus no operation causes overflow or underflow. 1566 Some((3 * quot) + ((3 * rem) >> 2u8)) 1567 } 1568 }