webauthn_rp

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

README.md (26027B)


      1 # `webauthn_rp`
      2 
      3 [<img alt="git" src="https://git.philomathiclife.com/badges/webauthn_rp.svg" height="20">](https://git.philomathiclife.com/webauthn_rp/log.html)
      4 [<img alt="crates.io" src="https://img.shields.io/crates/v/webauthn_rp.svg?style=for-the-badge&color=fc8d62&logo=rust" height="20">](https://crates.io/crates/webauthn_rp)
      5 [<img alt="docs.rs" src="https://img.shields.io/badge/docs.rs-webauthn_rp-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs" height="20">](https://docs.rs/webauthn_rp/latest/webauthn_rp/)
      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 ```rust
     20 use webauthn_rp::{
     21     AuthenticatedCredential, DiscoverableAuthentication64, DiscoverableAuthenticationServerState,
     22     DiscoverableCredentialRequestOptions, PublicKeyCredentialCreationOptions, RegisteredCredential,
     23     Registration, RegistrationServerState,
     24     request::{
     25         AsciiDomain, PublicKeyCredentialDescriptor, RpId,
     26         auth::AuthenticationVerificationOptions,
     27         register::{
     28             Nickname, PublicKeyCredentialUserEntity, RegistrationVerificationOptions,
     29             USER_HANDLE_MAX_LEN, UserHandle64, Username,
     30         },
     31     },
     32     response::{
     33         CredentialId,
     34         auth::error::AuthCeremonyErr,
     35         register::{CompressedPubKey, DynamicState, error::RegCeremonyErr},
     36     },
     37 };
     38 // These are available iff `serializable_server_state` is _not_ enabled.
     39 use webauthn_rp::request::{FixedCapHashSet, InsertResult};
     40 use serde::de::{Deserialize, Deserializer};
     41 use serde_json::Error as JsonErr;
     42 /// The RP ID our application uses.
     43 const RP_ID: &str = "example.com";
     44 /// Error we return in our application when a function fails.
     45 enum AppErr {
     46     /// WebAuthn registration ceremony failed.
     47     RegCeremony(RegCeremonyErr),
     48     /// WebAuthn authentication ceremony failed.
     49     AuthCeremony(AuthCeremonyErr),
     50     /// Unable to insert a WebAuthn ceremony.
     51     WebAuthnCeremonyCreation,
     52     /// WebAuthn ceremony does not exist; thus the ceremony could not be completed.
     53     MissingWebAuthnCeremony,
     54     /// General error related to JSON deserialization.
     55     Json(JsonErr),
     56     /// No account exists associated with a particular `UserHandle64`.
     57     NoAccount,
     58     /// No credential exists associated with a particular `CredentialId`.
     59     NoCredential,
     60     /// `CredentialId` exists but the associated `UserHandle64` does not match.
     61     CredentialUserIdMismatch,
     62 }
     63 impl From<JsonErr> for AppErr {
     64     fn from(value: JsonErr) -> Self {
     65         Self::Json(value)
     66     }
     67 }
     68 impl From<RegCeremonyErr> for AppErr {
     69     fn from(value: RegCeremonyErr) -> Self {
     70         Self::RegCeremony(value)
     71     }
     72 }
     73 impl From<AuthCeremonyErr> for AppErr {
     74     fn from(value: AuthCeremonyErr) -> Self {
     75         Self::AuthCeremony(value)
     76     }
     77 }
     78 /// First-time account creation.
     79 ///
     80 /// This gets sent from the user after an account is created on their side. The registration ceremony
     81 /// still has to be successfully completed for the account to be created server side. In the event of an error,
     82 /// the user should delete the created passkey since it won't be usable.
     83 struct AccountReg<'a, 'b> {
     84     registration: Registration,
     85     user_name: Username<'a>,
     86     user_display_name: Nickname<'b>,
     87 }
     88 impl<'de: 'a + 'b, 'a, 'b> Deserialize<'de> for AccountReg<'a, 'b> {
     89     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     90     where
     91         D: Deserializer<'de>,
     92     {
     93         // ⋮
     94     }
     95 }
     96 /// Starts account creation.
     97 ///
     98 /// This only makes sense for greenfield deployments since account information (e.g., user name) would likely
     99 /// already exist otherwise. This is similar to credential creation except a random `UserHandle64` is generated and
    100 /// will be used for subsequent credential registrations.
    101 fn start_account_creation(
    102     reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState<USER_HANDLE_MAX_LEN>>,
    103 ) -> Result<Vec<u8>, AppErr> {
    104     let rp_id = RpId::Domain(
    105         AsciiDomain::try_from(RP_ID.to_owned())
    106             .unwrap_or_else(|_e| unreachable!("example.com is a valid domain")),
    107     );
    108     let user_id = UserHandle64::new();
    109     let (server, client) =
    110         PublicKeyCredentialCreationOptions::first_passkey_with_blank_user_info(
    111             &rp_id, &user_id,
    112         )
    113         .start_ceremony()
    114         .unwrap_or_else(|_e| {
    115             unreachable!("we don't manually mutate the options; thus this can't fail")
    116         });
    117     if matches!(
    118         reg_ceremonies.insert_or_replace_all_expired(server),
    119         InsertResult::Success
    120     ) {
    121         Ok(serde_json::to_vec(&client)
    122             .unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState::serialize")))
    123     } else {
    124         Err(AppErr::WebAuthnCeremonyCreation)
    125     }
    126 }
    127 /// Finishes account creation.
    128 ///
    129 /// Pending a successful registration ceremony, a new account associated with the randomly generated
    130 /// `UserHandle64` will be created with a corresponding passkey entry. This passkey will be used to
    131 /// log into the application.
    132 ///
    133 /// Note if this errors, then the user should be notified to delete the passkey created on their
    134 /// authenticator.
    135 fn finish_account_creation(
    136     reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState<USER_HANDLE_MAX_LEN>>,
    137     client_data: Vec<u8>,
    138 ) -> Result<(), AppErr> {
    139     let account = serde_json::from_slice::<AccountReg<'_, '_>>(client_data.as_slice())?;
    140     insert_account(
    141         &account,
    142         reg_ceremonies
    143             // `Registration::challenge_relaxed` is available iff `serde_relaxed` is enabled.
    144             .take(&account.registration.challenge_relaxed()?)
    145             .ok_or(AppErr::MissingWebAuthnCeremony)?
    146             .verify(
    147                 &RpId::Domain(
    148                     AsciiDomain::try_from(RP_ID.to_owned())
    149                         .unwrap_or_else(|_e| unreachable!("example.com is a valid domain")),
    150                 ),
    151                 &account.registration,
    152                 &RegistrationVerificationOptions::<&str, &str>::default(),
    153             )?,
    154     )
    155 }
    156 /// Starts passkey registration.
    157 ///
    158 /// This is used for _existing_ accounts where the user is already logged in and wants to register another
    159 /// passkey. This is similar to account creation except we already have the user entity info and we need to
    160 /// fetch the registered `PublicKeyCredentialDescriptor`s to avoid accidentally overwriting a passkey on
    161 /// the authenticator.
    162 fn start_cred_registration(
    163     user_id: &UserHandle64,
    164     reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState<USER_HANDLE_MAX_LEN>>,
    165 ) -> Result<Vec<u8>, AppErr> {
    166     let rp_id = RpId::Domain(
    167         AsciiDomain::try_from(RP_ID.to_owned())
    168             .unwrap_or_else(|_e| unreachable!("example.com is a valid domain")),
    169     );
    170     let (entity, creds) = select_user_info(user_id)?.ok_or_else(|| AppErr::NoAccount)?;
    171     let (server, client) = PublicKeyCredentialCreationOptions::passkey(&rp_id, entity, creds)
    172         .start_ceremony()
    173         .unwrap_or_else(|_e| {
    174             unreachable!("we don't manually mutate the options; thus this won't error")
    175         });
    176     if matches!(
    177         reg_ceremonies.insert_or_replace_all_expired(server),
    178         InsertResult::Success
    179     ) {
    180         Ok(serde_json::to_vec(&client)
    181             .unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState::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 fn finish_cred_registration(
    195     reg_ceremonies: &mut FixedCapHashSet<RegistrationServerState<USER_HANDLE_MAX_LEN>>,
    196     client_data: Vec<u8>,
    197 ) -> Result<(), AppErr> {
    198     // `Registration::from_json_custom` is available iff `serde_relaxed` is enabled.
    199     let registration = Registration::from_json_custom(client_data.as_slice())?;
    200     insert_credential(
    201         reg_ceremonies
    202             // `Registration::challenge_relaxed` is available iff `serde_relaxed` is enabled.
    203             .take(&registration.challenge_relaxed()?)
    204             .ok_or(AppErr::MissingWebAuthnCeremony)?
    205             .verify(
    206                 &RpId::Domain(
    207                     AsciiDomain::try_from(RP_ID.to_owned())
    208                         .unwrap_or_else(|_e| unreachable!("example.com is a valid domain")),
    209                 ),
    210                 &registration,
    211                 &RegistrationVerificationOptions::<&str, &str>::default(),
    212             )?,
    213     )
    214 }
    215 /// Starts the passkey authentication ceremony.
    216 fn start_auth(
    217     auth_ceremonies: &mut FixedCapHashSet<DiscoverableAuthenticationServerState>,
    218 ) -> Result<Vec<u8>, AppErr> {
    219     let rp_id = RpId::Domain(
    220         AsciiDomain::try_from(RP_ID.to_owned())
    221             .unwrap_or_else(|_e| unreachable!("example.com is a valid domain")),
    222     );
    223     let (server, client) = DiscoverableCredentialRequestOptions::passkey(&rp_id)
    224         .start_ceremony()
    225         .unwrap_or_else(|_e| {
    226             unreachable!("we don't manually mutate the options; thus this won't error")
    227         });
    228     if matches!(
    229         auth_ceremonies.insert_or_replace_all_expired(server),
    230         InsertResult::Success
    231     ) {
    232         Ok(serde_json::to_vec(&client).unwrap_or_else(|_e| {
    233             unreachable!("bug in DiscoverableAuthenticationClientState::serialize")
    234         }))
    235     } else {
    236         Err(AppErr::WebAuthnCeremonyCreation)
    237     }
    238 }
    239 /// Finishes the passkey authentication ceremony.
    240 fn finish_auth(
    241     auth_ceremonies: &mut FixedCapHashSet<DiscoverableAuthenticationServerState>,
    242     client_data: Vec<u8>,
    243 ) -> Result<(), AppErr> {
    244     // `Authentication::from_json_custom` is available iff `serde_relaxed` is enabled.
    245     let authentication =
    246         DiscoverableAuthentication64::from_json_custom(client_data.as_slice())?;
    247     let mut cred = select_credential(
    248         authentication.raw_id(),
    249         authentication.response().user_handle(),
    250     )?
    251     .ok_or_else(|| AppErr::NoCredential)?;
    252     if auth_ceremonies
    253         // `Authentication::challenge_relaxed` is available iff `serde_relaxed` is enabled.
    254         .take(&authentication.challenge_relaxed()?)
    255         .ok_or(AppErr::MissingWebAuthnCeremony)?
    256         .verify(
    257             &RpId::Domain(
    258                 AsciiDomain::try_from(RP_ID.to_owned())
    259                     .unwrap_or_else(|_e| unreachable!("example.com is a valid domain")),
    260             ),
    261             &authentication,
    262             &mut cred,
    263             &AuthenticationVerificationOptions::<&str, &str>::default(),
    264         )?
    265     {
    266         update_credential(cred.id(), cred.dynamic_state())
    267     } else {
    268         Ok(())
    269     }
    270 }
    271 /// Writes `account` and `cred` to storage.
    272 ///
    273 /// # Errors
    274 ///
    275 /// Errors iff writing `account` or `cred` errors,  there already exists a credential using the same
    276 /// `CredentialId`, or there already exists an account using the same `UserHandle64`.
    277 fn insert_account(
    278     account: &AccountReg<'_, '_>,
    279     cred: RegisteredCredential<'_, USER_HANDLE_MAX_LEN>,
    280 ) -> Result<(), AppErr> {
    281     // ⋮
    282 }
    283 /// Fetches the user info and registered credentials associated with `user_id`.
    284 ///
    285 /// # Errors
    286 ///
    287 /// Errors iff fetching the data errors.
    288 fn select_user_info<'a>(
    289     user_id: &'a UserHandle64,
    290 ) -> Result<
    291     Option<(
    292         PublicKeyCredentialUserEntity<'static, 'static, 'a, USER_HANDLE_MAX_LEN>,
    293         Vec<PublicKeyCredentialDescriptor<Vec<u8>>>,
    294     )>,
    295     AppErr,
    296 > {
    297     // ⋮
    298 }
    299 /// Writes `cred` to storage.
    300 ///
    301 /// # Errors
    302 ///
    303 /// Errors iff writing `cred` errors or there already exists a credential using the same `CredentialId`.
    304 fn insert_credential(
    305     cred: RegisteredCredential<'_, USER_HANDLE_MAX_LEN>,
    306 ) -> Result<(), AppErr> {
    307     // ⋮
    308 }
    309 /// Fetches the `AuthenticatedCredential` associated with `cred_id` ensuring `user_id` matches the
    310 /// `UserHandle64` associated with the account.
    311 ///
    312 /// # Errors
    313 ///
    314 /// Errors iff fetching the data errors or the `user_id` does not match the stored `UserHandle64`.
    315 fn select_credential<'cred, 'user>(
    316     cred_id: CredentialId<&'cred [u8]>,
    317     user_id: &'user UserHandle64,
    318 ) -> Result<
    319     Option<
    320         AuthenticatedCredential<
    321             'cred,
    322             'user,
    323             USER_HANDLE_MAX_LEN,
    324             CompressedPubKey<[u8; 32], [u8; 32], [u8; 48], Vec<u8>>,
    325         >,
    326     >,
    327     AppErr,
    328 > {
    329     // ⋮
    330 }
    331 /// Overwrites the current `DynamicState` associated with `cred_id` with `dynamic_state`.
    332 ///
    333 /// # Errors
    334 ///
    335 /// Errors iff writing errors or `cred_id` does not exist.
    336 fn update_credential(
    337     cred_id: CredentialId<&[u8]>,
    338     dynamic_state: DynamicState,
    339 ) -> Result<(), AppErr> {
    340     // ⋮
    341 }
    342 ```
    343 
    344 ## Cargo "features"
    345 
    346 [`custom`](#custom) or both [`bin`](#bin) and [`serde`](#serde) must be enabled; otherwise a `compile_error`
    347  will occur.
    348 
    349 ### `bin`
    350 
    351 Enables binary (de)serialization via `Encode` and `Decode`. Since registered credentials will almost always
    352 have to be saved to persistent storage, _some_ form of (de)serialization is necessary. In the event `bin` is
    353 unsuitable or only partially suitable (e.g., human-readable output is desired), one will need to enable
    354 [`custom`](#custom) to allow construction of certain types (e.g., `AuthenticatedCredential`).
    355 
    356 If possible and desired, one may wish to save the data "directly" to avoid any potential temporary allocations.
    357 For example `StaticState::encode` will return a `Vec` containing hundreds (and possibly thousands in the
    358 extreme case) of bytes if the underlying public key is an RSA key. This additional allocation and copy of data
    359 is obviously avoided if `StaticState` is stored as a
    360 [composite type](https://www.postgresql.org/docs/current/rowtypes.html) or its fields are stored in separate
    361 columns when written to a relational database (RDB).
    362 
    363 ### `custom`
    364 
    365 Exposes functions (e.g., `AuthenticatedCredential::new`) that allows one to construct instances of types that
    366 cannot be constructed when [`bin`](#bin) or [`serde`](#serde) is not enabled.
    367 
    368 ### `serde`
    369 
    370 This feature _strictly_ adheres to the JSON-motivated definitions. You _will_ encounter clients that send data that
    371 cannot be deserialized using this feature. For many [`serde_relaxed`](#serde_relaxed) should be used instead.
    372 
    373 Enables (de)serialization of data sent to/from the client via [`serde`](https://docs.rs/serde/latest/serde/)
    374 based on the JSON-motivated definitions (e.g.,
    375 [`RegistrationResponseJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-registrationresponsejson)). Since
    376 data has to be sent to/from the client, _some_ form of (de)serialization is necessary. In the event `serde`
    377 is unsuitable or only partially suitable, one will need to enable [`custom`](#custom) to allow construction
    378 of certain types (e.g., `Registration`).
    379 
    380 Code is _strongly_ encouraged to rely on the `Deserialize` implementations as much as possible to reduce the
    381 chances of improperly deserializing the client data.
    382 
    383 Note that clients are free to send data in whatever form works best, so there is no requirement the
    384 JSON-motivated definitions are used even when JSON is sent. This is especially relevant since the JSON-motivated
    385 definitions were only added in [WebAuthn Level 3](https://www.w3.org/TR/webauthn-3/); thus many deployments only
    386 partially conform. Some specific deviations that may require partial customization of deserialization are the
    387 following:
    388 
    389 * [`ArrayBuffer`](https://webidl.spec.whatwg.org/#idl-ArrayBuffer)s encoded using something other than
    390   base64url.
    391 * `ArrayBuffer`s that are encoded multiple times (including the use of different encodings each time).
    392 * Missing fields (e.g.,
    393   [`transports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponsejson-transports)).
    394 * Different field names (e.g., `extensions` instead of
    395   [`clientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-registrationresponsejson-clientextensionresults)).
    396 
    397 ### `serde_relaxed`
    398 
    399 Automatically enables [`serde`](#serde) in addition to "relaxed" `Deserialize` implementations
    400 (e.g., `RegistrationRelaxed`). Roughly "relaxed" translates to unknown fields being ignored and only
    401 the fields necessary for construction of the type are required. Case still matters, duplicate fields are still
    402 forbidden, and interrelated data validation is still performed when applicable. This can be useful when one
    403 wants to accommodate non-conforming clients or clients that implement older versions of the spec.
    404 
    405 ### `serializable_server_state`
    406 
    407 Automatically enables [`bin`](#bin) in addition to `Encode` and `Decode` implementations for
    408 `RegistrationServerState` and `AuthenticationServerState`. Less accurate `SystemTime` is used instead of
    409 `Instant` for timeout enforcement. This should be enabled if you don't desire to use in-memory collections to
    410 store the instances of those types.
    411 
    412 Note even when written to persistent storage, an application should still periodically remove expired ceremonies.
    413 If one is using a relational database (RDB); then one can achieve this by storing `ServerState::sent_challenge`,
    414 the `Vec` returned from `Encode::encode`, and `ServerState::expiration` and periodically remove all rows
    415 whose expiration exceeds the current date and time.
    416 
    417 ## Registration and authentication
    418 
    419 Both [registration](https://www.w3.org/TR/webauthn-3/#registration-ceremony) and
    420 [authentication](https://www.w3.org/TR/webauthn-3/#authentication-ceremony) ceremonies rely on "challenges", and
    421 these challenges are inherently temporary. For this reason the data associated with challenge completion can
    422 often be stored in memory without concern for out-of-memory (OOM) conditions. There are several benefits to
    423 storing such data in memory:
    424 
    425 * No data manipulation
    426     * By leveraging move semantics, the data sent to the client cannot be mutated once the ceremony begins.
    427 * Improved timeout enforcement
    428     * By ensuring the same machine that started the ceremony is also used to finish the ceremony, deviation of
    429       system clocks is not a concern. Additionally, allowing serialization requires the use of some form of
    430       cross-platform "timestamp" (e.g., [Unix time](https://en.wikipedia.org/wiki/Unix_time)) which differ in
    431       implementation (e.g., platforms implement leap seconds in different ways) and are often not monotonically
    432       increasing. If data resides in memory, a monotonic `Instant` can be used instead.
    433 
    434 It is for those reasons data like `RegistrationServerState` are not serializable by default and require the
    435 use of in-memory collections (e.g., `FixedCapHashSet`). To better ensure OOM is not a concern, RPs should set
    436 reasonable timeouts. Since ceremonies can only be completed by moving data (e.g.,
    437 `RegistrationServerState::verify`), ceremony completion is guaranteed to free up the memory used—
    438 `RegistrationServerState` instances are only 48 bytes on `x86_64-unknown-linux-gnu` platforms. To avoid issues
    439 related to incomplete ceremonies, RPs can periodically iterate the collection for expired ceremonies and remove
    440 such data. Other techniques can be employed as well to mitigate OOM, but they are application specific and
    441 out-of-scope. If this is undesirable, one can enable [`serializable_server_state`](#serializable_server_state)
    442 so that `RegistrationServerState` and `AuthenticationServerState` implement `Encode` and `Decode`. Another
    443 reason one may need to store this information persistently is for load-balancing purposes where the server that
    444 started the ceremony is not guaranteed to be the server that finishes the ceremony.
    445 
    446 ## Supported signature algorithms
    447 
    448 The only supported signature algorithms are the following:
    449 
    450 * Ed25519 as defined in [RFC 8032 § 5.1](https://www.rfc-editor.org/rfc/rfc8032#section-5.1). This corresponds
    451   to `CoseAlgorithmIdentifier::Eddsa`.
    452 * ECDSA as defined in [SEC 1 Version 2.0 § 4.1](https://www.secg.org/sec1-v2.pdf#subsection.4.1) using SHA-256
    453   as the hash function and NIST P-256 as defined in
    454   [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)
    455   for the underlying elliptic curve. This corresponds to `CoseAlgorithmIdentifier::Es256`.
    456 * 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
    457   [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)
    458   for the underlying elliptic curve. This corresponds to `CoseAlgorithmIdentifier::Es384`.
    459 * RSASSA-PKCS1-v1_5 as defined in [RFC 8017 § 8.2](https://www.rfc-editor.org/rfc/rfc8017#section-8.2) using
    460   SHA-256 as the hash function. This corresponds to `CoseAlgorithmIdentifier::Rs256`.
    461 
    462 ## Correctness of code
    463 
    464 This library more strictly adheres to the spec than many other similar libraries including but not limited to
    465 the following ways:
    466 
    467 * [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).
    468 * `Deserialize` implementations requiring _exact_ conformance (e.g., not allowing unknown data).
    469 * More thorough interrelated data validation (e.g., all places a Credential ID exists must match).
    470 * Implement a lot of recommended (i.e., SHOULD) criteria (e.g.,
    471   [User display names conforming to the Nickname Profile as defined in RFC 8266](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name)).
    472 
    473 Unfortunately like almost all software, this library has not been formally verified; however great care is
    474 employed in the following ways:
    475 
    476 * Leverage move semantics to prevent mutation of data once in a static state.
    477 * Ensure a great many invariants via types.
    478 * Reduce code duplication.
    479 * Reduce variable mutation allowing for simpler algebraic reasoning.
    480 * `panic`-free code[^note] (i.e., define true/total functions).
    481 * Ensure arithmetic "side effects" don't occur (e.g., overflow).
    482 * Aggressive use of compiler and [Clippy](https://doc.rust-lang.org/stable/clippy/lints.html) lints.
    483 * Unit tests for common cases, edge cases, and error cases.
    484 
    485 ## Cryptographic libraries
    486 
    487 This library does not rely on _any_ sensitive data (e.g., private keys) as only signature verification is
    488 ever performed. This means that the only thing that matters with the libraries used is their algorithmic
    489 correctness and not other normally essential aspects like susceptibility to side-channel attacks. While I
    490 personally believe the libraries that are used are at least as "secure" as alternatives even when dealing with
    491 sensitive data, one only needs to audit the correctness of the libraries to be confident in their use. In fact
    492 [`curve25519_dalek`](https://docs.rs/curve25519-dalek/latest/curve25519_dalek/#backends) has been formally
    493 verified when the [`fiat`](https://github.com/mit-plv/fiat-crypto) backend is used making it _objectively_
    494 better than many other libraries whose correctness has not been proven. Two additional benefits of the library
    495 choices are simpler APIs making it more likely their use is correct and better cross-platform compatibility.
    496 
    497 ## Minimum Supported Rust Version (MSRV)
    498 
    499 This will frequently be updated to be the same as stable. Specifically, any time stable is updated and that
    500 update has "useful" features or compilation no longer succeeds (e.g., due to new compiler lints), then MSRV
    501 will be updated.
    502 
    503 MSRV changes will correspond to a SemVer patch version bump pre-`1.0.0`; otherwise a minor version bump.
    504 
    505 ## SemVer Policy
    506 
    507 * All on-by-default features of this library are covered by SemVer
    508 * MSRV is considered exempt from SemVer as noted above
    509 
    510 ## License
    511 
    512 Licensed under either of
    513 
    514 * Apache License, Version 2.0 ([LICENSE-APACHE](https://www.apache.org/licenses/LICENSE-2.0))
    515 * MIT license ([LICENSE-MIT](https://opensource.org/licenses/MIT))
    516 
    517 at your option.
    518 
    519 ## Contribution
    520 
    521 Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you,
    522 as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
    523 
    524 Before any PR is sent, `cargo clippy` and `cargo t` should be run _for each possible combination of "features"_
    525 using stable Rust. One easy way to achieve this is by building `ci` and invoking it with no commands in the
    526 `webauthn_rp` directory or sub-directories. You can fetch `ci` via `git clone https://git.philomathiclife.com/repos/ci`,
    527 and it can be built with `cargo build --release`. Additionally,
    528 `RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features` should be run to ensure documentation can be built.
    529 
    530 ### Status
    531 
    532 This package is actively maintained and will conform to the
    533 [latest WebAuthn API version](https://www.w3.org/TR/webauthn-3/). Previous versions will not be supported—excluding
    534 bug fixes of course—however functionality will exist to facilitate the migration process from the previous version.
    535 
    536 The crate is only tested on `x86_64-unknown-linux-gnu` and `x86_64-unknown-openbsd` targets, but it should work
    537 on most platforms.
    538 
    539 [^note]: `panic`s related to memory allocations or stack overflow are possible since such issues are not
    540          formally guarded against.