webauthn_rp

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

register.rs (181234B)


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