webauthn_rp

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

register.rs (182231B)


      1 #[cfg(feature = "serde_relaxed")]
      2 use self::{
      3     super::ser_relaxed::{RelaxedClientDataJsonParser, SerdeJsonErr},
      4     ser_relaxed::{CustomRegistration, RegistrationRelaxed},
      5 };
      6 #[cfg(all(doc, feature = "bin"))]
      7 use super::super::bin::{Decode, Encode};
      8 #[cfg(feature = "bin")]
      9 use super::register::bin::{AaguidOwned, MetadataOwned};
     10 use super::{
     11     super::request::register::{FourToSixtyThree, ResidentKeyRequirement},
     12     AuthData, AuthDataContainer, AuthExtOutput, AuthRespErr, AuthResponse, AuthTransports,
     13     AuthenticatorAttachment, Backup, CborSuccess, ClientDataJsonParser as _, CollectedClientData,
     14     CredentialId, Flag, FromCbor, HmacSecretGet, HmacSecretGetErr, LimitedVerificationParser,
     15     ParsedAuthData, Response, SentChallenge, cbor,
     16     error::CollectedClientDataErr,
     17     register::error::{
     18         AaguidErr, AttestationErr, AttestationObjectErr, AttestedCredentialDataErr,
     19         AuthenticatorDataErr, AuthenticatorExtensionOutputErr, CompressedP256PubKeyErr,
     20         CompressedP384PubKeyErr, CoseKeyErr, Ed25519PubKeyErr, Ed25519SignatureErr, PubKeyErr,
     21         RsaPubKeyErr, UncompressedP256PubKeyErr, UncompressedP384PubKeyErr,
     22     },
     23 };
     24 #[cfg(doc)]
     25 use super::{
     26     super::{
     27         AuthenticatedCredential, RegisteredCredential,
     28         hash::hash_set::MaxLenHashSet,
     29         request::{
     30             BackupReq, Challenge, UserVerificationRequirement,
     31             auth::{AuthenticationVerificationOptions, PublicKeyCredentialRequestOptions},
     32             register::{CoseAlgorithmIdentifier, Extension, RegistrationServerState},
     33         },
     34     },
     35     AuthenticatorTransport,
     36 };
     37 use core::{
     38     cmp::Ordering,
     39     convert::Infallible,
     40     fmt::{self, Display, Formatter},
     41 };
     42 use ed25519_dalek::{Signature, Verifier as _, VerifyingKey};
     43 use p256::{
     44     AffinePoint as P256Affine, EncodedPoint as P256Pt, NistP256,
     45     ecdsa::{DerSignature as P256Sig, VerifyingKey as P256VerKey},
     46     elliptic_curve::{Curve, generic_array::typenum::ToInt as _, point::DecompressPoint as _},
     47 };
     48 use p384::{
     49     AffinePoint as P384Affine, EncodedPoint as P384Pt, NistP384,
     50     ecdsa::{DerSignature as P384Sig, VerifyingKey as P384VerKey},
     51 };
     52 use rsa::{
     53     BigUint, RsaPublicKey,
     54     pkcs1v15::{self, VerifyingKey as RsaVerKey},
     55     sha2::{Sha256, digest::Digest as _},
     56 };
     57 #[cfg(all(doc, feature = "serde_relaxed"))]
     58 use serde::Deserialize;
     59 /// Contains functionality to (de)serialize data to a data store.
     60 #[cfg(feature = "bin")]
     61 pub mod bin;
     62 /// Contains error types.
     63 pub mod error;
     64 /// Contains functionality to deserialize data from a client.
     65 #[cfg(feature = "serde")]
     66 mod ser;
     67 /// Contains functionality to deserialize data from a client in a "relaxed" way.
     68 #[cfg(feature = "serde_relaxed")]
     69 pub mod ser_relaxed;
     70 /// [`credentialProtectionPolicy`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#dom-authenticationextensionsclientinputs-credentialprotectionpolicy).
     71 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
     72 pub enum CredentialProtectionPolicy {
     73     /// `credProtect` was not sent.
     74     None,
     75     /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional).
     76     UserVerificationOptional,
     77     /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist).
     78     UserVerificationOptionalWithCredentialIdList,
     79     /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired).
     80     UserVerificationRequired,
     81 }
     82 impl Display for CredentialProtectionPolicy {
     83     #[inline]
     84     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     85         f.write_str(match *self {
     86             Self::None => "no credential protection policy sent",
     87             Self::UserVerificationOptional => "user verification optional",
     88             Self::UserVerificationOptionalWithCredentialIdList => {
     89                 "user verification optional with credential ID list"
     90             }
     91             Self::UserVerificationRequired => "user verification required",
     92         })
     93     }
     94 }
     95 #[expect(clippy::too_long_first_doc_paragraph, reason = "false positive")]
     96 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension)
     97 /// and
     98 /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension).
     99 ///
    100 /// Note `hmac-secret-mc` can only exist if `hmac-secret` exists with a value of `true`.
    101 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
    102 pub enum HmacSecret {
    103     /// No `hmac-secret` extension.
    104     None,
    105     /// `hmac-secret` extension with a value of `false`.
    106     NotEnabled,
    107     /// `hmac-secret` extension with a value of `true`.
    108     Enabled,
    109     /// `hmac-secret` extension with a value of `true` and `hmac-secret-mc` contained one encrypted PRF output.
    110     One,
    111     /// `hmac-secret` extension with a value of `true` and `hmac-secret-mc` contained two encrypted PRF outputs.
    112     Two,
    113 }
    114 /// [Authenticator extension output](https://www.w3.org/TR/webauthn-3/#authenticator-extension-output).
    115 #[derive(Clone, Copy, Debug)]
    116 pub struct AuthenticatorExtensionOutput {
    117     /// [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension).
    118     pub cred_protect: CredentialProtectionPolicy,
    119     /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension)
    120     /// and
    121     /// [`hmac-secret-mc`](https://fidoalliance.org/specs/fido-v2.2-ps-20250228/fido-client-to-authenticator-protocol-v2.2-ps-20250228.html#sctn-hmac-secret-make-cred-extension).
    122     pub hmac_secret: HmacSecret,
    123     /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension).
    124     pub min_pin_length: Option<FourToSixtyThree>,
    125 }
    126 impl AuthExtOutput for AuthenticatorExtensionOutput {
    127     fn missing(self) -> bool {
    128         matches!(self.cred_protect, CredentialProtectionPolicy::None)
    129             && matches!(self.hmac_secret, HmacSecret::None)
    130             && self.min_pin_length.is_none()
    131     }
    132 }
    133 /// [`AuthenticatorExtensionOutput`] extensions that are saved in [`StaticState`] because they are used during
    134 /// authentication ceremonies.
    135 #[derive(Clone, Copy, Debug)]
    136 pub struct AuthenticatorExtensionOutputStaticState {
    137     /// [`AuthenticatorExtensionOutput::cred_protect`].
    138     pub cred_protect: CredentialProtectionPolicy,
    139     /// [`AuthenticatorExtensionOutput::hmac_secret`].
    140     ///
    141     /// Note we only care about whether or not it has been enabled. Specifcally this is `None` iff
    142     /// [`HmacSecret::None`], `Some(false)` iff [`HmacSecret::NotEnabled`]; otherwise `Some(true)`.
    143     pub hmac_secret: Option<bool>,
    144 }
    145 /// [`AuthenticatorExtensionOutput`] extensions that are saved in [`Metadata`] because they are purely informative
    146 /// and not used during authentication ceremonies.
    147 #[derive(Clone, Copy, Debug)]
    148 pub struct AuthenticatorExtensionOutputMetadata {
    149     /// [`AuthenticatorExtensionOutput::min_pin_length`].
    150     pub min_pin_length: Option<FourToSixtyThree>,
    151 }
    152 impl From<AuthenticatorExtensionOutput> for AuthenticatorExtensionOutputMetadata {
    153     #[inline]
    154     fn from(value: AuthenticatorExtensionOutput) -> Self {
    155         Self {
    156             min_pin_length: value.min_pin_length,
    157         }
    158     }
    159 }
    160 impl From<AuthenticatorExtensionOutput> for AuthenticatorExtensionOutputStaticState {
    161     #[inline]
    162     fn from(value: AuthenticatorExtensionOutput) -> Self {
    163         Self {
    164             cred_protect: value.cred_protect,
    165             hmac_secret: match value.hmac_secret {
    166                 HmacSecret::None => None,
    167                 HmacSecret::NotEnabled => Some(false),
    168                 HmacSecret::Enabled | HmacSecret::One | HmacSecret::Two => Some(true),
    169             },
    170         }
    171     }
    172 }
    173 impl FromCbor<'_> for CredentialProtectionPolicy {
    174     type Err = AuthenticatorExtensionOutputErr;
    175     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    176         /// [`userVerificationOptional`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptional).
    177         const USER_VERIFICATION_OPTIONAL: u8 = cbor::ONE;
    178         /// [`userVerificationOptionalWithCredentialIDList`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationoptionalwithcredentialidlist).
    179         const USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST: u8 = cbor::TWO;
    180         /// [`userVerificationRequired`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#userverificationrequired).
    181         const USER_VERIFICATION_REQUIRED: u8 = cbor::THREE;
    182         /// `credProtect` key.
    183         const KEY: [u8; 12] = [
    184             cbor::TEXT_11,
    185             b'c',
    186             b'r',
    187             b'e',
    188             b'd',
    189             b'P',
    190             b'r',
    191             b'o',
    192             b't',
    193             b'e',
    194             b'c',
    195             b't',
    196         ];
    197         cbor.split_at_checked(KEY.len()).map_or(
    198             Ok(CborSuccess {
    199                 value: Self::None,
    200                 remaining: cbor,
    201             }),
    202             |(key, key_rem)| {
    203                 if key == KEY {
    204                     key_rem
    205                         .split_first()
    206                         .ok_or(AuthenticatorExtensionOutputErr::Len)
    207                         .and_then(|(uv, remaining)| {
    208                             match *uv {
    209                                 USER_VERIFICATION_OPTIONAL => Ok(Self::UserVerificationOptional),
    210                                 USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST => {
    211                                     Ok(Self::UserVerificationOptionalWithCredentialIdList)
    212                                 }
    213                                 USER_VERIFICATION_REQUIRED => Ok(Self::UserVerificationRequired),
    214                                 _ => Err(AuthenticatorExtensionOutputErr::CredProtectValue),
    215                             }
    216                             .map(|value| CborSuccess { value, remaining })
    217                         })
    218                 } else {
    219                     Ok(CborSuccess {
    220                         value: Self::None,
    221                         remaining: cbor,
    222                     })
    223                 }
    224             },
    225         )
    226     }
    227 }
    228 /// [`hmac-secret`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-hmac-secret-extension).
    229 enum HmacSecretEnabled {
    230     /// No `hmac-secret` extension.
    231     None,
    232     /// `hmac-secret` set to the contained `bool`.
    233     Val(bool),
    234 }
    235 impl FromCbor<'_> for HmacSecretEnabled {
    236     type Err = AuthenticatorExtensionOutputErr;
    237     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    238         cbor.split_at_checked(cbor::HMAC_SECRET.len()).map_or(
    239             Ok(CborSuccess {
    240                 value: Self::None,
    241                 remaining: cbor,
    242             }),
    243             |(key, key_rem)| {
    244                 if key == cbor::HMAC_SECRET {
    245                     key_rem
    246                         .split_first()
    247                         .ok_or(AuthenticatorExtensionOutputErr::Len)
    248                         .and_then(|(hmac, remaining)| {
    249                             match *hmac {
    250                                 cbor::SIMPLE_FALSE => Ok(Self::Val(false)),
    251                                 cbor::SIMPLE_TRUE => Ok(Self::Val(true)),
    252                                 _ => Err(AuthenticatorExtensionOutputErr::HmacSecretValue),
    253                             }
    254                             .map(|value| CborSuccess { value, remaining })
    255                         })
    256                 } else {
    257                     Ok(CborSuccess {
    258                         value: Self::None,
    259                         remaining: cbor,
    260                     })
    261                 }
    262             },
    263         )
    264     }
    265 }
    266 /// [`minPinLength`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension).
    267 enum MinPinLength {
    268     /// No `minPinLength` extension.
    269     None,
    270     /// `minPinLength` with the value of the contained `FourToSixtyThree`.
    271     Val(FourToSixtyThree),
    272 }
    273 impl FromCbor<'_> for MinPinLength {
    274     type Err = AuthenticatorExtensionOutputErr;
    275     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    276         /// `minPinLength` key.
    277         const KEY: [u8; 13] = [
    278             cbor::TEXT_12,
    279             b'm',
    280             b'i',
    281             b'n',
    282             b'P',
    283             b'i',
    284             b'n',
    285             b'L',
    286             b'e',
    287             b'n',
    288             b'g',
    289             b't',
    290             b'h',
    291         ];
    292         cbor.split_at_checked(KEY.len()).map_or(
    293             Ok(CborSuccess {
    294                 value: Self::None,
    295                 remaining: cbor,
    296             }),
    297             |(key, key_rem)| {
    298                 if key == KEY {
    299                     key_rem
    300                         .split_first()
    301                         .ok_or(AuthenticatorExtensionOutputErr::Len)
    302                         .and_then(|(&key_len, remaining)| match key_len.cmp(&24) {
    303                             Ordering::Less => FourToSixtyThree::from_u8(key_len)
    304                                 .ok_or(AuthenticatorExtensionOutputErr::MinPinLengthValue)
    305                                 .map(|val| CborSuccess {
    306                                     value: Self::Val(val),
    307                                     remaining,
    308                                 }),
    309                             Ordering::Equal => remaining
    310                                 .split_first()
    311                                 .ok_or(AuthenticatorExtensionOutputErr::Len)
    312                                 .and_then(|(&key_24, rem)| {
    313                                     if key_24 > 23 {
    314                                         FourToSixtyThree::from_u8(key_24)
    315                                             .ok_or(
    316                                                 AuthenticatorExtensionOutputErr::MinPinLengthValue,
    317                                             )
    318                                             .map(|val| CborSuccess {
    319                                                 value: Self::Val(val),
    320                                                 remaining: rem,
    321                                             })
    322                                     } else {
    323                                         Err(AuthenticatorExtensionOutputErr::MinPinLengthValue)
    324                                     }
    325                                 }),
    326                             Ordering::Greater => {
    327                                 Err(AuthenticatorExtensionOutputErr::MinPinLengthValue)
    328                             }
    329                         })
    330                 } else {
    331                     Ok(CborSuccess {
    332                         value: Self::None,
    333                         remaining: cbor,
    334                     })
    335                 }
    336             },
    337         )
    338     }
    339 }
    340 impl From<HmacSecretGetErr> for AuthenticatorExtensionOutputErr {
    341     #[inline]
    342     fn from(value: HmacSecretGetErr) -> Self {
    343         match value {
    344             HmacSecretGetErr::Len => Self::Len,
    345             HmacSecretGetErr::Type => Self::HmacSecretMcType,
    346             HmacSecretGetErr::Value => Self::HmacSecretMcValue,
    347         }
    348     }
    349 }
    350 impl FromCbor<'_> for AuthenticatorExtensionOutput {
    351     type Err = AuthenticatorExtensionOutputErr;
    352     #[expect(
    353         clippy::too_many_lines,
    354         reason = "don't want to move logic into outer scope"
    355     )]
    356     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
    357         // We don't allow unsupported extensions; thus the only possibilities is any ordered element of
    358         // the power set of {"credProtect":<1, 2, or 3>, "hmac-secret":<true or false>, "minPinLength":<0-255>, "hmac-secret-mc":<48|80 bytes>}.
    359         // Since the keys are the same type (text), order is first done based on length; and then
    360         // byte-wise lexical order is followed; thus `credProtect` must come before `hmac-secret` which
    361         // must come before `minPinLength` which comes before `hmac-secret-mc`.
    362         //
    363         // Note `hmac-secret-mc` can only exist if `hmac-secret` exists with a value of `true`.
    364         cbor.split_first().map_or_else(
    365             || {
    366                 Ok(CborSuccess {
    367                     value: Self {
    368                         cred_protect: CredentialProtectionPolicy::None,
    369                         hmac_secret: HmacSecret::None,
    370                         min_pin_length: None,
    371                     },
    372                     remaining: cbor,
    373                 })
    374             },
    375             |(map, map_rem)| match *map {
    376                 cbor::MAP_1 => {
    377                     CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    378                         if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    379                             HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    380                                 |hmac_success| match hmac_success.value {
    381                                     HmacSecretEnabled::None => MinPinLength::from_cbor(
    382                                         hmac_success.remaining,
    383                                     )
    384                                     .and_then(|pin_success| match pin_success.value {
    385                                         MinPinLength::None => {
    386                                             // We don't even bother checking for `HmacSecretGet` since
    387                                             // it's only valid when `HmacSecretEnabled` exists with a value
    388                                             // of `true`.
    389                                             Err(AuthenticatorExtensionOutputErr::Missing)
    390                                         }
    391                                         MinPinLength::Val(min_pin_len) => Ok(CborSuccess {
    392                                             value: Self {
    393                                                 cred_protect: cred_success.value,
    394                                                 hmac_secret: HmacSecret::None,
    395                                                 min_pin_length: Some(min_pin_len),
    396                                             },
    397                                             remaining: pin_success.remaining,
    398                                         }),
    399                                     }),
    400                                     HmacSecretEnabled::Val(hmac) => Ok(CborSuccess {
    401                                         value: Self {
    402                                             cred_protect: cred_success.value,
    403                                             hmac_secret: if hmac {
    404                                                 HmacSecret::Enabled
    405                                             } else {
    406                                                 HmacSecret::NotEnabled
    407                                             },
    408                                             min_pin_length: None,
    409                                         },
    410                                         remaining: hmac_success.remaining,
    411                                     }),
    412                                 },
    413                             )
    414                         } else {
    415                             Ok(CborSuccess {
    416                                 value: Self {
    417                                     cred_protect: cred_success.value,
    418                                     hmac_secret: HmacSecret::None,
    419                                     min_pin_length: None,
    420                                 },
    421                                 remaining: cred_success.remaining,
    422                             })
    423                         }
    424                     })
    425                 }
    426                 cbor::MAP_2 => {
    427                     CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    428                         if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    429                             HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    430                                 |hmac_success| match hmac_success.value {
    431                                     HmacSecretEnabled::None => {
    432                                         // We don't even bother checking for `HmacSecretGet` since
    433                                         // it's only valid when `HmacSecretEnabled` exists with a value
    434                                         // of `true`.
    435                                         Err(AuthenticatorExtensionOutputErr::Missing)
    436                                     }
    437                                     HmacSecretEnabled::Val(hmac) => MinPinLength::from_cbor(
    438                                         hmac_success.remaining,
    439                                     )
    440                                     .and_then(|pin_success| match pin_success.value {
    441                                         MinPinLength::None => {
    442                                             if hmac {
    443                                                 HmacSecretGet::<true>::from_cbor(
    444                                                     pin_success.remaining,
    445                                                 )
    446                                                 .map_err(AuthenticatorExtensionOutputErr::from)
    447                                                 .and_then(|hmac_get| match hmac_get.value {
    448                                                     HmacSecretGet::None => Err(
    449                                                         AuthenticatorExtensionOutputErr::Missing,
    450                                                     ),
    451                                                     HmacSecretGet::One => Ok(CborSuccess {
    452                                                         value: Self {
    453                                                             cred_protect: cred_success.value,
    454                                                             hmac_secret: HmacSecret::One,
    455                                                             min_pin_length: None,
    456                                                         },
    457                                                         remaining: hmac_get.remaining,
    458                                                     }),
    459                                                     HmacSecretGet::Two => Ok(CborSuccess {
    460                                                         value: Self {
    461                                                             cred_protect: cred_success.value,
    462                                                             hmac_secret: HmacSecret::Two,
    463                                                             min_pin_length: None,
    464                                                         },
    465                                                         remaining: hmac_get.remaining,
    466                                                     }),
    467                                                 })
    468                                             } else {
    469                                                 // We don't even bother checking for `HmacSecretGet` since
    470                                                 // it's only valid when `HmacSecretEnabled` exists with a value
    471                                                 // of `true`.
    472                                                 Err(AuthenticatorExtensionOutputErr::Missing)
    473                                             }
    474                                         }
    475                                         MinPinLength::Val(min_pin_len) => Ok(CborSuccess {
    476                                             value: Self {
    477                                                 cred_protect: cred_success.value,
    478                                                 hmac_secret: if hmac {
    479                                                     HmacSecret::Enabled
    480                                                 } else {
    481                                                     HmacSecret::NotEnabled
    482                                                 },
    483                                                 min_pin_length: Some(min_pin_len),
    484                                             },
    485                                             remaining: pin_success.remaining,
    486                                         }),
    487                                     }),
    488                                 },
    489                             )
    490                         } else {
    491                             HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    492                                 |hmac_success| match hmac_success.value {
    493                                     HmacSecretEnabled::None => MinPinLength::from_cbor(
    494                                         hmac_success.remaining,
    495                                     )
    496                                     .and_then(|pin_success| match pin_success.value {
    497                                         MinPinLength::None => {
    498                                             // We don't even bother checking for `HmacSecretGet` since
    499                                             // it's only valid when `HmacSecretEnabled` exists with a value
    500                                             // of `true`.
    501                                             Err(AuthenticatorExtensionOutputErr::Missing)
    502                                         }
    503                                         MinPinLength::Val(min_pin_len) => Ok(CborSuccess {
    504                                             value: Self {
    505                                                 cred_protect: cred_success.value,
    506                                                 hmac_secret: HmacSecret::None,
    507                                                 min_pin_length: Some(min_pin_len),
    508                                             },
    509                                             remaining: pin_success.remaining,
    510                                         }),
    511                                     }),
    512                                     // We don't even bother checking for `HmacSecretGet` since
    513                                     // it's only valid when `HmacSecretEnabled` exists with a value
    514                                     // of `true`.
    515                                     HmacSecretEnabled::Val(hmac) => Ok(CborSuccess {
    516                                         value: Self {
    517                                             cred_protect: cred_success.value,
    518                                             hmac_secret: if hmac {
    519                                                 HmacSecret::Enabled
    520                                             } else {
    521                                                 HmacSecret::NotEnabled
    522                                             },
    523                                             min_pin_length: None,
    524                                         },
    525                                         remaining: hmac_success.remaining,
    526                                     }),
    527                                 },
    528                             )
    529                         }
    530                     })
    531                 }
    532                 cbor::MAP_3 => {
    533                     CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    534                         if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    535                             HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    536                                 |hmac_success| match hmac_success.value {
    537                                     HmacSecretEnabled::None => {
    538                                         Err(AuthenticatorExtensionOutputErr::Missing)
    539                                     }
    540                                     HmacSecretEnabled::Val(hmac) => {
    541                                         if hmac {
    542                                             MinPinLength::from_cbor(
    543                                                 hmac_success.remaining,
    544                                             )
    545                                             .and_then(|pin_success| match pin_success.value {
    546                                                 MinPinLength::None => Err(AuthenticatorExtensionOutputErr::Missing),
    547                                                 MinPinLength::Val(min_pin_len) => HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| {
    548                                                     match hmac_get.value {
    549                                                         HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    550                                                         HmacSecretGet::One => {
    551                                                             Ok(CborSuccess {
    552                                                                 value: Self {
    553                                                                     cred_protect: cred_success.value,
    554                                                                     hmac_secret: HmacSecret::One,
    555                                                                     min_pin_length: Some(min_pin_len),
    556                                                                 },
    557                                                                 remaining: hmac_get.remaining,
    558                                                             })
    559                                                         }
    560                                                         HmacSecretGet::Two => {
    561                                                             Ok(CborSuccess {
    562                                                                 value: Self {
    563                                                                     cred_protect: cred_success.value,
    564                                                                     hmac_secret: HmacSecret::Two,
    565                                                                     min_pin_length: Some(min_pin_len),
    566                                                                 },
    567                                                                 remaining: hmac_get.remaining,
    568                                                             })
    569                                                         }
    570                                                     }
    571                                                 })
    572                                             })
    573                                         } else {
    574                                             // We don't even bother checking for `HmacSecretGet` since
    575                                             // it's only valid when `HmacSecretEnabled` exists with a value
    576                                             // of `true`.
    577                                             Err(AuthenticatorExtensionOutputErr::Missing)
    578                                         }
    579                                     }
    580                                 },
    581                             )
    582                         } else {
    583                             HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    584                                 |hmac_success| match hmac_success.value {
    585                                     // We don't even bother checking for `HmacSecretGet` since
    586                                     // it's only valid when `HmacSecretEnabled` exists with a value
    587                                     // of `true`.
    588                                     HmacSecretEnabled::None => Err(AuthenticatorExtensionOutputErr::Missing),
    589                                     HmacSecretEnabled::Val(hmac) => {
    590                                         MinPinLength::from_cbor(hmac_success.remaining).and_then(|pin_success| {
    591                                             match pin_success.value {
    592                                                 MinPinLength::None => {
    593                                                     if hmac {
    594                                                         HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| {
    595                                                             match hmac_get.value {
    596                                                                 HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    597                                                                 HmacSecretGet::One => {
    598                                                                     Ok(CborSuccess {
    599                                                                         value: Self {
    600                                                                             cred_protect: cred_success.value,
    601                                                                             hmac_secret: HmacSecret::One,
    602                                                                             min_pin_length: None,
    603                                                                         },
    604                                                                         remaining: hmac_get.remaining,
    605                                                                     })
    606                                                                 }
    607                                                                 HmacSecretGet::Two => {
    608                                                                     Ok(CborSuccess {
    609                                                                         value: Self {
    610                                                                             cred_protect: cred_success.value,
    611                                                                             hmac_secret: HmacSecret::Two,
    612                                                                             min_pin_length: None,
    613                                                                         },
    614                                                                         remaining: hmac_get.remaining,
    615                                                                     })
    616                                                                 }
    617                                                             }
    618                                                         })
    619                                                     } else {
    620                                                         // We don't even bother checking for `HmacSecretGet` since
    621                                                         // it's only valid when `HmacSecretEnabled` exists with a value
    622                                                         // of `true`.
    623                                                         Err(AuthenticatorExtensionOutputErr::Missing)
    624                                                     }
    625                                                 }
    626                                                 MinPinLength::Val(min_pin_len) => {
    627                                                     Ok(CborSuccess {
    628                                                         value: Self {
    629                                                             cred_protect: cred_success.value,
    630                                                             hmac_secret: if hmac { HmacSecret::Enabled } else { HmacSecret::NotEnabled },
    631                                                             min_pin_length: Some(min_pin_len),
    632                                                         },
    633                                                         remaining: pin_success.remaining,
    634                                                     })
    635                                                 }
    636                                             }
    637                                         })
    638                                     }
    639                                 }
    640                             )
    641                         }
    642                     })
    643                 }
    644                 cbor::MAP_4 => {
    645                     CredentialProtectionPolicy::from_cbor(map_rem).and_then(|cred_success| {
    646                         if matches!(cred_success.value, CredentialProtectionPolicy::None) {
    647                             Err(AuthenticatorExtensionOutputErr::Missing)
    648                         } else {
    649                             HmacSecretEnabled::from_cbor(cred_success.remaining).and_then(
    650                                 |hmac_success| match hmac_success.value {
    651                                     HmacSecretEnabled::None => {
    652                                         Err(AuthenticatorExtensionOutputErr::Missing)
    653                                     }
    654                                     HmacSecretEnabled::Val(hmac) => {
    655                                         if hmac {
    656                                             MinPinLength::from_cbor(
    657                                                 hmac_success.remaining,
    658                                             )
    659                                             .and_then(|pin_success| match pin_success.value {
    660                                                 MinPinLength::None => {
    661                                                     Err(AuthenticatorExtensionOutputErr::Missing)
    662                                                 }
    663                                                 MinPinLength::Val(min_pin_len) => HmacSecretGet::<true>::from_cbor(pin_success.remaining).map_err(AuthenticatorExtensionOutputErr::from).and_then(|hmac_get| {
    664                                                     match hmac_get.value {
    665                                                         HmacSecretGet::None => Err(AuthenticatorExtensionOutputErr::Missing),
    666                                                         HmacSecretGet::One => {
    667                                                             Ok(CborSuccess {
    668                                                                 value: Self {
    669                                                                     cred_protect: cred_success.value,
    670                                                                     hmac_secret: HmacSecret::One,
    671                                                                     min_pin_length: Some(min_pin_len),
    672                                                                 },
    673                                                                 remaining: hmac_get.remaining,
    674                                                             })
    675                                                         }
    676                                                         HmacSecretGet::Two => {
    677                                                             Ok(CborSuccess {
    678                                                                 value: Self {
    679                                                                     cred_protect: cred_success.value,
    680                                                                     hmac_secret: HmacSecret::Two,
    681                                                                     min_pin_length: Some(min_pin_len),
    682                                                                 },
    683                                                                 remaining: hmac_get.remaining,
    684                                                             })
    685                                                         }
    686                                                     }
    687                                                 })
    688                                             })
    689                                         } else {
    690                                             // We don't even bother checking for `HmacSecretGet` since
    691                                             // it's only valid when `HmacSecretEnabled` exists with a value
    692                                             // of `true`.
    693                                             Err(AuthenticatorExtensionOutputErr::Missing)
    694                                         }
    695                                     }
    696                                 },
    697                             )
    698                         }
    699                     })
    700                 }
    701                 _ => Err(AuthenticatorExtensionOutputErr::CborHeader),
    702             },
    703         )
    704     }
    705 }
    706 /// 32-bytes representing an alleged Ed25519 public key (i.e., compressed y-coordinate).
    707 #[derive(Clone, Copy, Debug)]
    708 pub struct Ed25519PubKey<T>(T);
    709 impl<T> Ed25519PubKey<T> {
    710     /// Returns the contained data consuming `self`.
    711     #[inline]
    712     pub fn into_inner(self) -> T {
    713         self.0
    714     }
    715     /// Returns the contained data.
    716     #[inline]
    717     pub const fn inner(&self) -> &T {
    718         &self.0
    719     }
    720 }
    721 impl<T: AsRef<[u8]>> Ed25519PubKey<T> {
    722     /// Returns the compressed y-coordinate.
    723     #[inline]
    724     #[must_use]
    725     pub fn compressed_y_coordinate(&self) -> &[u8] {
    726         self.0.as_ref()
    727     }
    728 }
    729 impl Ed25519PubKey<&[u8]> {
    730     /// Validates `self` is in fact a valid Ed25519 public key.
    731     ///
    732     /// # Errors
    733     ///
    734     /// Errors iff `self` is not a valid Ed25519 public key.
    735     #[inline]
    736     pub fn validate(self) -> Result<(), PubKeyErr> {
    737         self.into_ver_key().map(|_| ())
    738     }
    739     /// Converts `self` into [`VerifyingKey`].
    740     pub(super) fn into_ver_key(self) -> Result<VerifyingKey, PubKeyErr> {
    741         self.into_owned().into_ver_key()
    742     }
    743     /// Transforms `self` into an "owned" version.
    744     #[inline]
    745     #[must_use]
    746     pub fn into_owned(self) -> Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> {
    747         Ed25519PubKey(*Ed25519PubKey::<&[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>::from(self).0)
    748     }
    749 }
    750 impl Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]> {
    751     /// Validates `self` is in fact a valid Ed25519 public key.
    752     ///
    753     /// # Errors
    754     ///
    755     /// Errors iff `self` is not a valid Ed25519 public key.
    756     #[inline]
    757     pub fn validate(self) -> Result<(), PubKeyErr> {
    758         self.into_ver_key().map(|_| ())
    759     }
    760     /// Converts `self` into [`VerifyingKey`].
    761     fn into_ver_key(self) -> Result<VerifyingKey, PubKeyErr> {
    762         // ["Taming the many EdDSAs"](https://eprint.iacr.org/2020/1244.pdf) goes over
    763         // and proves varying levels of signature security. The only property that is
    764         // important for WebAuthn is existential unforgeability under chosen message
    765         // attacks (EUF-CMA). No matter how `ed25519-dalek` is used this is met.
    766         // Additional properties that may be of importance are strong unforgeability
    767         // under chosen message attacks (SUF-CMA), binding signature (BS), and
    768         // strongly binding signature (SBS).
    769         // No matter how `ed25519-dalek` is used, SUF-CMA is achieved. Because
    770         // we always achieve SUF-CMA, we elect—despite no benefit in WebAuthn—to also
    771         // achieve SBS. One can achieve SBS-secure by simply rejecting small-order
    772         // keys which is precisely what `VerifyingKey::is_weak` does.
    773         // Note this means we _don't_ conform to [RFC 8032](https://www.rfc-editor.org/rfc/rfc8032)
    774         // nor [NIST SP 800-186](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-186.pdf).
    775         // As stated, there is no additional security by conforming to the above specs though;
    776         // furthermore, RFC 8032 does not require rejecting small-order points despite requiring
    777         // canonical encoding of points; thus it does not achieve SBS-security.
    778         // NIST SP 800-186 does achieve SUF-CMA and SBS-security but requires additional properties
    779         // that have no cryptographic importance. Specifically it mandates canonicity of encodings
    780         // for both public keys and signatures and requires not only that points not be small-order
    781         // but more strictly that points are in the prime-order subgroup (excluding the identity).
    782         // This is more work for no benefit, so we elect to stay within the confines of the exposed API.
    783         VerifyingKey::from_bytes(&self.0)
    784             .map_err(|_e| PubKeyErr::Ed25519)
    785             .and_then(|key| {
    786                 if key.is_weak() {
    787                     Err(PubKeyErr::Ed25519)
    788                 } else {
    789                     Ok(key)
    790                 }
    791             })
    792     }
    793 }
    794 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Ed25519PubKey<&'b [u8]> {
    795     type Error = Ed25519PubKeyErr;
    796     /// Interprets `value` as the compressed y-coordinate of an Ed25519 public key.
    797     #[inline]
    798     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
    799         if value.len() == ed25519_dalek::PUBLIC_KEY_LENGTH {
    800             Ok(Self(value))
    801         } else {
    802             Err(Ed25519PubKeyErr)
    803         }
    804     }
    805 }
    806 impl From<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    807     for Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    808 {
    809     #[inline]
    810     fn from(value: [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]) -> Self {
    811         Self(value)
    812     }
    813 }
    814 impl<'a: 'b, 'b> From<&'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>>
    815     for Ed25519PubKey<&'b [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    816 {
    817     #[inline]
    818     fn from(value: &'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self {
    819         Self(&value.0)
    820     }
    821 }
    822 impl<'a: 'b, 'b> From<Ed25519PubKey<&'a [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>>
    823     for Ed25519PubKey<&'b [u8]>
    824 {
    825     #[inline]
    826     fn from(value: Ed25519PubKey<&'a [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self {
    827         Self(value.0.as_slice())
    828     }
    829 }
    830 impl<'a: 'b, 'b> From<&'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>>
    831     for Ed25519PubKey<&'b [u8]>
    832 {
    833     #[inline]
    834     fn from(value: &'a Ed25519PubKey<[u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>) -> Self {
    835         Self(value.0.as_slice())
    836     }
    837 }
    838 impl<'a: 'b, 'b> From<Ed25519PubKey<&'a [u8]>>
    839     for Ed25519PubKey<&'b [u8; ed25519_dalek::PUBLIC_KEY_LENGTH]>
    840 {
    841     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
    842     #[inline]
    843     fn from(value: Ed25519PubKey<&'a [u8]>) -> Self {
    844         Self(
    845             value
    846                 .0
    847                 .try_into()
    848                 .unwrap_or_else(|_e| unreachable!("there is a bug in &Array::try_from")),
    849         )
    850     }
    851 }
    852 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<Ed25519PubKey<T>> for Ed25519PubKey<T2> {
    853     #[inline]
    854     fn eq(&self, other: &Ed25519PubKey<T>) -> bool {
    855         self.0 == other.0
    856     }
    857 }
    858 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<Ed25519PubKey<T>> for &Ed25519PubKey<T2> {
    859     #[inline]
    860     fn eq(&self, other: &Ed25519PubKey<T>) -> bool {
    861         **self == *other
    862     }
    863 }
    864 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&Ed25519PubKey<T>> for Ed25519PubKey<T2> {
    865     #[inline]
    866     fn eq(&self, other: &&Ed25519PubKey<T>) -> bool {
    867         *self == **other
    868     }
    869 }
    870 impl<T: Eq> Eq for Ed25519PubKey<T> {}
    871 /// Two 32-byte regions representing the big-endian x and y coordinates of an alleged P-256 public key.
    872 #[derive(Clone, Copy, Debug)]
    873 pub struct UncompressedP256PubKey<'a>(&'a [u8], &'a [u8]);
    874 impl<'a> UncompressedP256PubKey<'a> {
    875     /// Returns the big-endian x-coordinate.
    876     #[inline]
    877     #[must_use]
    878     pub const fn x(self) -> &'a [u8] {
    879         self.0
    880     }
    881     /// Returns the big-endian y-coordinate.
    882     #[inline]
    883     #[must_use]
    884     pub const fn y(self) -> &'a [u8] {
    885         self.1
    886     }
    887     /// Validates `self` is in fact a valid P-256 public key.
    888     ///
    889     /// # Errors
    890     ///
    891     /// Errors iff `self` is not a valid P-256 public key.
    892     #[inline]
    893     pub fn validate(self) -> Result<(), PubKeyErr> {
    894         self.into_ver_key().map(|_| ())
    895     }
    896     /// Converts `self` into [`P256VerKey`].
    897     fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> {
    898         P256VerKey::from_encoded_point(&P256Pt::from_affine_coordinates(
    899             self.0.into(),
    900             self.1.into(),
    901             false,
    902         ))
    903         .map_err(|_e| PubKeyErr::P256)
    904     }
    905     /// Returns `true` iff [`Self::y`] is odd.
    906     #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
    907     #[inline]
    908     #[must_use]
    909     pub const fn y_is_odd(self) -> bool {
    910         // `self.1.len() == 32`, so this won't `panic`.
    911         self.1[31] & 1 == 1
    912     }
    913     /// Transforms `self` into the compressed version that owns the data.
    914     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
    915     #[inline]
    916     #[must_use]
    917     pub fn into_compressed(
    918         self,
    919     ) -> CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> {
    920         CompressedP256PubKey {
    921             x: self.0.try_into().unwrap_or_else(|_e| unreachable!("there is a bug in UncompressedP256PubKey that allows for the x-coordinate to not be 32 bytes in length")),
    922             y_is_odd: self.y_is_odd(),
    923         }
    924     }
    925 }
    926 impl<'a: 'b, 'b> TryFrom<(&'a [u8], &'a [u8])> for UncompressedP256PubKey<'b> {
    927     type Error = UncompressedP256PubKeyErr;
    928     /// The first item is the big-endian x-coordinate, and the second item is the big-endian y-coordinate.
    929     #[inline]
    930     fn try_from((x, y): (&'a [u8], &'a [u8])) -> Result<Self, Self::Error> {
    931         /// Number of bytes each coordinate is made of.
    932         const COORD_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
    933         if x.len() == COORD_LEN {
    934             if y.len() == COORD_LEN {
    935                 Ok(Self(x, y))
    936             } else {
    937                 Err(UncompressedP256PubKeyErr::Y)
    938             }
    939         } else {
    940             Err(UncompressedP256PubKeyErr::X)
    941         }
    942     }
    943 }
    944 impl PartialEq<UncompressedP256PubKey<'_>> for UncompressedP256PubKey<'_> {
    945     #[inline]
    946     fn eq(&self, other: &UncompressedP256PubKey<'_>) -> bool {
    947         self.0 == other.0 && self.1 == other.1
    948     }
    949 }
    950 impl PartialEq<UncompressedP256PubKey<'_>> for &UncompressedP256PubKey<'_> {
    951     #[inline]
    952     fn eq(&self, other: &UncompressedP256PubKey<'_>) -> bool {
    953         **self == *other
    954     }
    955 }
    956 impl PartialEq<&UncompressedP256PubKey<'_>> for UncompressedP256PubKey<'_> {
    957     #[inline]
    958     fn eq(&self, other: &&UncompressedP256PubKey<'_>) -> bool {
    959         *self == **other
    960     }
    961 }
    962 impl Eq for UncompressedP256PubKey<'_> {}
    963 /// 32-bytes representing the big-endian x-coordinate and a `bool` representing whether the y-coordinate
    964 /// is odd of an alleged P-256 public key.
    965 #[derive(Clone, Copy, Debug)]
    966 pub struct CompressedP256PubKey<T> {
    967     /// 32-byte x-coordinate.
    968     x: T,
    969     /// `true` iff the y-coordinate is odd.
    970     y_is_odd: bool,
    971 }
    972 impl<T> CompressedP256PubKey<T> {
    973     /// Returns [`Self::x`] and [`Self::y_is_odd`] consuming `self`.
    974     #[inline]
    975     pub fn into_parts(self) -> (T, bool) {
    976         (self.x, self.y_is_odd)
    977     }
    978     /// Returns [`Self::x`] and [`Self::y_is_odd`].
    979     #[inline]
    980     pub const fn as_parts(&self) -> (&T, bool) {
    981         (&self.x, self.y_is_odd)
    982     }
    983     /// Returns the 32-byte big-endian x-coordinate.
    984     #[inline]
    985     pub const fn x(&self) -> &T {
    986         &self.x
    987     }
    988     /// `true` iff the y-coordinate is odd.
    989     #[inline]
    990     pub const fn y_is_odd(&self) -> bool {
    991         self.y_is_odd
    992     }
    993 }
    994 impl CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]> {
    995     /// Validates `self` is in fact a valid P-256 public key.
    996     ///
    997     /// # Errors
    998     ///
    999     /// Errors iff `self` is not a valid P-256 public key.
   1000     #[inline]
   1001     pub fn validate(self) -> Result<(), PubKeyErr> {
   1002         self.into_ver_key().map(|_| ())
   1003     }
   1004     /// Converts `self` into [`P256VerKey`].
   1005     pub(super) fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> {
   1006         P256Affine::decompress(&self.x.into(), u8::from(self.y_is_odd).into())
   1007             .into_option()
   1008             .ok_or(PubKeyErr::P256)
   1009             .and_then(|pt| P256VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P256))
   1010     }
   1011 }
   1012 impl CompressedP256PubKey<&[u8]> {
   1013     /// Validates `self` is in fact a valid P-256 public key.
   1014     ///
   1015     /// # Errors
   1016     ///
   1017     /// Errors iff `self` is not a valid P-256 public key.
   1018     #[inline]
   1019     pub fn validate(self) -> Result<(), PubKeyErr> {
   1020         self.into_ver_key().map(|_| ())
   1021     }
   1022     /// Converts `self` into [`P256VerKey`].
   1023     pub(super) fn into_ver_key(self) -> Result<P256VerKey, PubKeyErr> {
   1024         P256Affine::decompress(self.x.into(), u8::from(self.y_is_odd).into())
   1025             .into_option()
   1026             .ok_or(PubKeyErr::P256)
   1027             .and_then(|pt| P256VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P256))
   1028     }
   1029 }
   1030 impl<'a: 'b, 'b> TryFrom<(&'a [u8], bool)> for CompressedP256PubKey<&'b [u8]> {
   1031     type Error = CompressedP256PubKeyErr;
   1032     #[inline]
   1033     fn try_from((x, y_is_odd): (&'a [u8], bool)) -> Result<Self, Self::Error> {
   1034         /// The number of bytes the x-coordinate is.
   1035         const X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1036         if x.len() == X_LEN {
   1037             Ok(Self { x, y_is_odd })
   1038         } else {
   1039             Err(CompressedP256PubKeyErr)
   1040         }
   1041     }
   1042 }
   1043 impl From<([u8; <NistP256 as Curve>::FieldBytesSize::INT], bool)>
   1044     for CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>
   1045 {
   1046     #[inline]
   1047     fn from((x, y_is_odd): ([u8; <NistP256 as Curve>::FieldBytesSize::INT], bool)) -> Self {
   1048         Self { x, y_is_odd }
   1049     }
   1050 }
   1051 impl<'a: 'b, 'b> From<&'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>>
   1052     for CompressedP256PubKey<&'b [u8; <NistP256 as Curve>::FieldBytesSize::INT]>
   1053 {
   1054     #[inline]
   1055     fn from(
   1056         value: &'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>,
   1057     ) -> Self {
   1058         Self {
   1059             x: &value.x,
   1060             y_is_odd: value.y_is_odd,
   1061         }
   1062     }
   1063 }
   1064 impl<'a: 'b, 'b> From<CompressedP256PubKey<&'a [u8; <NistP256 as Curve>::FieldBytesSize::INT]>>
   1065     for CompressedP256PubKey<&'b [u8]>
   1066 {
   1067     #[inline]
   1068     fn from(
   1069         value: CompressedP256PubKey<&'a [u8; <NistP256 as Curve>::FieldBytesSize::INT]>,
   1070     ) -> Self {
   1071         Self {
   1072             x: value.x.as_slice(),
   1073             y_is_odd: value.y_is_odd,
   1074         }
   1075     }
   1076 }
   1077 impl<'a: 'b, 'b> From<&'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>>
   1078     for CompressedP256PubKey<&'b [u8]>
   1079 {
   1080     #[inline]
   1081     fn from(
   1082         value: &'a CompressedP256PubKey<[u8; <NistP256 as Curve>::FieldBytesSize::INT]>,
   1083     ) -> Self {
   1084         Self {
   1085             x: value.x.as_slice(),
   1086             y_is_odd: value.y_is_odd,
   1087         }
   1088     }
   1089 }
   1090 impl<'a: 'b, 'b> From<CompressedP256PubKey<&'a [u8]>>
   1091     for CompressedP256PubKey<&'b [u8; <NistP256 as Curve>::FieldBytesSize::INT]>
   1092 {
   1093     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   1094     #[inline]
   1095     fn from(value: CompressedP256PubKey<&'a [u8]>) -> Self {
   1096         Self {
   1097             x: value
   1098                 .x
   1099                 .try_into()
   1100                 .unwrap_or_else(|_e| unreachable!("&Array::try_from has a bug")),
   1101             y_is_odd: value.y_is_odd,
   1102         }
   1103     }
   1104 }
   1105 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP256PubKey<T>>
   1106     for CompressedP256PubKey<T2>
   1107 {
   1108     #[inline]
   1109     fn eq(&self, other: &CompressedP256PubKey<T>) -> bool {
   1110         self.x == other.x && self.y_is_odd == other.y_is_odd
   1111     }
   1112 }
   1113 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP256PubKey<T>>
   1114     for &CompressedP256PubKey<T2>
   1115 {
   1116     #[inline]
   1117     fn eq(&self, other: &CompressedP256PubKey<T>) -> bool {
   1118         **self == *other
   1119     }
   1120 }
   1121 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CompressedP256PubKey<T>>
   1122     for CompressedP256PubKey<T2>
   1123 {
   1124     #[inline]
   1125     fn eq(&self, other: &&CompressedP256PubKey<T>) -> bool {
   1126         *self == **other
   1127     }
   1128 }
   1129 impl<T: Eq> Eq for CompressedP256PubKey<T> {}
   1130 /// Two 48-byte regions representing the big-endian x and y coordinates of an alleged P-384 public key.
   1131 #[derive(Clone, Copy, Debug)]
   1132 pub struct UncompressedP384PubKey<'a>(&'a [u8], &'a [u8]);
   1133 impl<'a> UncompressedP384PubKey<'a> {
   1134     /// Returns the big-endian x-coordinate.
   1135     #[inline]
   1136     #[must_use]
   1137     pub const fn x(self) -> &'a [u8] {
   1138         self.0
   1139     }
   1140     /// Returns the big-endian y-coordinate.
   1141     #[inline]
   1142     #[must_use]
   1143     pub const fn y(self) -> &'a [u8] {
   1144         self.1
   1145     }
   1146     /// Validates `self` is in fact a valid P-384 public key.
   1147     ///
   1148     /// # Errors
   1149     ///
   1150     /// Errors iff `self` is not a valid P-384 public key.
   1151     #[inline]
   1152     pub fn validate(self) -> Result<(), PubKeyErr> {
   1153         self.into_ver_key().map(|_| ())
   1154     }
   1155     /// Converts `self` into [`P384VerKey`].
   1156     fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> {
   1157         P384VerKey::from_encoded_point(&P384Pt::from_affine_coordinates(
   1158             self.0.into(),
   1159             self.1.into(),
   1160             false,
   1161         ))
   1162         .map_err(|_e| PubKeyErr::P384)
   1163     }
   1164     /// Returns `true` iff [`Self::y`] is odd.
   1165     #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
   1166     #[inline]
   1167     #[must_use]
   1168     pub const fn y_is_odd(self) -> bool {
   1169         // `self.1.len() == 48`, so this won't `panic`.
   1170         self.1[47] & 1 == 1
   1171     }
   1172     /// Transforms `self` into the compressed version that owns the data.
   1173     #[expect(clippy::unreachable, reason = "want to crash when there is a bug")]
   1174     #[inline]
   1175     #[must_use]
   1176     pub fn into_compressed(
   1177         self,
   1178     ) -> CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> {
   1179         CompressedP384PubKey {
   1180             x: self.0.try_into().unwrap_or_else(|_e| unreachable!("there is a bug in UncompressedP384PubKey that allows for the x-coordinate to not be 48 bytes in length")),
   1181             y_is_odd: self.y_is_odd(),
   1182         }
   1183     }
   1184 }
   1185 impl<'a: 'b, 'b> TryFrom<(&'a [u8], &'a [u8])> for UncompressedP384PubKey<'b> {
   1186     type Error = UncompressedP384PubKeyErr;
   1187     /// The first item is the big-endian x-coordinate, and the second item is the big-endian y-coordinate.
   1188     #[inline]
   1189     fn try_from((x, y): (&'a [u8], &'a [u8])) -> Result<Self, Self::Error> {
   1190         /// Number of bytes each coordinate is made of.
   1191         const COORD_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   1192         if x.len() == COORD_LEN {
   1193             if y.len() == COORD_LEN {
   1194                 Ok(Self(x, y))
   1195             } else {
   1196                 Err(UncompressedP384PubKeyErr::Y)
   1197             }
   1198         } else {
   1199             Err(UncompressedP384PubKeyErr::X)
   1200         }
   1201     }
   1202 }
   1203 impl PartialEq<UncompressedP384PubKey<'_>> for UncompressedP384PubKey<'_> {
   1204     #[inline]
   1205     fn eq(&self, other: &UncompressedP384PubKey<'_>) -> bool {
   1206         self.0 == other.0 && self.1 == other.1
   1207     }
   1208 }
   1209 impl PartialEq<UncompressedP384PubKey<'_>> for &UncompressedP384PubKey<'_> {
   1210     #[inline]
   1211     fn eq(&self, other: &UncompressedP384PubKey<'_>) -> bool {
   1212         **self == *other
   1213     }
   1214 }
   1215 impl PartialEq<&UncompressedP384PubKey<'_>> for UncompressedP384PubKey<'_> {
   1216     #[inline]
   1217     fn eq(&self, other: &&UncompressedP384PubKey<'_>) -> bool {
   1218         *self == **other
   1219     }
   1220 }
   1221 impl Eq for UncompressedP384PubKey<'_> {}
   1222 /// 48-bytes representing the big-endian x-coordinate and a `bool` representing whether the y-coordinate
   1223 /// is odd of an alleged P-384 public key.
   1224 #[derive(Clone, Copy, Debug)]
   1225 pub struct CompressedP384PubKey<T> {
   1226     /// 48-byte x-coordinate.
   1227     x: T,
   1228     /// `true` iff the y-coordinate is odd.
   1229     y_is_odd: bool,
   1230 }
   1231 impl<T> CompressedP384PubKey<T> {
   1232     /// Returns [`Self::x`] and [`Self::y_is_odd`] consuming `self`.
   1233     #[inline]
   1234     pub fn into_parts(self) -> (T, bool) {
   1235         (self.x, self.y_is_odd)
   1236     }
   1237     /// Returns [`Self::x`] and [`Self::y_is_odd`].
   1238     #[inline]
   1239     pub const fn as_parts(&self) -> (&T, bool) {
   1240         (&self.x, self.y_is_odd)
   1241     }
   1242     /// Returns the 48-byte big-endian x-coordinate.
   1243     #[inline]
   1244     pub const fn x(&self) -> &T {
   1245         &self.x
   1246     }
   1247     /// `true` iff the y-coordinate is odd.
   1248     #[inline]
   1249     #[must_use]
   1250     pub const fn y_is_odd(&self) -> bool {
   1251         self.y_is_odd
   1252     }
   1253 }
   1254 impl CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]> {
   1255     /// Validates `self` is in fact a valid P-384 public key.
   1256     ///
   1257     /// # Errors
   1258     ///
   1259     /// Errors iff `self` is not a valid P-384 public key.
   1260     #[inline]
   1261     pub fn validate(self) -> Result<(), PubKeyErr> {
   1262         self.into_ver_key().map(|_| ())
   1263     }
   1264     /// Converts `self` into [`P384VerKey`].
   1265     pub(super) fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> {
   1266         P384Affine::decompress(&self.x.into(), u8::from(self.y_is_odd).into())
   1267             .into_option()
   1268             .ok_or(PubKeyErr::P384)
   1269             .and_then(|pt| P384VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P384))
   1270     }
   1271 }
   1272 impl CompressedP384PubKey<&[u8]> {
   1273     /// Validates `self` is in fact a valid P-384 public key.
   1274     ///
   1275     /// # Errors
   1276     ///
   1277     /// Errors iff `self` is not a valid P-384 public key.
   1278     #[inline]
   1279     pub fn validate(self) -> Result<(), PubKeyErr> {
   1280         self.into_ver_key().map(|_| ())
   1281     }
   1282     /// Converts `self` into [`P384VerKey`].
   1283     pub(super) fn into_ver_key(self) -> Result<P384VerKey, PubKeyErr> {
   1284         P384Affine::decompress(self.x.into(), u8::from(self.y_is_odd).into())
   1285             .into_option()
   1286             .ok_or(PubKeyErr::P384)
   1287             .and_then(|pt| P384VerKey::from_affine(pt).map_err(|_e| PubKeyErr::P384))
   1288     }
   1289 }
   1290 impl<'a: 'b, 'b> TryFrom<(&'a [u8], bool)> for CompressedP384PubKey<&'b [u8]> {
   1291     type Error = CompressedP384PubKeyErr;
   1292     #[inline]
   1293     fn try_from((x, y_is_odd): (&'a [u8], bool)) -> Result<Self, Self::Error> {
   1294         /// Number of bytes of the x-coordinate is.
   1295         const X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   1296         if x.len() == X_LEN {
   1297             Ok(Self { x, y_is_odd })
   1298         } else {
   1299             Err(CompressedP384PubKeyErr)
   1300         }
   1301     }
   1302 }
   1303 impl From<([u8; <NistP384 as Curve>::FieldBytesSize::INT], bool)>
   1304     for CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>
   1305 {
   1306     #[inline]
   1307     fn from((x, y_is_odd): ([u8; <NistP384 as Curve>::FieldBytesSize::INT], bool)) -> Self {
   1308         Self { x, y_is_odd }
   1309     }
   1310 }
   1311 impl<'a: 'b, 'b> From<&'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>>
   1312     for CompressedP384PubKey<&'b [u8; <NistP384 as Curve>::FieldBytesSize::INT]>
   1313 {
   1314     #[inline]
   1315     fn from(
   1316         value: &'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>,
   1317     ) -> Self {
   1318         Self {
   1319             x: &value.x,
   1320             y_is_odd: value.y_is_odd,
   1321         }
   1322     }
   1323 }
   1324 impl<'a: 'b, 'b> From<CompressedP384PubKey<&'a [u8; <NistP384 as Curve>::FieldBytesSize::INT]>>
   1325     for CompressedP384PubKey<&'b [u8]>
   1326 {
   1327     #[inline]
   1328     fn from(
   1329         value: CompressedP384PubKey<&'a [u8; <NistP384 as Curve>::FieldBytesSize::INT]>,
   1330     ) -> Self {
   1331         Self {
   1332             x: value.x.as_slice(),
   1333             y_is_odd: value.y_is_odd,
   1334         }
   1335     }
   1336 }
   1337 impl<'a: 'b, 'b> From<&'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>>
   1338     for CompressedP384PubKey<&'b [u8]>
   1339 {
   1340     #[inline]
   1341     fn from(
   1342         value: &'a CompressedP384PubKey<[u8; <NistP384 as Curve>::FieldBytesSize::INT]>,
   1343     ) -> Self {
   1344         Self {
   1345             x: value.x.as_slice(),
   1346             y_is_odd: value.y_is_odd,
   1347         }
   1348     }
   1349 }
   1350 impl<'a: 'b, 'b> From<CompressedP384PubKey<&'a [u8]>>
   1351     for CompressedP384PubKey<&'b [u8; <NistP384 as Curve>::FieldBytesSize::INT]>
   1352 {
   1353     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   1354     #[inline]
   1355     fn from(value: CompressedP384PubKey<&'a [u8]>) -> Self {
   1356         Self {
   1357             x: value
   1358                 .x
   1359                 .try_into()
   1360                 .unwrap_or_else(|_e| unreachable!("&Array::try_from has a bug")),
   1361             y_is_odd: value.y_is_odd,
   1362         }
   1363     }
   1364 }
   1365 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP384PubKey<T>>
   1366     for CompressedP384PubKey<T2>
   1367 {
   1368     #[inline]
   1369     fn eq(&self, other: &CompressedP384PubKey<T>) -> bool {
   1370         self.x == other.x && self.y_is_odd == other.y_is_odd
   1371     }
   1372 }
   1373 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<CompressedP384PubKey<T>>
   1374     for &CompressedP384PubKey<T2>
   1375 {
   1376     #[inline]
   1377     fn eq(&self, other: &CompressedP384PubKey<T>) -> bool {
   1378         **self == *other
   1379     }
   1380 }
   1381 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&CompressedP384PubKey<T>>
   1382     for CompressedP384PubKey<T2>
   1383 {
   1384     #[inline]
   1385     fn eq(&self, other: &&CompressedP384PubKey<T>) -> bool {
   1386         *self == **other
   1387     }
   1388 }
   1389 impl<T: Eq> Eq for CompressedP384PubKey<T> {}
   1390 /// The minimum RSA public key exponent allowed by [`RsaPubKey`].
   1391 ///
   1392 /// [RFC 8017 § 3.1](https://www.rfc-editor.org/rfc/rfc8017#section-3.1) states the smallest valid RSA public
   1393 /// exponent is 3.
   1394 pub const MIN_RSA_E: u32 = 3;
   1395 /// The most bits an RSA public key modulus is allowed to consist of per [`RsaPubKey`].
   1396 ///
   1397 /// [RFC 8230 § 6.1](https://www.rfc-editor.org/rfc/rfc8230#section-6.1) recommends allowing moduli up to 16K bits.
   1398 pub const MAX_RSA_N_BITS: usize = 0x4000;
   1399 /// The fewest bits an RSA public key modulus is allowed to consist of per [`RsaPubKey`].
   1400 ///
   1401 /// [RFC 8230 § 6.1](https://www.rfc-editor.org/rfc/rfc8230#section-6.1) requires the modulus to be at least 2048
   1402 /// bits.
   1403 pub const MIN_RSA_N_BITS: usize = 0x800;
   1404 /// [`MIN_RSA_N_BITS`]–[`MAX_RSA_N_BITS`] bits representing the big-endian modulus and a `u32` `>=`
   1405 /// [`MIN_RSA_E`] representing the exponent of an alleged RSA public key.
   1406 ///
   1407 /// Note the modulus and exponent are always odd.
   1408 #[derive(Clone, Copy, Debug)]
   1409 pub struct RsaPubKey<T>(T, u32);
   1410 impl<T> RsaPubKey<T> {
   1411     /// Returns [`Self::n`] and [`Self::e`] consuming `self`.
   1412     #[inline]
   1413     pub fn into_parts(self) -> (T, u32) {
   1414         (self.0, self.1)
   1415     }
   1416     /// Returns [`Self::n`] and [`Self::e`].
   1417     #[inline]
   1418     pub const fn as_parts(&self) -> (&T, u32) {
   1419         (&self.0, self.1)
   1420     }
   1421     /// Returns the big-endian modulus.
   1422     #[inline]
   1423     pub const fn n(&self) -> &T {
   1424         &self.0
   1425     }
   1426     /// Returns the exponent.
   1427     #[inline]
   1428     pub const fn e(&self) -> u32 {
   1429         self.1
   1430     }
   1431 }
   1432 impl<T: AsRef<[u8]>> RsaPubKey<T> {
   1433     /// Converts `self` into [`RsaVerKey`].
   1434     pub(super) fn as_ver_key(&self) -> RsaVerKey<Sha256> {
   1435         RsaVerKey::new(RsaPublicKey::new_unchecked(
   1436             BigUint::from_bytes_be(self.0.as_ref()),
   1437             self.1.into(),
   1438         ))
   1439     }
   1440 }
   1441 impl RsaPubKey<&[u8]> {
   1442     /// Transforms `self` into an "owned" version.
   1443     #[inline]
   1444     #[must_use]
   1445     pub fn into_owned(self) -> RsaPubKey<Vec<u8>> {
   1446         RsaPubKey(self.0.to_owned(), self.1)
   1447     }
   1448 }
   1449 impl<'a: 'b, 'b> TryFrom<(&'a [u8], u32)> for RsaPubKey<&'b [u8]> {
   1450     type Error = RsaPubKeyErr;
   1451     /// The first item is the big-endian modulus, and the second item is the exponent.
   1452     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   1453     #[expect(
   1454         clippy::arithmetic_side_effects,
   1455         clippy::as_conversions,
   1456         reason = "comment justifies correctness"
   1457     )]
   1458     #[inline]
   1459     fn try_from((n, e): (&'a [u8], u32)) -> Result<Self, Self::Error> {
   1460         n.first().map_or(Err(RsaPubKeyErr::NSize), |fst| {
   1461             // `fst.leading_zeros()` is inclusively between `0` and `8`, so it's safe to convert to a
   1462             // `usize`.
   1463             let zeros = fst.leading_zeros() as usize;
   1464             if zeros == 8 {
   1465                 Err(RsaPubKeyErr::NLeading0)
   1466                 // `bits` is at least 8 since `n.len()` is at least 1; thus underflow cannot occur.
   1467             } else if let Some(bits) = n.len().checked_mul(8)
   1468                 && (MIN_RSA_N_BITS..=MAX_RSA_N_BITS).contains(&(bits - zeros))
   1469             {
   1470                 // We know `n` is not empty, so this won't `panic`.
   1471                 if n.last()
   1472                     .unwrap_or_else(|| unreachable!("there is a bug in RsaPubKey::try_from"))
   1473                     & 1
   1474                     == 0
   1475                 {
   1476                     Err(RsaPubKeyErr::NEven)
   1477                 } else if e < MIN_RSA_E {
   1478                     Err(RsaPubKeyErr::ESize)
   1479                 } else if e & 1 == 0 {
   1480                     Err(RsaPubKeyErr::EEven)
   1481                 } else {
   1482                     Ok(Self(n, e))
   1483                 }
   1484             } else {
   1485                 Err(RsaPubKeyErr::NSize)
   1486             }
   1487         })
   1488     }
   1489 }
   1490 impl TryFrom<(Vec<u8>, u32)> for RsaPubKey<Vec<u8>> {
   1491     type Error = RsaPubKeyErr;
   1492     /// Similar to [`RsaPubKey::try_from`] except `n` is a `Vec`.
   1493     #[inline]
   1494     fn try_from((n, e): (Vec<u8>, u32)) -> Result<Self, Self::Error> {
   1495         match RsaPubKey::<&[u8]>::try_from((n.as_slice(), e)) {
   1496             Ok(_) => Ok(Self(n, e)),
   1497             Err(err) => Err(err),
   1498         }
   1499     }
   1500 }
   1501 impl<'a: 'b, 'b> From<&'a RsaPubKey<Vec<u8>>> for RsaPubKey<&'b Vec<u8>> {
   1502     #[inline]
   1503     fn from(value: &'a RsaPubKey<Vec<u8>>) -> Self {
   1504         Self(&value.0, value.1)
   1505     }
   1506 }
   1507 impl<'a: 'b, 'b> From<RsaPubKey<&'a Vec<u8>>> for RsaPubKey<&'b [u8]> {
   1508     #[inline]
   1509     fn from(value: RsaPubKey<&'a Vec<u8>>) -> Self {
   1510         Self(value.0.as_slice(), value.1)
   1511     }
   1512 }
   1513 impl<'a: 'b, 'b> From<&'a RsaPubKey<Vec<u8>>> for RsaPubKey<&'b [u8]> {
   1514     #[inline]
   1515     fn from(value: &'a RsaPubKey<Vec<u8>>) -> Self {
   1516         Self(value.0.as_slice(), value.1)
   1517     }
   1518 }
   1519 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<RsaPubKey<T>> for RsaPubKey<T2> {
   1520     #[inline]
   1521     fn eq(&self, other: &RsaPubKey<T>) -> bool {
   1522         self.0 == other.0 && self.1 == other.1
   1523     }
   1524 }
   1525 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<RsaPubKey<T>> for &RsaPubKey<T2> {
   1526     #[inline]
   1527     fn eq(&self, other: &RsaPubKey<T>) -> bool {
   1528         **self == *other
   1529     }
   1530 }
   1531 impl<T: PartialEq<T2>, T2: PartialEq<T>> PartialEq<&RsaPubKey<T>> for RsaPubKey<T2> {
   1532     #[inline]
   1533     fn eq(&self, other: &&RsaPubKey<T>) -> bool {
   1534         *self == **other
   1535     }
   1536 }
   1537 impl<T: Eq> Eq for RsaPubKey<T> {}
   1538 /// `kty` COSE key common parameter as defined by
   1539 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters).
   1540 const KTY: u8 = cbor::ONE;
   1541 /// `OKP` COSE key type as defined by
   1542 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1543 const OKP: u8 = cbor::ONE;
   1544 /// `EC2` COSE key type as defined by
   1545 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1546 const EC2: u8 = cbor::TWO;
   1547 /// `RSA` COSE key type as defined by
   1548 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type).
   1549 const RSA: u8 = cbor::THREE;
   1550 /// `alg` COSE key common parameter as defined by
   1551 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-common-parameters).
   1552 const ALG: u8 = cbor::THREE;
   1553 /// `EdDSA` COSE algorithm as defined by
   1554 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1555 const EDDSA: u8 = cbor::NEG_EIGHT;
   1556 /// `ES256` COSE algorithm as defined by
   1557 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1558 const ES256: u8 = cbor::NEG_SEVEN;
   1559 /// `ES384` COSE algorithm as defined by
   1560 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1561 ///
   1562 /// This is -35 encoded in cbor which is encoded as |-35| - 1 = 35 - 1 = 34. Note
   1563 /// this must be preceded with `cbor::NEG_INFO_24`.
   1564 const ES384: u8 = 34;
   1565 /// `RS256` COSE algorithm as defined by
   1566 /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1567 ///
   1568 /// This is -257 encoded in cbor which is encoded as |-257| - 1 = 257 - 1 = 256 = [1, 0] in big endian.
   1569 /// Note this must be preceded with `cbor::NEG_INFO_25`.
   1570 const RS256: [u8; 2] = [1, 0];
   1571 impl<'a> FromCbor<'a> for Ed25519PubKey<&'a [u8]> {
   1572     type Err = CoseKeyErr;
   1573     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1574         /// `crv` COSE key type parameter for [`OKP`] as defined by
   1575         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1576         const CRV: u8 = cbor::NEG_ONE;
   1577         /// `Ed25519` COSE elliptic curve as defined by
   1578         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves).
   1579         const ED25519: u8 = cbor::SIX;
   1580         /// `x` COSE key type parameter for [`OKP`] as defined by
   1581         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1582         const X: u8 = cbor::NEG_TWO;
   1583         // `32 as u8` is OK.
   1584         /// `ed25519_dalek::PUBLIC_KEY_LENGTH` as a `u8`.
   1585         #[expect(
   1586             clippy::as_conversions,
   1587             clippy::cast_possible_truncation,
   1588             reason = "explained above and want a const"
   1589         )]
   1590         const KEY_LEN_U8: u8 = ed25519_dalek::PUBLIC_KEY_LENGTH as u8;
   1591         /// COSE header.
   1592         /// {kty:OKP,alg:EdDSA,crv:Ed25519,x:<CompressedEdwardsYPoint>}.
   1593         /// `kty` and `alg` come before `crv` and `x` since map order first
   1594         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1595         /// `kty` comes before `alg` since order is done byte-wise and
   1596         /// 1 is before 3. `crv` is before `x` since `0b001_00000` comes before
   1597         /// `0b001_00001` byte-wise.
   1598         const HEADER: [u8; 10] = [
   1599             cbor::MAP_4,
   1600             KTY,
   1601             OKP,
   1602             ALG,
   1603             EDDSA,
   1604             CRV,
   1605             ED25519,
   1606             X,
   1607             cbor::BYTES_INFO_24,
   1608             KEY_LEN_U8,
   1609         ];
   1610         cbor.split_at_checked(HEADER.len())
   1611             .ok_or(CoseKeyErr::Len)
   1612             .and_then(|(header, header_rem)| {
   1613                 if header == HEADER {
   1614                     header_rem
   1615                         .split_at_checked(ed25519_dalek::PUBLIC_KEY_LENGTH)
   1616                         .ok_or(CoseKeyErr::Len)
   1617                         .map(|(key, remaining)| CborSuccess {
   1618                             value: Self(key),
   1619                             remaining,
   1620                         })
   1621                 } else {
   1622                     Err(CoseKeyErr::Ed25519CoseEncoding)
   1623                 }
   1624             })
   1625     }
   1626 }
   1627 impl<'a> FromCbor<'a> for UncompressedP256PubKey<'a> {
   1628     type Err = CoseKeyErr;
   1629     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1630         /// `crv` COSE key type parameter for [`EC2`] as defined by
   1631         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1632         const CRV: u8 = cbor::NEG_ONE;
   1633         /// `P-256` COSE elliptic curve as defined by
   1634         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves).
   1635         const P256: u8 = cbor::ONE;
   1636         /// `x` COSE key type parameter for [`EC2`] as defined by
   1637         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1638         const X: u8 = cbor::NEG_TWO;
   1639         /// `y` COSE key type parameter for [`EC2`] as defined by
   1640         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1641         const Y: u8 = cbor::NEG_THREE;
   1642         /// Number of bytes the x-coordinate takes.
   1643         const X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1644         // `32 as u8` is OK.
   1645         /// `X_LEN` as a `u8`.
   1646         #[expect(
   1647             clippy::as_conversions,
   1648             clippy::cast_possible_truncation,
   1649             reason = "explained above and want a const"
   1650         )]
   1651         const X_LEN_U8: u8 = X_LEN as u8;
   1652         /// Number of bytes the y-coordinate takes.
   1653         const Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT;
   1654         // `32 as u8` is OK.
   1655         /// `Y_LEN` as a `u8`.
   1656         #[expect(
   1657             clippy::as_conversions,
   1658             clippy::cast_possible_truncation,
   1659             reason = "explained above and want a const"
   1660         )]
   1661         const Y_LEN_U8: u8 = Y_LEN as u8;
   1662         /// COSE header.
   1663         // {kty:EC2,alg:ES256,crv:P-256,x:<affine x-coordinate>,...}.
   1664         /// `kty` and `alg` come before `crv`, `x`, and `y` since map order first
   1665         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1666         /// `kty` comes before `alg` since order is done byte-wise and
   1667         /// 1 is before 3. `crv` is before `x` which is before `y` since
   1668         /// `0b001_00000` comes before `0b001_00001` which comes before
   1669         /// `0b001_00010` byte-wise.
   1670         const HEADER: [u8; 10] = [
   1671             cbor::MAP_5,
   1672             KTY,
   1673             EC2,
   1674             ALG,
   1675             ES256,
   1676             CRV,
   1677             P256,
   1678             X,
   1679             cbor::BYTES_INFO_24,
   1680             X_LEN_U8,
   1681         ];
   1682         /// {...y:<affine y-coordinate>}.
   1683         const Y_META: [u8; 3] = [Y, cbor::BYTES_INFO_24, Y_LEN_U8];
   1684         cbor.split_at_checked(HEADER.len())
   1685             .ok_or(CoseKeyErr::Len)
   1686             .and_then(|(header, header_rem)| {
   1687                 if header == HEADER {
   1688                     header_rem
   1689                         .split_at_checked(X_LEN)
   1690                         .ok_or(CoseKeyErr::Len)
   1691                         .and_then(|(x, x_rem)| {
   1692                             x_rem
   1693                                 .split_at_checked(Y_META.len())
   1694                                 .ok_or(CoseKeyErr::Len)
   1695                                 .and_then(|(y_meta, y_meta_rem)| {
   1696                                     if y_meta == Y_META {
   1697                                         y_meta_rem
   1698                                             .split_at_checked(Y_LEN)
   1699                                             .ok_or(CoseKeyErr::Len)
   1700                                             .map(|(y, remaining)| CborSuccess {
   1701                                                 value: Self(x, y),
   1702                                                 remaining,
   1703                                             })
   1704                                     } else {
   1705                                         Err(CoseKeyErr::P256CoseEncoding)
   1706                                     }
   1707                                 })
   1708                         })
   1709                 } else {
   1710                     Err(CoseKeyErr::P256CoseEncoding)
   1711                 }
   1712             })
   1713     }
   1714 }
   1715 impl<'a> FromCbor<'a> for UncompressedP384PubKey<'a> {
   1716     type Err = CoseKeyErr;
   1717     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1718         /// `crv` COSE key type parameter for [`EC2`] as defined by
   1719         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1720         const CRV: u8 = cbor::NEG_ONE;
   1721         /// `P-384` COSE elliptic curve as defined by
   1722         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves).
   1723         const P384: u8 = cbor::TWO;
   1724         /// `x` COSE key type parameter for [`EC2`] as defined by
   1725         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1726         const X: u8 = cbor::NEG_TWO;
   1727         /// `y` COSE key type parameter for [`EC2`] as defined by
   1728         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1729         const Y: u8 = cbor::NEG_THREE;
   1730         /// Number of bytes the x-coordinate takes.
   1731         const X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   1732         // `48 as u8` is OK.
   1733         /// `X_LEN` as a `u8`.
   1734         #[expect(
   1735             clippy::as_conversions,
   1736             clippy::cast_possible_truncation,
   1737             reason = "explained above and want a const"
   1738         )]
   1739         const X_LEN_U8: u8 = X_LEN as u8;
   1740         /// Number of bytes the y-coordinate takes.
   1741         const Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT;
   1742         // `48 as u8` is OK.
   1743         /// `Y_LEN` as a `u8`.
   1744         #[expect(
   1745             clippy::as_conversions,
   1746             clippy::cast_possible_truncation,
   1747             reason = "explained above and want a const"
   1748         )]
   1749         const Y_LEN_U8: u8 = Y_LEN as u8;
   1750         /// COSE header.
   1751         // {kty:EC2,alg:ES384,crv:P-384,x:<affine x-coordinate>,...}.
   1752         /// `kty` and `alg` come before `crv`, `x`, and `y` since map order first
   1753         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1754         /// `kty` comes before `alg` since order is done byte-wise and
   1755         /// 1 is before 3. `crv` is before `x` which is before `y` since
   1756         /// `0b001_00000` comes before `0b001_00001` which comes before
   1757         /// `0b001_00010` byte-wise.
   1758         const HEADER: [u8; 11] = [
   1759             cbor::MAP_5,
   1760             KTY,
   1761             EC2,
   1762             ALG,
   1763             cbor::NEG_INFO_24,
   1764             ES384,
   1765             CRV,
   1766             P384,
   1767             X,
   1768             cbor::BYTES_INFO_24,
   1769             X_LEN_U8,
   1770         ];
   1771         /// {...y:<affine y-coordinate>}.
   1772         const Y_META: [u8; 3] = [Y, cbor::BYTES_INFO_24, Y_LEN_U8];
   1773         cbor.split_at_checked(HEADER.len())
   1774             .ok_or(CoseKeyErr::Len)
   1775             .and_then(|(header, header_rem)| {
   1776                 if header == HEADER {
   1777                     header_rem
   1778                         .split_at_checked(X_LEN)
   1779                         .ok_or(CoseKeyErr::Len)
   1780                         .and_then(|(x, x_rem)| {
   1781                             x_rem
   1782                                 .split_at_checked(Y_META.len())
   1783                                 .ok_or(CoseKeyErr::Len)
   1784                                 .and_then(|(y_meta, y_meta_rem)| {
   1785                                     if y_meta == Y_META {
   1786                                         y_meta_rem
   1787                                             .split_at_checked(Y_LEN)
   1788                                             .ok_or(CoseKeyErr::Len)
   1789                                             .map(|(y, remaining)| CborSuccess {
   1790                                                 value: Self(x, y),
   1791                                                 remaining,
   1792                                             })
   1793                                     } else {
   1794                                         Err(CoseKeyErr::P384CoseEncoding)
   1795                                     }
   1796                                 })
   1797                         })
   1798                 } else {
   1799                     Err(CoseKeyErr::P384CoseEncoding)
   1800                 }
   1801             })
   1802     }
   1803 }
   1804 impl<'a> FromCbor<'a> for RsaPubKey<&'a [u8]> {
   1805     type Err = CoseKeyErr;
   1806     #[expect(
   1807         clippy::arithmetic_side_effects,
   1808         clippy::big_endian_bytes,
   1809         clippy::indexing_slicing,
   1810         clippy::missing_asserts_for_indexing,
   1811         reason = "comments justify their correctness"
   1812     )]
   1813     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   1814         /// `n` COSE key type parameter for [`RSA`] as defined by
   1815         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1816         const N: u8 = cbor::NEG_ONE;
   1817         /// `e` COSE key type parameter for [`RSA`] as defined by
   1818         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#key-type-parameters).
   1819         const E: u8 = cbor::NEG_TWO;
   1820         /// COSE header.
   1821         /// {kty:RSA,alg:RS256,n:<RSA modulus>,...}.
   1822         /// `kty` and `alg` come before `n` and `e` since map order first
   1823         /// is done by data type and `cbor::UINT`s come before `cbor::NEG`s.
   1824         /// `kty` comes before `alg` since order is done byte-wise and
   1825         /// 1 is before 3. `n` is before `e` since `0b001_00000` comes before
   1826         /// `0b001_00001` byte-wise.
   1827         ///
   1828         /// Note `RS256` COSE algorithm as defined by
   1829         /// [IANA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
   1830         /// is encoded as -257 which is encoded in CBOR as `[cbor::NEG_INFO_25, 1, 0]` since
   1831         /// |-257| - 1 = 256 which takes two bytes to encode as 1, 0 in big-endian.
   1832         /// Ditto for a byte string of length 0x100 to 0xFFFF inclusively replacing `cbor::NEG_INFO_25` with
   1833         /// `cbor::BYTES_INFO_25`.
   1834         ///
   1835         /// Recall that [`RsaPubKey`] requires the modulus to be at least 256 bytes in length but no greater than
   1836         /// 2048 bytes in length; thus we know a valid and allowed `n` will have length whose metadata
   1837         /// takes exactly two bytes.
   1838         const HEADER: [u8; 9] = [
   1839             cbor::MAP_4,
   1840             KTY,
   1841             RSA,
   1842             ALG,
   1843             cbor::NEG_INFO_25,
   1844             1,
   1845             0,
   1846             N,
   1847             cbor::BYTES_INFO_25,
   1848         ];
   1849         cbor.split_at_checked(HEADER.len()).ok_or(CoseKeyErr::Len).and_then(|(header, header_rem)| {
   1850             if header == HEADER {
   1851                 header_rem.split_at_checked(2).ok_or(CoseKeyErr::Len).and_then(|(n_len_slice, n_len_rem)| {
   1852                     let mut len = [0; 2];
   1853                     len.copy_from_slice(n_len_slice);
   1854                     // cbor uints are in big-endian.
   1855                     let n_len = usize::from(u16::from_be_bytes(len));
   1856                     if n_len > 255 {
   1857                         n_len_rem.split_at_checked(n_len).ok_or(CoseKeyErr::Len).and_then(|(n, n_rem)| {
   1858                             n_rem.split_at_checked(2).ok_or(CoseKeyErr::RsaCoseEncoding).and_then(|(e_meta, e_meta_rem)| {
   1859                                 // `e_meta.len() == 2`, so this is fine.
   1860                                 if e_meta[0] == E {
   1861                                     // `e_meta.len() == 2`, so this is fine.
   1862                                     let e_meta_len = e_meta[1];
   1863                                     if e_meta_len & cbor::BYTES == cbor::BYTES {
   1864                                         let e_len = usize::from(e_meta_len ^ cbor::BYTES);
   1865                                         if e_len < 5 {
   1866                                             e_meta_rem.split_at_checked(e_len).ok_or(CoseKeyErr::Len).and_then(|(e_slice, remaining)| {
   1867                                                 e_slice.first().ok_or(CoseKeyErr::Len).and_then(|e_first| {
   1868                                                     // We ensure the leading byte is not 0; otherwise the
   1869                                                     // exponent is not properly encoded. Note the exponent
   1870                                                     //  can never be 0.
   1871                                                     if *e_first > 0 {
   1872                                                         let mut e = [0; 4];
   1873                                                         // `e_slice.len()` is `e_len` which is less than 5.
   1874                                                         // We also know it is greater than 0 since `e_slice.first()` did not err.
   1875                                                         // Thus this won't `panic`.
   1876                                                         e[4 - e_len..].copy_from_slice(e_slice);
   1877                                                         Self::try_from((n, u32::from_be_bytes(e))).map_err(CoseKeyErr::RsaPubKey).map(|value| CborSuccess { value, remaining, } )
   1878                                                     } else {
   1879                                                         Err(CoseKeyErr::RsaCoseEncoding)
   1880                                                     }
   1881                                                 })
   1882                                             })
   1883                                         } else {
   1884                                             Err(CoseKeyErr::RsaExponentTooLarge)
   1885                                         }
   1886                                     } else {
   1887                                         Err(CoseKeyErr::RsaCoseEncoding)
   1888                                     }
   1889                                 } else {
   1890                                     Err(CoseKeyErr::RsaCoseEncoding)
   1891                                 }
   1892                             })
   1893                         })
   1894                     } else {
   1895                         Err(CoseKeyErr::RsaCoseEncoding)
   1896                     }
   1897                 })
   1898             } else {
   1899                 Err(CoseKeyErr::RsaCoseEncoding)
   1900             }
   1901         })
   1902     }
   1903 }
   1904 /// An alleged uncompressed public key that borrows the key data.
   1905 ///
   1906 /// Note [`Self::Ed25519`] is compressed.
   1907 #[derive(Clone, Copy, Debug)]
   1908 pub enum UncompressedPubKey<'a> {
   1909     /// An alleged Ed25519 public key.
   1910     Ed25519(Ed25519PubKey<&'a [u8]>),
   1911     /// An alleged uncompressed P-256 public key.
   1912     P256(UncompressedP256PubKey<'a>),
   1913     /// An alleged uncompressed P-384 public key.
   1914     P384(UncompressedP384PubKey<'a>),
   1915     /// An alleged RSA public key.
   1916     Rsa(RsaPubKey<&'a [u8]>),
   1917 }
   1918 impl UncompressedPubKey<'_> {
   1919     /// Validates `self` is in fact a valid public key.
   1920     ///
   1921     /// # Errors
   1922     ///
   1923     /// Errors iff `self` is not a valid public key.
   1924     #[inline]
   1925     pub fn validate(self) -> Result<(), PubKeyErr> {
   1926         match self {
   1927             Self::Ed25519(k) => k.validate(),
   1928             Self::P256(k) => k.validate(),
   1929             Self::P384(k) => k.validate(),
   1930             Self::Rsa(_) => Ok(()),
   1931         }
   1932     }
   1933     /// Transforms `self` into the compressed version that owns the data.
   1934     #[inline]
   1935     #[must_use]
   1936     pub fn into_compressed(
   1937         self,
   1938     ) -> CompressedPubKey<
   1939         [u8; ed25519_dalek::PUBLIC_KEY_LENGTH],
   1940         [u8; <NistP256 as Curve>::FieldBytesSize::INT],
   1941         [u8; <NistP384 as Curve>::FieldBytesSize::INT],
   1942         Vec<u8>,
   1943     > {
   1944         match self {
   1945             Self::Ed25519(key) => CompressedPubKey::Ed25519(key.into_owned()),
   1946             Self::P256(key) => CompressedPubKey::P256(key.into_compressed()),
   1947             Self::P384(key) => CompressedPubKey::P384(key.into_compressed()),
   1948             Self::Rsa(key) => CompressedPubKey::Rsa(key.into_owned()),
   1949         }
   1950     }
   1951 }
   1952 impl PartialEq<UncompressedPubKey<'_>> for UncompressedPubKey<'_> {
   1953     #[inline]
   1954     fn eq(&self, other: &UncompressedPubKey<'_>) -> bool {
   1955         match *self {
   1956             Self::Ed25519(k) => matches!(*other, UncompressedPubKey::Ed25519(k2) if k == k2),
   1957             Self::P256(k) => matches!(*other, UncompressedPubKey::P256(k2) if k == k2),
   1958             Self::P384(k) => matches!(*other, UncompressedPubKey::P384(k2) if k == k2),
   1959             Self::Rsa(k) => matches!(*other, UncompressedPubKey::Rsa(k2) if k == k2),
   1960         }
   1961     }
   1962 }
   1963 impl PartialEq<&UncompressedPubKey<'_>> for UncompressedPubKey<'_> {
   1964     #[inline]
   1965     fn eq(&self, other: &&UncompressedPubKey<'_>) -> bool {
   1966         *self == **other
   1967     }
   1968 }
   1969 impl PartialEq<UncompressedPubKey<'_>> for &UncompressedPubKey<'_> {
   1970     #[inline]
   1971     fn eq(&self, other: &UncompressedPubKey<'_>) -> bool {
   1972         **self == *other
   1973     }
   1974 }
   1975 impl Eq for UncompressedPubKey<'_> {}
   1976 /// An alleged compressed public key.
   1977 ///
   1978 /// Note [`Self::Rsa`] is uncompressed.
   1979 #[derive(Clone, Copy, Debug)]
   1980 pub enum CompressedPubKey<T, T2, T3, T4> {
   1981     /// An alleged Ed25519 public key.
   1982     Ed25519(Ed25519PubKey<T>),
   1983     /// An alleged compressed P-256 public key.
   1984     P256(CompressedP256PubKey<T2>),
   1985     /// An alleged compressed P-384 public key.
   1986     P384(CompressedP384PubKey<T3>),
   1987     /// An alleged RSA public key.
   1988     Rsa(RsaPubKey<T4>),
   1989 }
   1990 /// `CompressedPubKey` that owns the key data.
   1991 pub type CompressedPubKeyOwned = CompressedPubKey<
   1992     [u8; ed25519_dalek::PUBLIC_KEY_LENGTH],
   1993     [u8; <NistP256 as Curve>::FieldBytesSize::INT],
   1994     [u8; <NistP384 as Curve>::FieldBytesSize::INT],
   1995     Vec<u8>,
   1996 >;
   1997 /// `CompressedPubKey` that borrows the key data.
   1998 pub type CompressedPubKeyBorrowed<'a> = CompressedPubKey<&'a [u8], &'a [u8], &'a [u8], &'a [u8]>;
   1999 impl CompressedPubKey<&[u8], &[u8], &[u8], &[u8]> {
   2000     /// Validates `self` is in fact a valid public key.
   2001     ///
   2002     /// # Errors
   2003     ///
   2004     /// Errors iff `self` is not a valid public key.
   2005     #[inline]
   2006     pub fn validate(self) -> Result<(), PubKeyErr> {
   2007         match self {
   2008             Self::Ed25519(k) => k.validate(),
   2009             Self::P256(k) => k.validate(),
   2010             Self::P384(k) => k.validate(),
   2011             Self::Rsa(_) => Ok(()),
   2012         }
   2013     }
   2014 }
   2015 impl<'a: 'b, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>, T3: AsRef<[u8]>, T4: AsRef<[u8]>>
   2016     From<&'a CompressedPubKey<T, T2, T3, T4>>
   2017     for CompressedPubKey<&'b [u8], &'b [u8], &'b [u8], &'b [u8]>
   2018 {
   2019     #[inline]
   2020     fn from(value: &'a CompressedPubKey<T, T2, T3, T4>) -> Self {
   2021         match *value {
   2022             CompressedPubKey::Ed25519(ref val) => Self::Ed25519(Ed25519PubKey(val.0.as_ref())),
   2023             CompressedPubKey::P256(ref val) => Self::P256(CompressedP256PubKey {
   2024                 x: val.x.as_ref(),
   2025                 y_is_odd: val.y_is_odd,
   2026             }),
   2027             CompressedPubKey::P384(ref val) => Self::P384(CompressedP384PubKey {
   2028                 x: val.x.as_ref(),
   2029                 y_is_odd: val.y_is_odd,
   2030             }),
   2031             CompressedPubKey::Rsa(ref val) => Self::Rsa(RsaPubKey(val.0.as_ref(), val.1)),
   2032         }
   2033     }
   2034 }
   2035 impl<
   2036     T: PartialEq<T5>,
   2037     T5: PartialEq<T>,
   2038     T2: PartialEq<T6>,
   2039     T6: PartialEq<T2>,
   2040     T3: PartialEq<T7>,
   2041     T7: PartialEq<T3>,
   2042     T4: PartialEq<T8>,
   2043     T8: PartialEq<T4>,
   2044 > PartialEq<CompressedPubKey<T, T2, T3, T4>> for CompressedPubKey<T5, T6, T7, T8>
   2045 {
   2046     #[inline]
   2047     fn eq(&self, other: &CompressedPubKey<T, T2, T3, T4>) -> bool {
   2048         match *self {
   2049             Self::Ed25519(ref val) => {
   2050                 matches!(*other, CompressedPubKey::Ed25519(ref val2) if val == val2)
   2051             }
   2052             Self::P256(ref val) => {
   2053                 matches!(*other, CompressedPubKey::P256(ref val2) if val == val2)
   2054             }
   2055             Self::P384(ref val) => {
   2056                 matches!(*other, CompressedPubKey::P384(ref val2) if val == val2)
   2057             }
   2058             Self::Rsa(ref val) => matches!(*other, CompressedPubKey::Rsa(ref val2) if val == val2),
   2059         }
   2060     }
   2061 }
   2062 impl<
   2063     T: PartialEq<T5>,
   2064     T5: PartialEq<T>,
   2065     T2: PartialEq<T6>,
   2066     T6: PartialEq<T2>,
   2067     T3: PartialEq<T7>,
   2068     T7: PartialEq<T3>,
   2069     T4: PartialEq<T8>,
   2070     T8: PartialEq<T4>,
   2071 > PartialEq<CompressedPubKey<T, T2, T3, T4>> for &CompressedPubKey<T5, T6, T7, T8>
   2072 {
   2073     #[inline]
   2074     fn eq(&self, other: &CompressedPubKey<T, T2, T3, T4>) -> bool {
   2075         **self == *other
   2076     }
   2077 }
   2078 impl<
   2079     T: PartialEq<T5>,
   2080     T5: PartialEq<T>,
   2081     T2: PartialEq<T6>,
   2082     T6: PartialEq<T2>,
   2083     T3: PartialEq<T7>,
   2084     T7: PartialEq<T3>,
   2085     T4: PartialEq<T8>,
   2086     T8: PartialEq<T4>,
   2087 > PartialEq<&CompressedPubKey<T, T2, T3, T4>> for CompressedPubKey<T5, T6, T7, T8>
   2088 {
   2089     #[inline]
   2090     fn eq(&self, other: &&CompressedPubKey<T, T2, T3, T4>) -> bool {
   2091         *self == **other
   2092     }
   2093 }
   2094 impl<T: Eq, T2: Eq, T3: Eq, T4: Eq> Eq for CompressedPubKey<T, T2, T3, T4> {}
   2095 impl<'a> FromCbor<'a> for UncompressedPubKey<'a> {
   2096     type Err = CoseKeyErr;
   2097     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2098         // {kty:<type>...}.
   2099         cbor.get(2)
   2100             .ok_or(CoseKeyErr::Len)
   2101             .and_then(|kty| match *kty {
   2102                 OKP => Ed25519PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2103                     value: Self::Ed25519(key.value),
   2104                     remaining: key.remaining,
   2105                 }),
   2106                 // {kty:EC2,alg:ES256|ES384,...}
   2107                 EC2 => cbor.get(4).ok_or(CoseKeyErr::Len).and_then(|alg| {
   2108                     if *alg == ES256 {
   2109                         UncompressedP256PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2110                             value: Self::P256(key.value),
   2111                             remaining: key.remaining,
   2112                         })
   2113                     } else {
   2114                         UncompressedP384PubKey::from_cbor(cbor).map(|key| CborSuccess {
   2115                             value: Self::P384(key.value),
   2116                             remaining: key.remaining,
   2117                         })
   2118                     }
   2119                 }),
   2120                 RSA => RsaPubKey::from_cbor(cbor).map(|key| CborSuccess {
   2121                     value: Self::Rsa(key.value),
   2122                     remaining: key.remaining,
   2123                 }),
   2124                 _ => Err(CoseKeyErr::CoseKeyType),
   2125             })
   2126     }
   2127 }
   2128 /// Length of AAGUID.
   2129 const AAGUID_LEN: usize = 16;
   2130 /// 16 bytes representing an
   2131 /// [Authenticator Attestation Globally Unique Identifier (AAGUID)](https://www.w3.org/TR/webauthn-3/#aaguid).
   2132 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2133 pub struct Aaguid<'a>(&'a [u8]);
   2134 impl<'a> Aaguid<'a> {
   2135     /// Returns the contained data.
   2136     #[inline]
   2137     #[must_use]
   2138     pub const fn data(self) -> &'a [u8] {
   2139         self.0
   2140     }
   2141 }
   2142 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Aaguid<'b> {
   2143     type Error = AaguidErr;
   2144     #[inline]
   2145     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2146         if value.len() == AAGUID_LEN {
   2147             Ok(Self(value))
   2148         } else {
   2149             Err(AaguidErr)
   2150         }
   2151     }
   2152 }
   2153 /// [Attested credential data](https://www.w3.org/TR/webauthn-3/#attested-credential-data).
   2154 #[derive(Debug)]
   2155 pub struct AttestedCredentialData<'a> {
   2156     /// [`aaguid`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-aaguid).
   2157     pub aaguid: Aaguid<'a>,
   2158     /// [`credentialId`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialid).
   2159     pub credential_id: CredentialId<&'a [u8]>,
   2160     /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey).
   2161     pub credential_public_key: UncompressedPubKey<'a>,
   2162 }
   2163 impl<'a> FromCbor<'a> for AttestedCredentialData<'a> {
   2164     type Err = AttestedCredentialDataErr;
   2165     #[expect(clippy::big_endian_bytes, reason = "CBOR integers are big-endian")]
   2166     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2167         /// Number of bytes the `CredentialId` length is encoded into.
   2168         const CRED_LEN_LEN: usize = 2;
   2169         cbor.split_at_checked(AAGUID_LEN)
   2170             .ok_or(AttestedCredentialDataErr::Len)
   2171             .and_then(|(aaguid, aaguid_rem)| {
   2172                 aaguid_rem
   2173                     .split_at_checked(CRED_LEN_LEN)
   2174                     .ok_or(AttestedCredentialDataErr::Len)
   2175                     .and_then(|(cred_len_slice, cred_len_rem)| {
   2176                         let mut cred_len = [0; CRED_LEN_LEN];
   2177                         cred_len.copy_from_slice(cred_len_slice);
   2178                         // `credentialIdLength` is in big-endian.
   2179                         cred_len_rem
   2180                             .split_at_checked(usize::from(u16::from_be_bytes(cred_len)))
   2181                             .ok_or(AttestedCredentialDataErr::Len)
   2182                             .and_then(|(cred_id, cred_id_rem)| {
   2183                                 CredentialId::from_slice(cred_id)
   2184                                     .map_err(AttestedCredentialDataErr::CredentialId)
   2185                                     .and_then(|credential_id| {
   2186                                         UncompressedPubKey::from_cbor(cred_id_rem)
   2187                                             .map_err(AttestedCredentialDataErr::CoseKey)
   2188                                             .map(|cose| CborSuccess {
   2189                                                 value: Self {
   2190                                                     aaguid: Aaguid(aaguid),
   2191                                                     credential_id,
   2192                                                     credential_public_key: cose.value,
   2193                                                 },
   2194                                                 remaining: cose.remaining,
   2195                                             })
   2196                                     })
   2197                             })
   2198                     })
   2199             })
   2200     }
   2201 }
   2202 /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   2203 #[derive(Debug)]
   2204 pub struct AuthenticatorData<'a> {
   2205     /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash).
   2206     rp_id_hash: &'a [u8],
   2207     /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags).
   2208     flags: Flag,
   2209     /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount).
   2210     sign_count: u32,
   2211     /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata).
   2212     attested_credential_data: AttestedCredentialData<'a>,
   2213     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions).
   2214     extensions: AuthenticatorExtensionOutput,
   2215 }
   2216 impl<'a> AuthenticatorData<'a> {
   2217     /// [`rpIdHash`](https://www.w3.org/TR/webauthn-3/#authdata-rpidhash).
   2218     #[inline]
   2219     #[must_use]
   2220     pub const fn rp_id_hash(&self) -> &'a [u8] {
   2221         self.rp_id_hash
   2222     }
   2223     /// [`flags`](https://www.w3.org/TR/webauthn-3/#authdata-flags).
   2224     #[inline]
   2225     #[must_use]
   2226     pub const fn flags(&self) -> Flag {
   2227         self.flags
   2228     }
   2229     /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount).
   2230     #[inline]
   2231     #[must_use]
   2232     pub const fn sign_count(&self) -> u32 {
   2233         self.sign_count
   2234     }
   2235     /// [`attestedCredentialData`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata).
   2236     #[inline]
   2237     #[must_use]
   2238     pub const fn attested_credential_data(&self) -> &AttestedCredentialData<'a> {
   2239         &self.attested_credential_data
   2240     }
   2241     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions).
   2242     #[inline]
   2243     #[must_use]
   2244     pub const fn extensions(&self) -> AuthenticatorExtensionOutput {
   2245         self.extensions
   2246     }
   2247 }
   2248 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AuthenticatorData<'b> {
   2249     type Error = AuthenticatorDataErr;
   2250     /// Deserializes `value` based on the
   2251     /// [authenticator data structure](https://www.w3.org/TR/webauthn-3/#table-authData).
   2252     #[expect(
   2253         clippy::panic_in_result_fn,
   2254         reason = "we want to crash when there is a bug"
   2255     )]
   2256     #[inline]
   2257     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2258         Self::from_cbor(value)
   2259             .map_err(AuthenticatorDataErr::from)
   2260             .map(|success| {
   2261                 assert!(
   2262                     success.remaining.is_empty(),
   2263                     "there is a bug in AuthenticatorData::from_cbor"
   2264                 );
   2265                 success.value
   2266             })
   2267     }
   2268 }
   2269 impl<'a> AuthData<'a> for AuthenticatorData<'a> {
   2270     type UpBitErr = Infallible;
   2271     type CredData = AttestedCredentialData<'a>;
   2272     type Ext = AuthenticatorExtensionOutput;
   2273     fn contains_at_bit() -> bool {
   2274         true
   2275     }
   2276     fn user_is_not_present() -> Result<(), Self::UpBitErr> {
   2277         Ok(())
   2278     }
   2279     fn new(
   2280         rp_id_hash: &'a [u8],
   2281         flags: Flag,
   2282         sign_count: u32,
   2283         attested_credential_data: Self::CredData,
   2284         extensions: Self::Ext,
   2285     ) -> Self {
   2286         Self {
   2287             rp_id_hash,
   2288             flags,
   2289             sign_count,
   2290             attested_credential_data,
   2291             extensions,
   2292         }
   2293     }
   2294     fn rp_hash(&self) -> &'a [u8] {
   2295         self.rp_id_hash
   2296     }
   2297     fn flag(&self) -> Flag {
   2298         self.flags
   2299     }
   2300 }
   2301 /// [None](https://www.w3.org/TR/webauthn-3/#sctn-none-attestation).
   2302 struct NoneAttestation;
   2303 impl FromCbor<'_> for NoneAttestation {
   2304     type Err = AttestationErr;
   2305     fn from_cbor(cbor: &[u8]) -> Result<CborSuccess<'_, Self>, Self::Err> {
   2306         cbor.split_first()
   2307             .ok_or(AttestationErr::Len)
   2308             .and_then(|(map, remaining)| {
   2309                 if *map == cbor::MAP_0 {
   2310                     Ok(CborSuccess {
   2311                         value: Self,
   2312                         remaining,
   2313                     })
   2314                 } else {
   2315                     Err(AttestationErr::NoneFormat)
   2316                 }
   2317             })
   2318     }
   2319 }
   2320 /// A 64-byte slice that allegedly represents an Ed25519 signature.
   2321 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2322 pub struct Ed25519Signature<'a>(&'a [u8]);
   2323 impl<'a> Ed25519Signature<'a> {
   2324     /// Returns signature.
   2325     #[inline]
   2326     #[must_use]
   2327     pub const fn data(self) -> &'a [u8] {
   2328         self.0
   2329     }
   2330     /// Transforms `self` into `Signature`.
   2331     fn into_sig(self) -> Signature {
   2332         let mut sig = [0; ed25519_dalek::SIGNATURE_LENGTH];
   2333         sig.copy_from_slice(self.0);
   2334         Signature::from_bytes(&sig)
   2335     }
   2336 }
   2337 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for Ed25519Signature<'b> {
   2338     type Error = Ed25519SignatureErr;
   2339     /// Interprets `value` as an Ed25519 signature.
   2340     ///
   2341     /// # Errors
   2342     ///
   2343     /// Errors iff `value.len() != 64`.
   2344     #[inline]
   2345     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2346         if value.len() == ed25519_dalek::SIGNATURE_LENGTH {
   2347             Ok(Self(value))
   2348         } else {
   2349             Err(Ed25519SignatureErr)
   2350         }
   2351     }
   2352 }
   2353 impl<'a> FromCbor<'a> for Ed25519Signature<'a> {
   2354     type Err = AttestationErr;
   2355     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2356         // `64 as u8` is OK.
   2357         /// CBOR metadata describing the signature.
   2358         #[expect(
   2359             clippy::as_conversions,
   2360             clippy::cast_possible_truncation,
   2361             reason = "comments justify their correctness"
   2362         )]
   2363         const HEADER: [u8; 2] = [cbor::BYTES_INFO_24, ed25519_dalek::SIGNATURE_LENGTH as u8];
   2364         cbor.split_at_checked(HEADER.len())
   2365             .ok_or(AttestationErr::Len)
   2366             .and_then(|(header, header_rem)| {
   2367                 if header == HEADER {
   2368                     header_rem
   2369                         .split_at_checked(ed25519_dalek::SIGNATURE_LENGTH)
   2370                         .ok_or(AttestationErr::Len)
   2371                         .map(|(sig, remaining)| CborSuccess {
   2372                             value: Self(sig),
   2373                             remaining,
   2374                         })
   2375                 } else {
   2376                     Err(AttestationErr::PackedFormatCborEd25519Signature)
   2377                 }
   2378             })
   2379     }
   2380 }
   2381 /// An alleged DER-encoded P-256 signature using SHA-256.
   2382 struct P256DerSig<'a>(&'a [u8]);
   2383 impl<'a> FromCbor<'a> for P256DerSig<'a> {
   2384     type Err = AttestationErr;
   2385     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2386         // ```asn
   2387         // Signature ::= SEQUENCE {
   2388         //     r INTEGER,
   2389         //     s INTEGER
   2390         // }
   2391         // ```
   2392         // We assume `r` and `s` can be _any_ 32-byte unsigned integer; thus when DER-encoded without metadata,
   2393         // the number of bytes is inclusively between 1 and 33—INTEGER is a signed integer; thus a 0 byte must
   2394         // be prepended iff the high bit is 1 (i.e., 32-byte unsigned integers with the high bit set take 33 bytes).
   2395         // With metadata this makes the lengths inclusively between 3 and 35—we add 1 for the INTEGER tag and 1
   2396         // for the number of bytes it takes to encode the integer. The total encoded length is thus inclusively
   2397         // between 8 = 1 + 1 + 2(3) and 72 = 1 + 1 + 2(35)—we add 1 for the CONSTRUCTED SEQUENCE tag and 1 for
   2398         // the number of bytes to encode the sequence. Instead of handling that specific range of lengths,
   2399         // we handle all lengths inclusively between 0 and 255.
   2400         cbor.split_first()
   2401             .ok_or(AttestationErr::Len)
   2402             .and_then(|(bytes, bytes_rem)| {
   2403                 if bytes & cbor::BYTES == cbor::BYTES {
   2404                     let len_info = bytes ^ cbor::BYTES;
   2405                     match len_info {
   2406                         ..=23 => Ok((bytes_rem, len_info)),
   2407                         24 => bytes_rem.split_first().ok_or(AttestationErr::Len).and_then(
   2408                             |(&len, len_rem)| {
   2409                                 if len > 23 {
   2410                                     Ok((len_rem, len))
   2411                                 } else {
   2412                                     Err(AttestationErr::PackedFormatCborP256Signature)
   2413                                 }
   2414                             },
   2415                         ),
   2416                         _ => Err(AttestationErr::PackedFormatCborP256Signature),
   2417                     }
   2418                     .and_then(|(rem, len)| {
   2419                         rem.split_at_checked(usize::from(len))
   2420                             .ok_or(AttestationErr::Len)
   2421                             .map(|(sig, remaining)| CborSuccess {
   2422                                 value: Self(sig),
   2423                                 remaining,
   2424                             })
   2425                     })
   2426                 } else {
   2427                     Err(AttestationErr::PackedFormatCborP256Signature)
   2428                 }
   2429             })
   2430     }
   2431 }
   2432 /// An alleged DER-encoded P-384 signature using SHA-384.
   2433 struct P384DerSig<'a>(&'a [u8]);
   2434 impl<'a> FromCbor<'a> for P384DerSig<'a> {
   2435     type Err = AttestationErr;
   2436     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2437         // ```asn
   2438         // Signature ::= SEQUENCE {
   2439         //     r INTEGER,
   2440         //     s INTEGER
   2441         // }
   2442         // ```
   2443         // We assume `r` and `s` can be _any_ 48-byte unsigned integer; thus when DER-encoded without metadata,
   2444         // the number of bytes is inclusively between 1 and 49—INTEGER is a signed integer; thus a 0 byte must
   2445         // be prepended iff the high bit is 1 (i.e., 48-byte unsigned integers with the high bit set take 49 bytes).
   2446         // With metadata this makes the lengths inclusively between 3 and 51—we add 1 for the INTEGER tag and 1
   2447         // for the number of bytes it takes to encode the integer. The total encoded length is thus inclusively
   2448         // between 8 = 1 + 1 + 2(3) and 104 = 1 + 1 + 2(51)—we add 1 for the CONSTRUCTED SEQUENCE tag and 1 for
   2449         // the number of bytes to encode the sequence. Instead of handling that specific range of lengths,
   2450         // we handle all lengths inclusively between 0 and 255.
   2451         cbor.split_first()
   2452             .ok_or(AttestationErr::Len)
   2453             .and_then(|(bytes, bytes_rem)| {
   2454                 if bytes & cbor::BYTES == cbor::BYTES {
   2455                     let len_info = bytes ^ cbor::BYTES;
   2456                     match len_info {
   2457                         ..=23 => Ok((bytes_rem, len_info)),
   2458                         24 => bytes_rem.split_first().ok_or(AttestationErr::Len).and_then(
   2459                             |(&len, len_rem)| {
   2460                                 if len > 23 {
   2461                                     Ok((len_rem, len))
   2462                                 } else {
   2463                                     Err(AttestationErr::PackedFormatCborP384Signature)
   2464                                 }
   2465                             },
   2466                         ),
   2467                         _ => Err(AttestationErr::PackedFormatCborP384Signature),
   2468                     }
   2469                     .and_then(|(rem, len)| {
   2470                         rem.split_at_checked(usize::from(len))
   2471                             .ok_or(AttestationErr::Len)
   2472                             .map(|(sig, remaining)| CborSuccess {
   2473                                 value: Self(sig),
   2474                                 remaining,
   2475                             })
   2476                     })
   2477                 } else {
   2478                     Err(AttestationErr::PackedFormatCborP384Signature)
   2479                 }
   2480             })
   2481     }
   2482 }
   2483 /// An alleged RSASSA-PKCS1-v1_5 signature using SHA-256.
   2484 struct RsaPkcs1v15Sig<'a>(&'a [u8]);
   2485 impl<'a> FromCbor<'a> for RsaPkcs1v15Sig<'a> {
   2486     type Err = AttestationErr;
   2487     #[expect(clippy::big_endian_bytes, reason = "CBOR integers are big-endian")]
   2488     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2489         // RSASSA-PKCS1-v1_5 signatures are the same length as the modulus. We only allow moduli consisting of
   2490         // [`MIN_RSA_N_BYTES`] to [`MAX_RSA_N_BYTES`] bytes inclusively. This means
   2491         // all signatures will use the same CBOR metadata tag (i.e., `cbor::BYTES_INFO_25`).
   2492         cbor.split_first()
   2493             .ok_or(AttestationErr::Len)
   2494             .and_then(|(bytes, bytes_rem)| {
   2495                 if *bytes == cbor::BYTES_INFO_25 {
   2496                     bytes_rem
   2497                         .split_at_checked(2)
   2498                         .ok_or(AttestationErr::Len)
   2499                         .and_then(|(len_slice, len_rem)| {
   2500                             let mut cbor_len = [0; 2];
   2501                             cbor_len.copy_from_slice(len_slice);
   2502                             // CBOR uints are big-endian.
   2503                             let len = usize::from(u16::from_be_bytes(cbor_len));
   2504                             if len > 255 {
   2505                                 len_rem
   2506                                     .split_at_checked(len)
   2507                                     .ok_or(AttestationErr::Len)
   2508                                     .map(|(sig, remaining)| CborSuccess {
   2509                                         value: Self(sig),
   2510                                         remaining,
   2511                                     })
   2512                             } else {
   2513                                 Err(AttestationErr::PackedFormatCborRs256Signature)
   2514                             }
   2515                         })
   2516                 } else {
   2517                     Err(AttestationErr::PackedFormatCborRs256Signature)
   2518                 }
   2519             })
   2520     }
   2521 }
   2522 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation) signature.
   2523 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2524 pub enum Sig<'a> {
   2525     /// Alleged Ed25519 signature.
   2526     Ed25519(Ed25519Signature<'a>),
   2527     /// Alleged DER-encoded P-256 signature using SHA-256.
   2528     P256(&'a [u8]),
   2529     /// Alleged DER-encoded P-384 signature using SHA-384.
   2530     P384(&'a [u8]),
   2531     /// Alleged RSASSA-PKCS1-v1_5 signature using SHA-256.
   2532     Rs256(&'a [u8]),
   2533 }
   2534 /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation).
   2535 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2536 pub struct PackedAttestation<'a> {
   2537     /// [Attestation signature](https://www.w3.org/TR/webauthn-3/#attestation-signature).
   2538     pub signature: Sig<'a>,
   2539 }
   2540 impl<'a> FromCbor<'a> for PackedAttestation<'a> {
   2541     type Err = AttestationErr;
   2542     #[expect(
   2543         clippy::too_many_lines,
   2544         reason = "don't want to move code to an outer scope"
   2545     )]
   2546     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2547         /// Parses `data` as packed attestation up until `x5c` _without_ the `cbor::MAP_2` or `cbor::MAP_3` header.
   2548         fn parse_to_cert_chain(data: &[u8]) -> Result<CborSuccess<'_, Sig<'_>>, AttestationErr> {
   2549             // {"alg":CoseAlgorithmIdentifier,"sig":sig_bytes...}
   2550             /// "alg" key.
   2551             const ALG: [u8; 4] = [cbor::TEXT_3, b'a', b'l', b'g'];
   2552             /// "sig" key.
   2553             const SIG: [u8; 4] = [cbor::TEXT_3, b's', b'i', b'g'];
   2554             data.split_at_checked(ALG.len())
   2555                 .ok_or(AttestationErr::Len)
   2556                 .and_then(|(alg, alg_rem)| {
   2557                     if alg == ALG {
   2558                         alg_rem.split_first().ok_or(AttestationErr::Len).and_then(
   2559                             |(cose, cose_rem)| match *cose {
   2560                                 EDDSA => cose_rem
   2561                                     .split_at_checked(SIG.len())
   2562                                     .ok_or(AttestationErr::Len)
   2563                                     .and_then(|(sig, sig_rem)| {
   2564                                         if sig == SIG {
   2565                                             Ed25519Signature::from_cbor(sig_rem).map(|success| {
   2566                                                 CborSuccess {
   2567                                                     value: Sig::Ed25519(success.value),
   2568                                                     remaining: success.remaining,
   2569                                                 }
   2570                                             })
   2571                                         } else {
   2572                                             Err(AttestationErr::PackedFormatMissingSig)
   2573                                         }
   2574                                     }),
   2575                                 ES256 => cose_rem
   2576                                     .split_at_checked(SIG.len())
   2577                                     .ok_or(AttestationErr::Len)
   2578                                     .and_then(|(sig, sig_rem)| {
   2579                                         if sig == SIG {
   2580                                             P256DerSig::from_cbor(sig_rem).map(|success| {
   2581                                                 CborSuccess {
   2582                                                     value: Sig::P256(success.value.0),
   2583                                                     remaining: success.remaining,
   2584                                                 }
   2585                                             })
   2586                                         } else {
   2587                                             Err(AttestationErr::PackedFormatMissingSig)
   2588                                         }
   2589                                     }),
   2590                                 cbor::NEG_INFO_24 => cose_rem
   2591                                     .split_first()
   2592                                     .ok_or(AttestationErr::Len)
   2593                                     .and_then(|(len, len_rem)| {
   2594                                         if *len == ES384 {
   2595                                             len_rem
   2596                                                 .split_at_checked(SIG.len())
   2597                                                 .ok_or(AttestationErr::Len)
   2598                                                 .and_then(|(sig, sig_rem)| {
   2599                                                     if sig == SIG {
   2600                                                         P384DerSig::from_cbor(sig_rem).map(
   2601                                                             |success| CborSuccess {
   2602                                                                 value: Sig::P384(success.value.0),
   2603                                                                 remaining: success.remaining,
   2604                                                             },
   2605                                                         )
   2606                                                     } else {
   2607                                                         Err(AttestationErr::PackedFormatMissingSig)
   2608                                                     }
   2609                                                 })
   2610                                         } else {
   2611                                             Err(AttestationErr::PackedFormatUnsupportedAlg)
   2612                                         }
   2613                                     }),
   2614                                 cbor::NEG_INFO_25 => cose_rem
   2615                                     .split_at_checked(2)
   2616                                     .ok_or(AttestationErr::Len)
   2617                                     .and_then(|(len, len_rem)| {
   2618                                         if len == RS256 {
   2619                                             len_rem
   2620                                                 .split_at_checked(SIG.len())
   2621                                                 .ok_or(AttestationErr::Len)
   2622                                                 .and_then(|(sig, sig_rem)| {
   2623                                                     if sig == SIG {
   2624                                                         RsaPkcs1v15Sig::from_cbor(sig_rem).map(
   2625                                                             |success| CborSuccess {
   2626                                                                 value: Sig::Rs256(success.value.0),
   2627                                                                 remaining: success.remaining,
   2628                                                             },
   2629                                                         )
   2630                                                     } else {
   2631                                                         Err(AttestationErr::PackedFormatMissingSig)
   2632                                                     }
   2633                                                 })
   2634                                         } else {
   2635                                             Err(AttestationErr::PackedFormatUnsupportedAlg)
   2636                                         }
   2637                                     }),
   2638                                 _ => Err(AttestationErr::PackedFormatUnsupportedAlg),
   2639                             },
   2640                         )
   2641                     } else {
   2642                         Err(AttestationErr::PackedFormatMissingAlg)
   2643                     }
   2644                 })
   2645         }
   2646         cbor.split_first()
   2647             .ok_or(AttestationErr::Len)
   2648             .and_then(|(map, map_rem)| match *map {
   2649                 cbor::MAP_2 => parse_to_cert_chain(map_rem).map(|success| CborSuccess {
   2650                     value: Self {
   2651                         signature: success.value,
   2652                     },
   2653                     remaining: success.remaining,
   2654                 }),
   2655                 _ => Err(AttestationErr::PackedFormat),
   2656             })
   2657     }
   2658 }
   2659 /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids).
   2660 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2661 pub enum AttestationFormat<'a> {
   2662     /// [None](https://www.w3.org/TR/webauthn-3/#sctn-none-attestation).
   2663     None,
   2664     /// [Packed](https://www.w3.org/TR/webauthn-3/#sctn-packed-attestation).
   2665     Packed(PackedAttestation<'a>),
   2666 }
   2667 impl<'a> FromCbor<'a> for AttestationFormat<'a> {
   2668     type Err = AttestationErr;
   2669     fn from_cbor(cbor: &'a [u8]) -> Result<CborSuccess<'a, Self>, Self::Err> {
   2670         // Note we assume that cbor starts _after_ `cbor::MAP_3`.
   2671         // {"fmt":"none"|"packed", "attStmt": NoneAttestation|CborPacked, ...}.
   2672         /// "fmt" key.
   2673         const FMT: [u8; 4] = [cbor::TEXT_3, b'f', b'm', b't'];
   2674         /// "none" value.
   2675         const NONE: [u8; 5] = [cbor::TEXT_4, b'n', b'o', b'n', b'e'];
   2676         /// "packed" value.
   2677         const PACKED: [u8; 7] = [cbor::TEXT_6, b'p', b'a', b'c', b'k', b'e', b'd'];
   2678         /// "attStmt" key.
   2679         const ATT_STMT: [u8; 8] = [cbor::TEXT_7, b'a', b't', b't', b'S', b't', b'm', b't'];
   2680         cbor.split_at_checked(FMT.len())
   2681             .ok_or(AttestationErr::Len)
   2682             .and_then(|(fmt, fmt_rem)| {
   2683                 if fmt == FMT {
   2684                     fmt_rem
   2685                         .split_at_checked(NONE.len())
   2686                         .ok_or(AttestationErr::Len)
   2687                         // `PACKED.len() > NONE.len()`, so we check for `PACKED` in `and_then`.
   2688                         .and_then(|(none, none_rem)| {
   2689                             if none == NONE {
   2690                                 none_rem
   2691                                     .split_at_checked(ATT_STMT.len())
   2692                                     .ok_or(AttestationErr::Len)
   2693                                     .and_then(|(att, att_rem)| {
   2694                                         if att == ATT_STMT {
   2695                                             NoneAttestation::from_cbor(att_rem).map(|success| {
   2696                                                 CborSuccess {
   2697                                                     value: Self::None,
   2698                                                     remaining: success.remaining,
   2699                                                 }
   2700                                             })
   2701                                         } else {
   2702                                             Err(AttestationErr::MissingStatement)
   2703                                         }
   2704                                     })
   2705                             } else {
   2706                                 fmt_rem
   2707                                     .split_at_checked(PACKED.len())
   2708                                     .ok_or(AttestationErr::Len)
   2709                                     .and_then(|(packed, packed_rem)| {
   2710                                         if packed == PACKED {
   2711                                             packed_rem
   2712                                                 .split_at_checked(ATT_STMT.len())
   2713                                                 .ok_or(AttestationErr::Len)
   2714                                                 .and_then(|(att, att_rem)| {
   2715                                                     if att == ATT_STMT {
   2716                                                         PackedAttestation::from_cbor(att_rem).map(
   2717                                                             |success| CborSuccess {
   2718                                                                 value: Self::Packed(success.value),
   2719                                                                 remaining: success.remaining,
   2720                                                             },
   2721                                                         )
   2722                                                     } else {
   2723                                                         Err(AttestationErr::MissingStatement)
   2724                                                     }
   2725                                                 })
   2726                                         } else {
   2727                                             Err(AttestationErr::UnsupportedFormat)
   2728                                         }
   2729                                     })
   2730                             }
   2731                         })
   2732                 } else {
   2733                     Err(AttestationErr::MissingFormat)
   2734                 }
   2735             })
   2736     }
   2737 }
   2738 impl<'a> AttestationObject<'a> {
   2739     /// Deserializes `data` based on the
   2740     /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object)
   2741     /// returning [`Self`] and the index within `data` that the authenticator data portion
   2742     /// begins.
   2743     #[expect(single_use_lifetimes, reason = "false positive")]
   2744     #[expect(
   2745         clippy::panic_in_result_fn,
   2746         reason = "we want to crash when there is a bug"
   2747     )]
   2748     #[expect(
   2749         clippy::arithmetic_side_effects,
   2750         reason = "comment justifies its correctness"
   2751     )]
   2752     #[expect(clippy::big_endian_bytes, reason = "cbor lengths are in big-endian")]
   2753     fn parse_data<'b: 'a>(data: &'b [u8]) -> Result<(Self, usize), AttestationObjectErr> {
   2754         /// `authData` key.
   2755         const AUTH_DATA_KEY: [u8; 9] =
   2756             [cbor::TEXT_8, b'a', b'u', b't', b'h', b'D', b'a', b't', b'a'];
   2757         // {"fmt":<AttestationFormat>, "attStmt":<AttestationObject>, "authData":<AuthentcatorData>}.
   2758         data.split_first().ok_or(AttestationObjectErr::Len).and_then(|(map, map_rem)| {
   2759             if *map == cbor::MAP_3 {
   2760                 AttestationFormat::from_cbor(map_rem).map_err(AttestationObjectErr::Attestation).and_then(|att| {
   2761                     att.remaining.split_at_checked(AUTH_DATA_KEY.len()).ok_or(AttestationObjectErr::Len).and_then(|(key, key_rem)| {
   2762                         if key == AUTH_DATA_KEY {
   2763                             key_rem.split_first().ok_or(AttestationObjectErr::Len).and_then(|(bytes, bytes_rem)| {
   2764                                 if bytes & cbor::BYTES == cbor::BYTES {
   2765                                     match bytes ^ cbor::BYTES {
   2766                                         24 => bytes_rem.split_first().ok_or(AttestationObjectErr::Len).and_then(|(&len, auth_data)| {
   2767                                             if len > 23 {
   2768                                                 Ok((usize::from(len), auth_data))
   2769                                             } else {
   2770                                                 Err(AttestationObjectErr::AuthDataLenInfo)
   2771                                             }
   2772                                         }),
   2773                                         25 => bytes_rem.split_at_checked(2).ok_or(AttestationObjectErr::Len).and_then(|(len_slice, auth_data)| {
   2774                                             let mut auth_len = [0; 2];
   2775                                             auth_len.copy_from_slice(len_slice);
   2776                                             // Length is encoded as big-endian.
   2777                                             let len = usize::from(u16::from_be_bytes(auth_len));
   2778                                             if len > 255 {
   2779                                                 Ok((len, auth_data))
   2780                                             } else {
   2781                                                 Err(AttestationObjectErr::AuthDataLenInfo)
   2782                                             }
   2783                                         }),
   2784                                         _ => Err(AttestationObjectErr::AuthDataLenInfo),
   2785                                     }.and_then(|(cbor_auth_data_len, auth_slice)| {
   2786                                         if cbor_auth_data_len == auth_slice.len() {
   2787                                             AuthenticatorData::from_cbor(auth_slice).map_err(|e| AttestationObjectErr::AuthData(e.into())).and_then(|auth_data| {
   2788                                                 assert!(auth_data.remaining.is_empty(), "there is a bug in AuthenticatorData::from_cbor");
   2789                                                 match att.value {
   2790                                                     AttestationFormat::None => Ok(()),
   2791                                                     AttestationFormat::Packed(ref val) => match val.signature {
   2792                                                         Sig::Ed25519(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::Ed25519(_)) {
   2793                                                             Ok(())
   2794                                                         } else {
   2795                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   2796                                                         },
   2797                                                         Sig::P256(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::P256(_)) {
   2798                                                             Ok(())
   2799                                                         } else {
   2800                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   2801                                                         },
   2802                                                         Sig::P384(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::P384(_)) {
   2803                                                             Ok(())
   2804                                                         } else {
   2805                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   2806                                                         },
   2807                                                         Sig::Rs256(_) => if matches!(auth_data.value.attested_credential_data.credential_public_key, UncompressedPubKey::Rsa(_)) {
   2808                                                             Ok(())
   2809                                                         } else {
   2810                                                             Err(AttestationObjectErr::SelfAttestationAlgorithmMismatch)
   2811                                                         },
   2812                                                     },
   2813                                                     // `cbor_auth_data_len == `auth_slice.len()` and `auth_slice.len() < data.len()`, so underflow won't happen.
   2814                                                 }.map(|()| (Self { attestation: att.value, auth_data: auth_data.value }, data.len() - cbor_auth_data_len))
   2815                                             })
   2816                                         } else {
   2817                                             Err(AttestationObjectErr::CborAuthDataLenMismatch)
   2818                                         }
   2819                                     })
   2820                                 } else {
   2821                                     Err(AttestationObjectErr::AuthDataType)
   2822                                 }
   2823                             })
   2824                         } else {
   2825                             Err(AttestationObjectErr::MissingAuthData)
   2826                         }
   2827                     })
   2828                 })
   2829             } else {
   2830                 Err(AttestationObjectErr::NotAMapOf3)
   2831             }
   2832         })
   2833     }
   2834 }
   2835 /// [Attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object).
   2836 #[derive(Debug)]
   2837 pub struct AttestationObject<'a> {
   2838     /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids).
   2839     attestation: AttestationFormat<'a>,
   2840     /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   2841     auth_data: AuthenticatorData<'a>,
   2842 }
   2843 impl<'a> AttestationObject<'a> {
   2844     /// [Attestation statement format identifiers](https://www.w3.org/TR/webauthn-3/#sctn-attstn-fmt-ids).
   2845     #[inline]
   2846     #[must_use]
   2847     pub const fn attestation(&self) -> AttestationFormat<'a> {
   2848         self.attestation
   2849     }
   2850     /// [Authenticator data](https://www.w3.org/TR/webauthn-3/#authenticator-data).
   2851     #[inline]
   2852     #[must_use]
   2853     pub const fn auth_data(&self) -> &AuthenticatorData<'a> {
   2854         &self.auth_data
   2855     }
   2856 }
   2857 impl<'a: 'b, 'b> TryFrom<&'a [u8]> for AttestationObject<'b> {
   2858     type Error = AttestationObjectErr;
   2859     /// Deserializes `value` based on the
   2860     /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object).
   2861     #[inline]
   2862     fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
   2863         Self::parse_data(value).map(|(val, _)| val)
   2864     }
   2865 }
   2866 impl<'a> AuthDataContainer<'a> for AttestationObject<'a> {
   2867     type Auth = AuthenticatorData<'a>;
   2868     type Err = AttestationObjectErr;
   2869     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   2870     #[expect(clippy::indexing_slicing, reason = "comment justifies its correctness")]
   2871     fn from_data(data: &'a [u8]) -> Result<ParsedAuthData<'a, Self>, Self::Err> {
   2872         // `data.len().checked_sub(Sha256::output_size())` is clearly less than `data.len()`;
   2873         // thus indexing wont `panic`.
   2874         Self::parse_data(&data[..data.len().checked_sub(Sha256::output_size()).unwrap_or_else(|| unreachable!("AttestationObject::from_data must be passed a slice with 32 bytes of trailing data"))]).map(|(attest, auth_idx)| ParsedAuthData { data: attest, auth_data_and_32_trailing_bytes: &data[auth_idx..], })
   2875     }
   2876     fn authenticator_data(&self) -> &Self::Auth {
   2877         &self.auth_data
   2878     }
   2879 }
   2880 /// [`AuthenticatorAttestationResponse`](https://www.w3.org/TR/webauthn-3/#authenticatorattestationresponse).
   2881 #[derive(Debug)]
   2882 pub struct AuthenticatorAttestation {
   2883     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson).
   2884     client_data_json: Vec<u8>,
   2885     /// [attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object) followed by the SHA-256 hash
   2886     /// of [`Self::client_data_json`].
   2887     attestation_object_and_c_data_hash: Vec<u8>,
   2888     /// [`getTransports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports).
   2889     transports: AuthTransports,
   2890 }
   2891 impl AuthenticatorAttestation {
   2892     /// [`clientDataJSON`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorresponse-clientdatajson).
   2893     #[inline]
   2894     #[must_use]
   2895     pub const fn client_data_json(&self) -> &[u8] {
   2896         self.client_data_json.as_slice()
   2897     }
   2898     /// [attestation object](https://www.w3.org/TR/webauthn-3/#attestation-object).
   2899     #[expect(
   2900         clippy::arithmetic_side_effects,
   2901         clippy::indexing_slicing,
   2902         reason = "comment justifies their correctness"
   2903     )]
   2904     #[inline]
   2905     #[must_use]
   2906     pub fn attestation_object(&self) -> &[u8] {
   2907         // We only allow creation via [`Self::new`] which creates [`Self::attestation_object_and_c_data_hash`]
   2908         // by appending the SHA-256 hash of [`Self::client_data_json`] to the attestation object that was passed;
   2909         // thus indexing is fine and subtraction won't cause underflow.
   2910         &self.attestation_object_and_c_data_hash
   2911             [..self.attestation_object_and_c_data_hash.len() - Sha256::output_size()]
   2912     }
   2913     /// [`getTransports`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattestationresponse-gettransports).
   2914     #[inline]
   2915     #[must_use]
   2916     pub const fn transports(&self) -> AuthTransports {
   2917         self.transports
   2918     }
   2919     /// Constructs an instance of `Self` with the contained data.
   2920     ///
   2921     /// Note calling code is encouraged to ensure `attestation_object` has at least 32 bytes
   2922     /// of available capacity; if not, a reallocation will occur which may hinder performance
   2923     /// depending on its size.
   2924     #[inline]
   2925     #[must_use]
   2926     pub fn new(
   2927         client_data_json: Vec<u8>,
   2928         mut attestation_object: Vec<u8>,
   2929         transports: AuthTransports,
   2930     ) -> Self {
   2931         attestation_object.extend_from_slice(&Sha256::digest(client_data_json.as_slice()));
   2932         Self {
   2933             client_data_json,
   2934             attestation_object_and_c_data_hash: attestation_object,
   2935             transports,
   2936         }
   2937     }
   2938 }
   2939 impl AuthResponse for AuthenticatorAttestation {
   2940     type Auth<'a>
   2941         = AttestationObject<'a>
   2942     where
   2943         Self: 'a;
   2944     type CredKey<'a> = ();
   2945     #[expect(clippy::unreachable, reason = "when there is a bug, we want to crash")]
   2946     fn parse_data_and_verify_sig(
   2947         &self,
   2948         (): Self::CredKey<'_>,
   2949         relaxed: bool,
   2950     ) -> Result<
   2951         (CollectedClientData<'_>, Self::Auth<'_>),
   2952         AuthRespErr<<Self::Auth<'_> as AuthDataContainer<'_>>::Err>,
   2953     > {
   2954         if relaxed {
   2955             #[cfg(not(feature = "serde_relaxed"))]
   2956             unreachable!("AuthenticatorAttestation::parse_data_and_verify_sig: must be passed false when serde_relaxed is not enabled");
   2957             #[cfg(feature = "serde_relaxed")]
   2958             CollectedClientData::from_client_data_json_relaxed::<true>(
   2959                 self.client_data_json.as_slice(),
   2960             ).map_err(AuthRespErr::CollectedClientDataRelaxed)
   2961         } else {
   2962             CollectedClientData::from_client_data_json::<true>(self.client_data_json.as_slice()).map_err(AuthRespErr::CollectedClientData)
   2963         }
   2964         .and_then(|client_data_json| {
   2965             Self::Auth::from_data(self.attestation_object_and_c_data_hash.as_slice())
   2966                 .map_err(AuthRespErr::Auth)
   2967                 .and_then(|val| {
   2968                     match val.data.auth_data.attested_credential_data.credential_public_key {
   2969                         UncompressedPubKey::Ed25519(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| {
   2970                             match val.data.attestation {
   2971                                 AttestationFormat::None => Ok(()),
   2972                                 AttestationFormat::Packed(packed) => match packed.signature {
   2973                                     // We don't need to use `VerifyingKey::verify_strict` since
   2974                                     // `Ed25519PubKey::into_ver_key` verifies the public key is not
   2975                                     // in the small-order subgroup. `VerifyingKey::verify_strict` additionally
   2976                                     // ensures _R_ of the signature is not in the small-order subgroup, but this
   2977                                     // doesn't provide additional benefits and is still not enough to comply
   2978                                     // with standards like RFC 8032 or NIST SP 800-186.
   2979                                     Sig::Ed25519(sig) => ver_key.verify(val.auth_data_and_32_trailing_bytes, &sig.into_sig()).map_err(|_e| AuthRespErr::Signature),
   2980                                     Sig::P256(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   2981                                 }
   2982                             }
   2983                         }),
   2984                         UncompressedPubKey::P256(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| {
   2985                             match val.data.attestation {
   2986                                 AttestationFormat::None => Ok(()),
   2987                                 AttestationFormat::Packed(packed) => match packed.signature {
   2988                                     Sig::P256(sig) => P256Sig::from_bytes(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   2989                                     Sig::Ed25519(_) | Sig::P384(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   2990                                 }
   2991                             }
   2992                         }),
   2993                         UncompressedPubKey::P384(key) => key.into_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| {
   2994                             match val.data.attestation {
   2995                                 AttestationFormat::None => Ok(()),
   2996                                 AttestationFormat::Packed(packed) => match packed.signature {
   2997                                     Sig::P384(sig) => P384Sig::from_bytes(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   2998                                     Sig::Ed25519(_) | Sig::P256(_) | Sig::Rs256(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   2999                                 }
   3000                             }
   3001                         }),
   3002                         UncompressedPubKey::Rsa(key) => match val.data.attestation {
   3003                             AttestationFormat::None => Ok(()),
   3004                             AttestationFormat::Packed(packed) => match packed.signature {
   3005                                 Sig::Rs256(sig) => pkcs1v15::Signature::try_from(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| key.as_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)),
   3006                                 Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) => unreachable!("there is a bug in AttestationObject::from_data"),
   3007                             }
   3008                         },
   3009                     }.map(|()| (client_data_json, val.data))
   3010                 })
   3011         })
   3012     }
   3013 }
   3014 /// [`CredentialPropertiesOutput`](https://www.w3.org/TR/webauthn-3/#dictdef-credentialpropertiesoutput).
   3015 ///
   3016 /// Note [`Self::rk`] is frequently unreliable. For example there are times it is `Some(false)` despite the
   3017 /// credential being stored client-side. One may have better luck checking if [`AuthTransports::contains`]
   3018 /// [`AuthenticatorTransport::Internal`] and using that as an indicator if a client-side credential was created.
   3019 #[derive(Clone, Copy, Debug)]
   3020 pub struct CredentialPropertiesOutput {
   3021     /// [`rk`](https://www.w3.org/TR/webauthn-3/#dom-credentialpropertiesoutput-rk).
   3022     pub rk: Option<bool>,
   3023 }
   3024 /// [`AuthenticationExtensionsPRFOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfoutputs).
   3025 ///
   3026 /// Note since this is a server-side library, we don't store
   3027 /// [`results`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-results)
   3028 /// since it contains sensitive data that should remain client-side.
   3029 #[derive(Clone, Copy, Debug)]
   3030 pub struct AuthenticationExtensionsPrfOutputs {
   3031     /// [`enabled`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsprfoutputs-enabled).
   3032     pub enabled: bool,
   3033 }
   3034 /// [`AuthenticationExtensionsClientOutputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientoutputs).
   3035 #[derive(Clone, Copy, Debug)]
   3036 pub struct ClientExtensionsOutputs {
   3037     /// [`credProps`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-credprops).
   3038     pub cred_props: Option<CredentialPropertiesOutput>,
   3039     /// [`prf`](https://www.w3.org/TR/webauthn-3/#dom-authenticationextensionsclientoutputs-prf).
   3040     pub prf: Option<AuthenticationExtensionsPrfOutputs>,
   3041 }
   3042 /// [`ClientExtensionsOutputs`] extensions that are saved in [`Metadata`] because they are purely informative
   3043 /// and not used during authentication ceremonies.
   3044 #[derive(Clone, Copy, Debug)]
   3045 pub struct ClientExtensionsOutputsMetadata {
   3046     /// [`ClientExtensionsOutputs::cred_props`].
   3047     pub cred_props: Option<CredentialPropertiesOutput>,
   3048 }
   3049 /// [`ClientExtensionsOutputs`] extensions that are saved in [`StaticState`] because they are used during
   3050 /// authentication ceremonies.
   3051 #[derive(Clone, Copy, Debug)]
   3052 pub struct ClientExtensionsOutputsStaticState {
   3053     /// [`ClientExtensionsOutputs::prf`].
   3054     pub prf: Option<AuthenticationExtensionsPrfOutputs>,
   3055 }
   3056 impl From<ClientExtensionsOutputs> for ClientExtensionsOutputsMetadata {
   3057     #[inline]
   3058     fn from(value: ClientExtensionsOutputs) -> Self {
   3059         Self {
   3060             cred_props: value.cred_props,
   3061         }
   3062     }
   3063 }
   3064 impl From<ClientExtensionsOutputs> for ClientExtensionsOutputsStaticState {
   3065     #[inline]
   3066     fn from(value: ClientExtensionsOutputs) -> Self {
   3067         Self { prf: value.prf }
   3068     }
   3069 }
   3070 /// [`PublicKeyCredential`](https://www.w3.org/TR/webauthn-3/#iface-pkcredential) for registration ceremonies.
   3071 #[expect(
   3072     clippy::field_scoped_visibility_modifiers,
   3073     reason = "no invariants to uphold"
   3074 )]
   3075 #[derive(Debug)]
   3076 pub struct Registration {
   3077     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response).
   3078     pub(crate) response: AuthenticatorAttestation,
   3079     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment).
   3080     pub(crate) authenticator_attachment: AuthenticatorAttachment,
   3081     /// [`getClientExtensionResults()`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults).
   3082     pub(crate) client_extension_results: ClientExtensionsOutputs,
   3083 }
   3084 impl Registration {
   3085     /// [`response`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-response).
   3086     #[inline]
   3087     #[must_use]
   3088     pub const fn response(&self) -> &AuthenticatorAttestation {
   3089         &self.response
   3090     }
   3091     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment).
   3092     #[inline]
   3093     #[must_use]
   3094     pub const fn authenticator_attachment(&self) -> AuthenticatorAttachment {
   3095         self.authenticator_attachment
   3096     }
   3097     /// [`getClientExtensionResults()`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults).
   3098     #[inline]
   3099     #[must_use]
   3100     pub const fn client_extension_results(&self) -> ClientExtensionsOutputs {
   3101         self.client_extension_results
   3102     }
   3103     /// Constructs a `Registration` based on the passed arguments.
   3104     #[cfg(feature = "custom")]
   3105     #[inline]
   3106     #[must_use]
   3107     pub const fn new(
   3108         response: AuthenticatorAttestation,
   3109         authenticator_attachment: AuthenticatorAttachment,
   3110         client_extension_results: ClientExtensionsOutputs,
   3111     ) -> Self {
   3112         Self {
   3113             response,
   3114             authenticator_attachment,
   3115             client_extension_results,
   3116         }
   3117     }
   3118     /// Returns the associated `SentChallenge`.
   3119     ///
   3120     /// This is useful when wanting to extract the corresponding [`RegistrationServerState`] from
   3121     /// an in-memory collection (e.g., [`MaxLenHashSet`]) or storage.
   3122     ///
   3123     /// Note if [`CollectedClientData::from_client_data_json`] returns `Ok`, then this will return
   3124     /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse
   3125     /// is _not_ true. This is because this function parses the minimal amount of data possible.
   3126     ///
   3127     /// # Errors
   3128     ///
   3129     /// Errors iff [`AuthenticatorAttestation::client_data_json`] does not contain a base64url-encoded
   3130     /// [`Challenge`] in the required position.
   3131     #[inline]
   3132     pub fn challenge(&self) -> Result<SentChallenge, CollectedClientDataErr> {
   3133         LimitedVerificationParser::<true>::get_sent_challenge(
   3134             self.response.client_data_json.as_slice(),
   3135         )
   3136     }
   3137     /// Returns the associated `SentChallenge`.
   3138     ///
   3139     /// This is useful when wanting to extract the corresponding [`RegistrationServerState`] from
   3140     /// an in-memory collection (e.g., [`MaxLenHashSet`]) or storage.
   3141     ///
   3142     /// Note if [`CollectedClientData::from_client_data_json_relaxed`] returns `Ok`, then this will return
   3143     /// `Ok` containing the same value as [`CollectedClientData::challenge`]; however the converse
   3144     /// is _not_ true. This is because this function attempts to reduce the amount of data parsed.
   3145     ///
   3146     /// # Errors
   3147     ///
   3148     /// Errors iff [`AuthenticatorAttestation::client_data_json`] is invalid JSON _after_ ignoring
   3149     /// a leading U+FEFF and replacing any sequences of invalid UTF-8 code units with U+FFFD or
   3150     /// [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-collectedclientdata-challenge) does not exist
   3151     /// or is not a base64url-encoded [`Challenge`].
   3152     #[cfg(feature = "serde_relaxed")]
   3153     #[inline]
   3154     pub fn challenge_relaxed(&self) -> Result<SentChallenge, SerdeJsonErr> {
   3155         RelaxedClientDataJsonParser::<true>::get_sent_challenge(
   3156             self.response.client_data_json.as_slice(),
   3157         )
   3158     }
   3159     /// Convenience function for [`RegistrationRelaxed::deserialize`].
   3160     ///
   3161     /// # Errors
   3162     ///
   3163     /// Errors iff [`RegistrationRelaxed::deserialize`] does.
   3164     #[cfg(feature = "serde_relaxed")]
   3165     #[inline]
   3166     pub fn from_json_relaxed(json: &[u8]) -> Result<Self, SerdeJsonErr> {
   3167         serde_json::from_slice::<RegistrationRelaxed>(json).map(|val| val.0)
   3168     }
   3169     /// Convenience function for [`CustomRegistration::deserialize`].
   3170     ///
   3171     /// # Errors
   3172     ///
   3173     /// Errors iff [`CustomRegistration::deserialize`] does.
   3174     #[cfg(feature = "serde_relaxed")]
   3175     #[inline]
   3176     pub fn from_json_custom(json: &[u8]) -> Result<Self, SerdeJsonErr> {
   3177         serde_json::from_slice::<CustomRegistration>(json).map(|val| val.0)
   3178     }
   3179 }
   3180 impl Response for Registration {
   3181     type Auth = AuthenticatorAttestation;
   3182     fn auth(&self) -> &Self::Auth {
   3183         &self.response
   3184     }
   3185 }
   3186 /// [Attestation statement](https://www.w3.org/TR/webauthn-3/#attestation-statement).
   3187 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   3188 pub enum Attestation {
   3189     /// [None](https://www.w3.org/TR/webauthn-3/#none).
   3190     None,
   3191     /// [Self](https://www.w3.org/TR/webauthn-3/#self).
   3192     Surrogate,
   3193 }
   3194 /// Metadata associated with a [`RegisteredCredential`].
   3195 ///
   3196 /// This information exists purely for informative reasons as it is not used in any way during authentication
   3197 /// ceremonies; consequently, one may not want to store this information.
   3198 #[derive(Clone, Copy, Debug)]
   3199 pub struct Metadata<'a> {
   3200     /// [Attestation statement](https://www.w3.org/TR/webauthn-3/#attestation-statement).
   3201     pub attestation: Attestation,
   3202     /// [`aaguid`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-aaguid).
   3203     pub aaguid: Aaguid<'a>,
   3204     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) output during registration that is
   3205     /// never used during authentication ceremonies.
   3206     pub extensions: AuthenticatorExtensionOutputMetadata,
   3207     /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults)
   3208     /// output during registration that is never used during authentication ceremonies.
   3209     pub client_extension_results: ClientExtensionsOutputsMetadata,
   3210     /// `ResidentKeyRequirement` sent during registration.
   3211     pub resident_key: ResidentKeyRequirement,
   3212 }
   3213 impl Metadata<'_> {
   3214     /// Transforms `self` into a JSON object conforming to the following pseudo-schema:
   3215     ///
   3216     /// ```json
   3217     /// // MetadataJSON:
   3218     /// {
   3219     ///   "attestation": "none" | "self",
   3220     ///   "aaguid": "<32-uppercase-hexadecimal digits>",
   3221     ///   "extensions": {
   3222     ///     "min_pin_length": null | <8-bit unsigned integer without leading 0s>
   3223     ///   },
   3224     ///   "client_extension_results": {
   3225     ///     "cred_props": null | CredPropsJSON
   3226     ///   },
   3227     ///   "resident_key": "required" | "discouraged" | "preferred"
   3228     /// }
   3229     /// // CredPropsJSON:
   3230     /// {
   3231     ///   "rk": null | false | true
   3232     /// }
   3233     /// // PrfJSON:
   3234     /// {
   3235     ///   "enabled": false | true
   3236     /// }
   3237     /// ```
   3238     /// where unnecessary whitespace does not exist.
   3239     ///
   3240     /// This primarily exists so that one can store the data in a human-readable way without the need of
   3241     /// bringing the data back into the application. This allows one to read the data using some external
   3242     /// means purely for informative reasons. If one wants the ability to "act" on the data; then one should
   3243     /// [`Metadata::encode`] the data instead, or in addition to, that way it can be [`MetadataOwned::decode`]d.
   3244     ///
   3245     /// # Examples
   3246     ///
   3247     /// ```
   3248     /// # use core::str::FromStr;
   3249     /// # use webauthn_rp::{
   3250     /// #     request::register::{FourToSixtyThree, ResidentKeyRequirement},
   3251     /// #     response::register::{
   3252     /// #         Aaguid, Attestation,
   3253     /// #         AuthenticatorExtensionOutputMetadata, ClientExtensionsOutputsMetadata, CredentialPropertiesOutput,
   3254     /// #         Metadata,
   3255     /// #     },
   3256     /// # };
   3257     /// let metadata = Metadata {
   3258     ///     attestation: Attestation::None,
   3259     ///     aaguid: Aaguid::try_from([15; 16].as_slice())?,
   3260     ///     extensions: AuthenticatorExtensionOutputMetadata {
   3261     ///         min_pin_length: Some(FourToSixtyThree::Sixteen),
   3262     ///     },
   3263     ///     client_extension_results: ClientExtensionsOutputsMetadata {
   3264     ///         cred_props: Some(CredentialPropertiesOutput {
   3265     ///             rk: Some(true),
   3266     ///         }),
   3267     ///     },
   3268     ///     resident_key: ResidentKeyRequirement::Required
   3269     /// };
   3270     /// let json = serde_json::json!({
   3271     ///     "attestation": "none",
   3272     ///     "aaguid": "0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F",
   3273     ///     "extensions": {
   3274     ///         "min_pin_length": 16
   3275     ///     },
   3276     ///     "client_extension_results": {
   3277     ///         "cred_props": {
   3278     ///             "rk": true
   3279     ///         }
   3280     ///     },
   3281     ///     "resident_key": "required"
   3282     /// });
   3283     /// assert_eq!(metadata.into_json(), json.to_string());
   3284     /// # Ok::<_, webauthn_rp::AggErr>(())
   3285     /// ```
   3286     #[expect(unsafe_code, reason = "comment justifies its correctness and reason")]
   3287     #[expect(
   3288         clippy::arithmetic_side_effects,
   3289         clippy::integer_division,
   3290         clippy::integer_division_remainder_used,
   3291         reason = "comments justify their correctness"
   3292     )]
   3293     #[inline]
   3294     #[must_use]
   3295     pub fn into_json(self) -> String {
   3296         // Maximum capacity needed is not _that_ much larger than the minimum, 173. An example is the
   3297         // following:
   3298         // `{"attestation":"none","aaguid":"00000000000000000000000000000000","extensions":{"min_pin_length":null},"client_extension_results":{"cred_props":{"rk":false},"prf":{"enabled":false}},"resident_key":"discouraged"}`.
   3299         // We use a raw `Vec` instead of a `String` since we need to transform some binary values into ASCII which
   3300         // is easier to do as bytes.
   3301         let mut buffer = Vec::with_capacity(187);
   3302         buffer.extend_from_slice(br#"{"attestation":"#);
   3303         buffer.extend_from_slice(match self.attestation {
   3304             Attestation::None => br#""none","aaguid":""#,
   3305             Attestation::Surrogate => br#""self","aaguid":""#,
   3306         });
   3307         self.aaguid.0.iter().fold((), |(), byt| {
   3308             // Get the first nibble.
   3309             let nib_fst = byt & 0xf0;
   3310             // We simply add the appropriate offset. For decimal digits this means simply adding `b'0'`; but
   3311             // for uppercase hexadecimal, this means adding 55 since `b'A'` is 65.
   3312             // Overflow cannot occur since this maxes at `b'F'`.
   3313             buffer.push(nib_fst + if nib_fst < 0xa { b'0' } else { 55 });
   3314             // Get the second nibble.
   3315             let nib_snd = byt & 0xf;
   3316             // We simply add the appropriate offset. For decimal digits this means simply adding `b'0'`; but
   3317             // for uppercase hexadecimal, this means adding 55 since `b'A'` is 65.
   3318             // Overflow cannot occur since this maxes at `b'F'`.
   3319             buffer.push(nib_snd + if nib_snd < 0xa { b'0' } else { 55 });
   3320         });
   3321         buffer.extend_from_slice(br#"","extensions":{"min_pin_length":"#);
   3322         match self.extensions.min_pin_length {
   3323             None => buffer.extend_from_slice(b"null"),
   3324             Some(pin) => {
   3325                 // Clearly correct.
   3326                 let dig_1 = pin.into_u8() / 10;
   3327                 // Clearly correct.
   3328                 let dig_2 = pin.into_u8() % 10;
   3329                 if dig_1 > 0 {
   3330                     // We simply add the appropriate offset which is `b'0` for decimal digits.
   3331                     // Overflow cannot occur since this maxes at `b'9'`.
   3332                     buffer.push(dig_1 + b'0');
   3333                 }
   3334                 // We simply add the appropriate offset which is `b'0` for decimal digits.
   3335                 // Overflow cannot occur since this maxes at `b'9'`.
   3336                 buffer.push(dig_2 + b'0');
   3337             }
   3338         }
   3339         buffer.extend_from_slice(br#"},"client_extension_results":{"cred_props":"#);
   3340         match self.client_extension_results.cred_props {
   3341             None => buffer.extend_from_slice(b"null"),
   3342             Some(props) => {
   3343                 buffer.extend_from_slice(br#"{"rk":"#);
   3344                 match props.rk {
   3345                     None => buffer.extend_from_slice(b"null}"),
   3346                     Some(rk) => buffer.extend_from_slice(if rk { b"true}" } else { b"false}" }),
   3347                 }
   3348             }
   3349         }
   3350         buffer.extend_from_slice(br#"},"resident_key":"#);
   3351         buffer.extend_from_slice(match self.resident_key {
   3352             ResidentKeyRequirement::Required => br#""required"}"#,
   3353             ResidentKeyRequirement::Discouraged => br#""discouraged"}"#,
   3354             ResidentKeyRequirement::Preferred => br#""preferred"}"#,
   3355         });
   3356         // SAFETY:
   3357         // Clearly above only appends ASCII, a subset of UTF-8, to `buffer`; thus `buffer`
   3358         // is valid UTF-8.
   3359         unsafe { String::from_utf8_unchecked(buffer) }
   3360     }
   3361     /// Transforms `self` into an "owned" version.
   3362     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
   3363     #[cfg(feature = "bin")]
   3364     #[inline]
   3365     #[must_use]
   3366     pub fn into_owned(self) -> MetadataOwned {
   3367         MetadataOwned {
   3368             attestation: self.attestation,
   3369             aaguid: AaguidOwned(self.aaguid.0.try_into().unwrap_or_else(|_e| {
   3370                 unreachable!(
   3371                     "there is a bug in Metadata that allows AAGUID to not have length of 16"
   3372                 )
   3373             })),
   3374             extensions: self.extensions,
   3375             client_extension_results: self.client_extension_results,
   3376             resident_key: self.resident_key,
   3377         }
   3378     }
   3379 }
   3380 /// [`RegisteredCredential`] and [`AuthenticatedCredential`] static state.
   3381 ///
   3382 /// `PublicKey` needs to be [`UncompressedPubKey`] or [`CompressedPubKey`] for this type to be of any use.
   3383 #[derive(Clone, Copy, Debug)]
   3384 pub struct StaticState<PublicKey> {
   3385     /// [`credentialPublicKey`](https://www.w3.org/TR/webauthn-3/#authdata-attestedcredentialdata-credentialpublickey).
   3386     pub credential_public_key: PublicKey,
   3387     /// [`extensions`](https://www.w3.org/TR/webauthn-3/#authdata-extensions) output during registration that are
   3388     /// used during authentication ceremonies.
   3389     pub extensions: AuthenticatorExtensionOutputStaticState,
   3390     /// [`getClientExtensionResults`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-getclientextensionresults)
   3391     /// output during registration that are used during authentication ceremonies.
   3392     pub client_extension_results: ClientExtensionsOutputsStaticState,
   3393 }
   3394 impl<'a: 'b, 'b, T: AsRef<[u8]>, T2: AsRef<[u8]>, T3: AsRef<[u8]>, T4: AsRef<[u8]>>
   3395     From<&'a StaticState<CompressedPubKey<T, T2, T3, T4>>>
   3396     for StaticState<CompressedPubKey<&'b [u8], &'b [u8], &'b [u8], &'b [u8]>>
   3397 {
   3398     #[inline]
   3399     fn from(value: &'a StaticState<CompressedPubKey<T, T2, T3, T4>>) -> Self {
   3400         Self {
   3401             credential_public_key: (&value.credential_public_key).into(),
   3402             extensions: value.extensions,
   3403             client_extension_results: value.client_extension_results,
   3404         }
   3405     }
   3406 }
   3407 /// `StaticState` with an uncompressed [`Self::credential_public_key`].
   3408 pub type StaticStateUncompressed<'a> = StaticState<UncompressedPubKey<'a>>;
   3409 /// `StaticState` with a compressed [`Self::credential_public_key`] that owns the key data.
   3410 pub type StaticStateCompressed = StaticState<CompressedPubKeyOwned>;
   3411 impl StaticStateUncompressed<'_> {
   3412     /// Transforms `self` into `StaticState` that contains the compressed version of the public key.
   3413     #[inline]
   3414     #[must_use]
   3415     pub fn into_compressed(self) -> StaticStateCompressed {
   3416         StaticStateCompressed {
   3417             credential_public_key: self.credential_public_key.into_compressed(),
   3418             extensions: self.extensions,
   3419             client_extension_results: self.client_extension_results,
   3420         }
   3421     }
   3422 }
   3423 /// [`RegisteredCredential`] and [`AuthenticatedCredential`] dynamic state.
   3424 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   3425 pub struct DynamicState {
   3426     /// [`UV`](https://www.w3.org/TR/webauthn-3/#authdata-flags-uv).
   3427     ///
   3428     /// Once this is `true`, it will remain `true`. It will be set to `true` when `false` iff
   3429     /// [`Flag::user_verified`] and [`AuthenticationVerificationOptions::update_uv`]. In other words, this only
   3430     /// means that the user has been verified _at some point in the past_; but it does _not_ mean the user has
   3431     /// most-recently been verified this is because user verification is a _ceremony-specific_ property. To
   3432     /// enforce user verification for all ceremonies, [`UserVerificationRequirement::Required`] must always be
   3433     /// sent.
   3434     pub user_verified: bool,
   3435     /// This can only be updated if [`BackupReq`] allows for it.
   3436     pub backup: Backup,
   3437     /// [`signCount`](https://www.w3.org/TR/webauthn-3/#authdata-signcount).
   3438     ///
   3439     /// This is only updated if the authenticator supports
   3440     /// [signature counters](https://www.w3.org/TR/webauthn-3/#signature-counter), and the behavior of how it is
   3441     /// updated is controlled by [`AuthenticationVerificationOptions::sig_counter_enforcement`].
   3442     pub sign_count: u32,
   3443     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredential-authenticatorattachment).
   3444     ///
   3445     /// [`AuthenticationVerificationOptions::auth_attachment_enforcement`] controls if/how this updated.
   3446     pub authenticator_attachment: AuthenticatorAttachment,
   3447 }
   3448 impl PartialEq<&Self> for DynamicState {
   3449     #[inline]
   3450     fn eq(&self, other: &&Self) -> bool {
   3451         *self == **other
   3452     }
   3453 }
   3454 impl PartialEq<DynamicState> for &DynamicState {
   3455     #[inline]
   3456     fn eq(&self, other: &DynamicState) -> bool {
   3457         **self == *other
   3458     }
   3459 }
   3460 #[cfg(test)]
   3461 mod tests {
   3462     use super::{
   3463         super::{
   3464             super::{
   3465                 AggErr,
   3466                 request::{AsciiDomain, RpId},
   3467             },
   3468             auth::{AuthenticatorData, NonDiscoverableAuthenticatorAssertion},
   3469         },
   3470         AttestationFormat, AttestationObject, AuthDataContainer as _, AuthExtOutput as _,
   3471         AuthTransports, AuthenticatorAttestation, AuthenticatorExtensionOutput,
   3472         AuthenticatorExtensionOutputErr, Backup, CborSuccess, CredentialProtectionPolicy,
   3473         FourToSixtyThree, FromCbor as _, HmacSecret, Sig, UncompressedPubKey,
   3474         cbor::{
   3475             BYTES, BYTES_INFO_24, MAP_1, MAP_2, MAP_3, MAP_4, SIMPLE_FALSE, SIMPLE_TRUE, TEXT_11,
   3476             TEXT_12, TEXT_14,
   3477         },
   3478     };
   3479     use ed25519_dalek::Verifier as _;
   3480     use p256::ecdsa::{DerSignature as P256Sig, SigningKey as P256Key};
   3481     use rsa::sha2::{Digest as _, Sha256};
   3482     #[expect(clippy::panic, reason = "OK in tests")]
   3483     #[expect(
   3484         clippy::arithmetic_side_effects,
   3485         clippy::indexing_slicing,
   3486         clippy::missing_asserts_for_indexing,
   3487         reason = "comments justifies correctness"
   3488     )]
   3489     fn hex_decode<const N: usize>(input: &[u8; N]) -> Vec<u8> {
   3490         /// Value to subtract from a lowercase hex digit.
   3491         const LOWER_OFFSET: u8 = b'a' - 10;
   3492         assert_eq!(
   3493             N & 1,
   3494             0,
   3495             "hex_decode must be passed a reference to an array of even length"
   3496         );
   3497         let mut data = Vec::with_capacity(N >> 1);
   3498         input.chunks_exact(2).fold((), |(), byte| {
   3499             // `byte.len() == 2`.
   3500             let mut hex = byte[0];
   3501             let val = match hex {
   3502                 // `Won't underflow`.
   3503                 b'0'..=b'9' => hex - b'0',
   3504                 // `Won't underflow`.
   3505                 b'a'..=b'f' => hex - LOWER_OFFSET,
   3506                 _ => panic!("hex_decode must be passed a valid lowercase hexadecimal array"),
   3507             } << 4u8;
   3508             // `byte.len() == 2`.
   3509             hex = byte[1];
   3510             data.push(
   3511                 val | match hex {
   3512                     // `Won't underflow`.
   3513                     b'0'..=b'9' => hex - b'0',
   3514                     // `Won't underflow`.
   3515                     b'a'..=b'f' => hex - LOWER_OFFSET,
   3516                     _ => panic!("hex_decode must be passed a valid lowercase hexadecimal array"),
   3517                 },
   3518             );
   3519         });
   3520         data
   3521     }
   3522     /// <https://pr-preview.s3.amazonaws.com/w3c/webauthn/pull/2209.html#sctn-test-vectors-none-es256>
   3523     #[expect(
   3524         clippy::panic_in_result_fn,
   3525         clippy::unwrap_in_result,
   3526         clippy::unwrap_used,
   3527         reason = "OK in tests"
   3528     )]
   3529     #[test]
   3530     fn es256_test_vector() -> Result<(), AggErr> {
   3531         let rp_id = RpId::Domain(AsciiDomain::try_from("example.org".to_owned())?);
   3532         let credential_private_key =
   3533             hex_decode(b"6e68e7a58484a3264f66b77f5d6dc5bc36a47085b615c9727ab334e8c369c2ee");
   3534         let aaguid = hex_decode(b"8446ccb9ab1db374750b2367ff6f3a1f");
   3535         let credential_id =
   3536             hex_decode(b"f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4");
   3537         let client_data_json = hex_decode(b"7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a22414d4d507434557878475453746e63647134313759447742466938767049612d7077386f4f755657345441222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a20426b5165446a646354427258426941774a544c4535513d3d227d");
   3538         let attestation_object = hex_decode(b"a363666d74646e6f6e656761747453746d74a068617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b559000000008446ccb9ab1db374750b2367ff6f3a1f0020f91f391db4c9b2fde0ea70189cba3fb63f579ba6122b33ad94ff3ec330084be4a5010203262001215820afefa16f97ca9b2d23eb86ccb64098d20db90856062eb249c33a9b672f26df61225820930a56b87a2fca66334b03458abf879717c12cc68ed73290af2e2664796b9220");
   3539         let key = *P256Key::from_slice(credential_private_key.as_slice())
   3540             .unwrap()
   3541             .verifying_key();
   3542         let enc_key = key.to_encoded_point(false);
   3543         let auth_attest =
   3544             AuthenticatorAttestation::new(client_data_json, attestation_object, AuthTransports(0));
   3545         let att_obj = AttestationObject::from_data(
   3546             auth_attest.attestation_object_and_c_data_hash.as_slice(),
   3547         )?;
   3548         assert_eq!(
   3549             aaguid,
   3550             att_obj.data.auth_data.attested_credential_data.aaguid.0
   3551         );
   3552         assert_eq!(
   3553             credential_id,
   3554             att_obj
   3555                 .data
   3556                 .auth_data
   3557                 .attested_credential_data
   3558                 .credential_id
   3559                 .0
   3560         );
   3561         assert!(
   3562             matches!(att_obj.data.auth_data.attested_credential_data.credential_public_key, UncompressedPubKey::P256(pub_key) if **enc_key.x().unwrap() == *pub_key.0 && **enc_key.y().unwrap() == *pub_key.1)
   3563         );
   3564         assert_eq!(
   3565             *att_obj.data.auth_data.rp_id_hash,
   3566             *Sha256::digest(rp_id.as_ref())
   3567         );
   3568         assert!(att_obj.data.auth_data.flags.user_present);
   3569         assert!(matches!(att_obj.data.attestation, AttestationFormat::None));
   3570         let authenticator_data = hex_decode(
   3571             b"bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b51900000000",
   3572         );
   3573         let client_data_json_2 = hex_decode(b"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a224f63446e55685158756c5455506f334a5558543049393770767a7a59425039745a63685879617630314167222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73657d");
   3574         let signature = hex_decode(b"3046022100f50a4e2e4409249c4a853ba361282f09841df4dd4547a13a87780218deffcd380221008480ac0f0b93538174f575bf11a1dd5d78c6e486013f937295ea13653e331e87");
   3575         let auth_assertion = NonDiscoverableAuthenticatorAssertion::<1>::without_user(
   3576             client_data_json_2,
   3577             authenticator_data,
   3578             signature,
   3579         );
   3580         let auth_data = AuthenticatorData::try_from(auth_assertion.authenticator_data())?;
   3581         assert_eq!(*auth_data.rp_id_hash(), *Sha256::digest(rp_id.as_ref()));
   3582         assert!(auth_data.flags().user_present);
   3583         assert!(match att_obj.data.auth_data.flags.backup {
   3584             Backup::NotEligible => matches!(auth_data.flags().backup, Backup::NotEligible),
   3585             Backup::Eligible => !matches!(auth_data.flags().backup, Backup::NotEligible),
   3586             Backup::Exists => matches!(auth_data.flags().backup, Backup::Exists),
   3587         });
   3588         let sig = P256Sig::from_bytes(auth_assertion.signature()).unwrap();
   3589         let mut msg = auth_assertion.authenticator_data().to_owned();
   3590         msg.extend_from_slice(&Sha256::digest(auth_assertion.client_data_json()));
   3591         key.verify(msg.as_slice(), &sig).unwrap();
   3592         Ok(())
   3593     }
   3594     /// <https://pr-preview.s3.amazonaws.com/w3c/webauthn/pull/2209.html#sctn-test-vectors-packed-self-es256>
   3595     #[expect(
   3596         clippy::panic_in_result_fn,
   3597         clippy::unwrap_in_result,
   3598         clippy::unwrap_used,
   3599         reason = "OK in tests"
   3600     )]
   3601     #[expect(clippy::indexing_slicing, reason = "comment justifies correctness")]
   3602     #[test]
   3603     fn es256_self_attest_test_vector() -> Result<(), AggErr> {
   3604         let rp_id = RpId::Domain(AsciiDomain::try_from("example.org".to_owned())?);
   3605         let credential_private_key =
   3606             hex_decode(b"b4bbfa5d68e1693b6ef5a19a0e60ef7ee2cbcac81f7fec7006ac3a21e0c5116a");
   3607         let aaguid = hex_decode(b"df850e09db6afbdfab51697791506cfc");
   3608         let credential_id =
   3609             hex_decode(b"455ef34e2043a87db3d4afeb39bbcb6cc32df9347c789a865ecdca129cbef58c");
   3610         let client_data_json = hex_decode(b"7b2274797065223a22776562617574686e2e637265617465222c226368616c6c656e6765223a2265476e4374334c55745936366b336a506a796e6962506b31716e666644616966715a774c33417032392d55222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a205539685458764b453255526b4d6e625f3078594856673d3d227d");
   3611         let attestation_object = hex_decode(b"a363666d74667061636b65646761747453746d74a263616c67266373696758483046022100ae045923ded832b844cae4d5fc864277c0dc114ad713e271af0f0d371bd3ac540221009077a088ed51a673951ad3ba2673d5029bab65b64f4ea67b234321f86fcfac5d68617574684461746158a4bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b55d00000000df850e09db6afbdfab51697791506cfc0020455ef34e2043a87db3d4afeb39bbcb6cc32df9347c789a865ecdca129cbef58ca5010203262001215820eb151c8176b225cc651559fecf07af450fd85802046656b34c18f6cf193843c5225820927b8aa427a2be1b8834d233a2d34f61f13bfd44119c325d5896e183fee484f2");
   3612         let key = *P256Key::from_slice(credential_private_key.as_slice())
   3613             .unwrap()
   3614             .verifying_key();
   3615         let enc_key = key.to_encoded_point(false);
   3616         let auth_attest =
   3617             AuthenticatorAttestation::new(client_data_json, attestation_object, AuthTransports(0));
   3618         let (att_obj, auth_idx) = AttestationObject::parse_data(auth_attest.attestation_object())?;
   3619         assert_eq!(aaguid, att_obj.auth_data.attested_credential_data.aaguid.0);
   3620         assert_eq!(
   3621             credential_id,
   3622             att_obj.auth_data.attested_credential_data.credential_id.0
   3623         );
   3624         assert!(
   3625             matches!(att_obj.auth_data.attested_credential_data.credential_public_key, UncompressedPubKey::P256(pub_key) if **enc_key.x().unwrap() == *pub_key.0 && **enc_key.y().unwrap() == *pub_key.1)
   3626         );
   3627         assert_eq!(
   3628             *att_obj.auth_data.rp_id_hash,
   3629             *Sha256::digest(rp_id.as_ref())
   3630         );
   3631         assert!(att_obj.auth_data.flags.user_present);
   3632         assert!(match att_obj.attestation {
   3633             AttestationFormat::None => false,
   3634             AttestationFormat::Packed(attest) => {
   3635                 match attest.signature {
   3636                     Sig::Ed25519(_) | Sig::P384(_) | Sig::Rs256(_) => false,
   3637                     Sig::P256(sig) => {
   3638                         let s = P256Sig::from_bytes(sig).unwrap();
   3639                         key.verify(
   3640                             // Won't `panic` since `auth_idx` is returned from `AttestationObject::parse_data`.
   3641                             &auth_attest.attestation_object_and_c_data_hash[auth_idx..],
   3642                             &s,
   3643                         )
   3644                         .is_ok()
   3645                     }
   3646                 }
   3647             }
   3648         });
   3649         let authenticator_data = hex_decode(
   3650             b"bfabc37432958b063360d3ad6461c9c4735ae7f8edd46592a5e0f01452b2e4b50900000000",
   3651         );
   3652         let client_data_json_2 = hex_decode(b"7b2274797065223a22776562617574686e2e676574222c226368616c6c656e6765223a225248696843784e534e493352594d45314f7731476d3132786e726b634a5f6666707637546e2d4a71386773222c226f726967696e223a2268747470733a2f2f6578616d706c652e6f7267222c2263726f73734f726967696e223a66616c73652c22657874726144617461223a22636c69656e74446174614a534f4e206d617920626520657874656e6465642077697468206164646974696f6e616c206669656c647320696e20746865206675747572652c207375636820617320746869733a206754623533727a36456853576f6d58477a696d4331513d3d227d");
   3653         let signature = hex_decode(b"3044022076691be76a8618976d9803c4cdc9b97d34a7af37e3bdc894a2bf54f040ffae850220448033a015296ffb09a762efd0d719a55346941e17e91ebf64c60d439d0b9744");
   3654         let auth_assertion = NonDiscoverableAuthenticatorAssertion::<1>::without_user(
   3655             client_data_json_2,
   3656             authenticator_data,
   3657             signature,
   3658         );
   3659         let auth_data = AuthenticatorData::try_from(auth_assertion.authenticator_data())?;
   3660         assert_eq!(*auth_data.rp_id_hash(), *Sha256::digest(rp_id.as_ref()));
   3661         assert!(auth_data.flags().user_present);
   3662         assert!(match att_obj.auth_data.flags.backup {
   3663             Backup::NotEligible => matches!(auth_data.flags().backup, Backup::NotEligible),
   3664             Backup::Eligible | Backup::Exists =>
   3665                 !matches!(auth_data.flags().backup, Backup::NotEligible),
   3666         });
   3667         let sig = P256Sig::from_bytes(auth_assertion.signature()).unwrap();
   3668         let mut msg = auth_assertion.authenticator_data().to_owned();
   3669         msg.extend_from_slice(&Sha256::digest(auth_assertion.client_data_json()));
   3670         key.verify(msg.as_slice(), &sig).unwrap();
   3671         Ok(())
   3672     }
   3673     struct AuthExtOptions<'a> {
   3674         cred_protect: Option<u8>,
   3675         hmac_secret: Option<bool>,
   3676         min_pin_length: Option<u8>,
   3677         hmac_secret_mc: Option<&'a [u8]>,
   3678     }
   3679     #[expect(
   3680         clippy::panic,
   3681         clippy::unreachable,
   3682         reason = "want to crash when there is a bug"
   3683     )]
   3684     #[expect(
   3685         clippy::arithmetic_side_effects,
   3686         clippy::as_conversions,
   3687         clippy::cast_possible_truncation,
   3688         reason = "comments justify correctness"
   3689     )]
   3690     fn generate_auth_extensions(opts: &AuthExtOptions<'_>) -> Vec<u8> {
   3691         // Maxes at 4, so addition is clearly free from overflow.
   3692         let map_len = u8::from(opts.cred_protect.is_some())
   3693             + u8::from(opts.hmac_secret.is_some())
   3694             + u8::from(opts.min_pin_length.is_some())
   3695             + u8::from(opts.hmac_secret_mc.is_some());
   3696         let header = match map_len {
   3697             0 => return Vec::new(),
   3698             1 => MAP_1,
   3699             2 => MAP_2,
   3700             3 => MAP_3,
   3701             4 => MAP_4,
   3702             _ => unreachable!("bug"),
   3703         };
   3704         let mut cbor = Vec::with_capacity(128);
   3705         cbor.push(header);
   3706         if let Some(protect) = opts.cred_protect {
   3707             cbor.push(TEXT_11);
   3708             cbor.extend_from_slice(b"credProtect".as_slice());
   3709             if protect >= 24 {
   3710                 cbor.push(24);
   3711             }
   3712             cbor.push(protect);
   3713         }
   3714         if let Some(hmac) = opts.hmac_secret {
   3715             cbor.push(TEXT_11);
   3716             cbor.extend_from_slice(b"hmac-secret".as_slice());
   3717             cbor.push(if hmac { SIMPLE_TRUE } else { SIMPLE_FALSE });
   3718         }
   3719         if let Some(pin) = opts.min_pin_length {
   3720             cbor.push(TEXT_12);
   3721             cbor.extend_from_slice(b"minPinLength".as_slice());
   3722             if pin >= 24 {
   3723                 cbor.push(24);
   3724             }
   3725             cbor.push(pin);
   3726         }
   3727         if let Some(mc) = opts.hmac_secret_mc {
   3728             cbor.push(TEXT_14);
   3729             cbor.extend_from_slice(b"hmac-secret-mc".as_slice());
   3730             match mc.len() {
   3731                 len @ ..=23 => {
   3732                     // `as` is clearly OK.
   3733                     cbor.push(BYTES | len as u8);
   3734                 }
   3735                 len @ 24..=255 => {
   3736                     cbor.push(BYTES_INFO_24);
   3737                     // `as` is clearly OK.
   3738                     cbor.push(len as u8);
   3739                 }
   3740                 _ => panic!(
   3741                     "AuthExtOptions does not allow hmac_secret_mc to have length greater than 255"
   3742                 ),
   3743             }
   3744             cbor.extend_from_slice(mc);
   3745         }
   3746         cbor
   3747     }
   3748     #[expect(clippy::panic_in_result_fn, reason = "not a problem for a test")]
   3749     #[expect(clippy::shadow_unrelated, reason = "struct destructuring is prefered")]
   3750     #[expect(clippy::too_many_lines, reason = "a lot to test")]
   3751     #[test]
   3752     fn auth_ext() -> Result<(), AuthenticatorExtensionOutputErr> {
   3753         let mut opts = generate_auth_extensions(&AuthExtOptions {
   3754             cred_protect: None,
   3755             hmac_secret: None,
   3756             min_pin_length: None,
   3757             hmac_secret_mc: None,
   3758         });
   3759         let CborSuccess { value, remaining } =
   3760             AuthenticatorExtensionOutput::from_cbor(opts.as_slice())?;
   3761         assert!(remaining.is_empty());
   3762         assert!(value.missing());
   3763         opts = generate_auth_extensions(&AuthExtOptions {
   3764             cred_protect: None,
   3765             hmac_secret: None,
   3766             min_pin_length: None,
   3767             hmac_secret_mc: Some([0; 48].as_slice()),
   3768         });
   3769         assert!(
   3770             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3771                 |e| matches!(e, AuthenticatorExtensionOutputErr::Missing),
   3772                 |_| false,
   3773             )
   3774         );
   3775         opts = generate_auth_extensions(&AuthExtOptions {
   3776             cred_protect: None,
   3777             hmac_secret: Some(true),
   3778             min_pin_length: None,
   3779             hmac_secret_mc: Some([0; 48].as_slice()),
   3780         });
   3781         let CborSuccess { value, remaining } =
   3782             AuthenticatorExtensionOutput::from_cbor(opts.as_slice())?;
   3783         assert!(remaining.is_empty());
   3784         assert!(
   3785             matches!(value.cred_protect, CredentialProtectionPolicy::None)
   3786                 && matches!(value.hmac_secret, HmacSecret::One)
   3787                 && value.min_pin_length.is_none()
   3788         );
   3789         opts = generate_auth_extensions(&AuthExtOptions {
   3790             cred_protect: None,
   3791             hmac_secret: Some(false),
   3792             min_pin_length: None,
   3793             hmac_secret_mc: Some([0; 48].as_slice()),
   3794         });
   3795         assert!(
   3796             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3797                 |e| matches!(e, AuthenticatorExtensionOutputErr::Missing),
   3798                 |_| false,
   3799             )
   3800         );
   3801         opts = generate_auth_extensions(&AuthExtOptions {
   3802             cred_protect: None,
   3803             hmac_secret: Some(true),
   3804             min_pin_length: None,
   3805             hmac_secret_mc: Some([0; 49].as_slice()),
   3806         });
   3807         assert!(
   3808             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3809                 |e| matches!(e, AuthenticatorExtensionOutputErr::HmacSecretMcValue),
   3810                 |_| false,
   3811             )
   3812         );
   3813         opts = generate_auth_extensions(&AuthExtOptions {
   3814             cred_protect: None,
   3815             hmac_secret: Some(true),
   3816             min_pin_length: None,
   3817             hmac_secret_mc: Some([0; 23].as_slice()),
   3818         });
   3819         assert!(
   3820             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3821                 |e| matches!(e, AuthenticatorExtensionOutputErr::HmacSecretMcType),
   3822                 |_| false,
   3823             )
   3824         );
   3825         opts = generate_auth_extensions(&AuthExtOptions {
   3826             cred_protect: Some(1),
   3827             hmac_secret: Some(true),
   3828             min_pin_length: Some(5),
   3829             hmac_secret_mc: Some([0; 48].as_slice()),
   3830         });
   3831         let CborSuccess { value, remaining } =
   3832             AuthenticatorExtensionOutput::from_cbor(opts.as_slice())?;
   3833         assert!(remaining.is_empty());
   3834         assert!(
   3835             matches!(
   3836                 value.cred_protect,
   3837                 CredentialProtectionPolicy::UserVerificationOptional
   3838             ) && matches!(value.hmac_secret, HmacSecret::One)
   3839                 && value
   3840                     .min_pin_length
   3841                     .is_some_and(|pin| pin == FourToSixtyThree::Five)
   3842         );
   3843         opts = generate_auth_extensions(&AuthExtOptions {
   3844             cred_protect: Some(0),
   3845             hmac_secret: None,
   3846             min_pin_length: None,
   3847             hmac_secret_mc: None,
   3848         });
   3849         assert!(
   3850             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3851                 |e| matches!(e, AuthenticatorExtensionOutputErr::CredProtectValue),
   3852                 |_| false,
   3853             )
   3854         );
   3855         opts = generate_auth_extensions(&AuthExtOptions {
   3856             cred_protect: None,
   3857             hmac_secret: None,
   3858             min_pin_length: Some(3),
   3859             hmac_secret_mc: None,
   3860         });
   3861         assert!(
   3862             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3863                 |e| matches!(e, AuthenticatorExtensionOutputErr::MinPinLengthValue),
   3864                 |_| false,
   3865             )
   3866         );
   3867         opts = generate_auth_extensions(&AuthExtOptions {
   3868             cred_protect: None,
   3869             hmac_secret: None,
   3870             min_pin_length: Some(64),
   3871             hmac_secret_mc: None,
   3872         });
   3873         assert!(
   3874             AuthenticatorExtensionOutput::from_cbor(opts.as_slice()).map_or_else(
   3875                 |e| matches!(e, AuthenticatorExtensionOutputErr::MinPinLengthValue),
   3876                 |_| false,
   3877             )
   3878         );
   3879         Ok(())
   3880     }
   3881 }