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