webauthn_rp

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

register.rs (211583B)


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