webauthn_rp

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

commit 1d95b0f6f7b1a68e29404a5dcd679b573d460618
parent 873a002485f4bdf0c6631c35eecfaab6f5507581
Author: Zack Newman <zack@philomathiclife.com>
Date:   Sun, 22 Jun 2025 22:31:58 -0600

more lints

Diffstat:
MCargo.toml | 29++++++++++++++++++++++++++++-
Msrc/hash.rs | 2+-
Msrc/hash/hash_map.rs | 2+-
Msrc/hash/hash_set.rs | 4++--
Msrc/lib.rs | 3++-
Msrc/request.rs | 11+++++++++--
Msrc/request/auth.rs | 26++++++++++++--------------
Msrc/request/register.rs | 16+++++++++++++++-
Msrc/response.rs | 3+++
Msrc/response/auth/error.rs | 4++--
Msrc/response/register.rs | 1+
Msrc/response/register/bin.rs | 2+-
Msrc/response/register/ser.rs | 18++++++++++++------
Msrc/response/register/ser_relaxed.rs | 6+++---
14 files changed, 92 insertions(+), 35 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -13,18 +13,45 @@ rust-version = "1.87.0" version = "0.4.0" [lints.rust] -unknown_lints = { level = "deny", priority = -1 } +ambiguous_negative_literals = { level = "deny", priority = -1 } +closure_returning_async_block = { level = "deny", priority = -1 } +deref_into_dyn_supertrait = { level = "deny", priority = -1 } +ffi_unwind_calls = { level = "deny", priority = -1 } future_incompatible = { level = "deny", priority = -1 } +impl_trait_redundant_captures = { level = "deny", priority = -1 } +keyword-idents = { level = "deny", priority = -1 } let_underscore = { level = "deny", priority = -1 } +linker_messages = { level = "deny", priority = -1 } +macro_use_extern_crate = { level = "deny", priority = -1 } +meta_variable_misuse = { level = "deny", priority = -1 } +missing_copy_implementations = { level = "deny", priority = -1 } +missing_debug_implementations = { level = "deny", priority = -1 } missing_docs = { level = "deny", priority = -1 } +non_ascii_idents = { level = "deny", priority = -1 } nonstandard_style = { level = "deny", priority = -1 } +redundant_imports = { level = "deny", priority = -1 } +redundant_lifetimes = { level = "deny", priority = -1 } refining_impl_trait = { level = "deny", priority = -1 } rust_2018_compatibility = { level = "deny", priority = -1 } rust_2018_idioms = { level = "deny", priority = -1 } rust_2021_compatibility = { level = "deny", priority = -1 } rust_2024_compatibility = { level = "deny", priority = -1 } +single-use-lifetimes = { level = "deny", priority = -1 } +trivial_casts = { level = "deny", priority = -1 } +trivial_numeric_casts = { level = "deny", priority = -1 } +unit_bindings = { level = "deny", priority = -1 } +unknown_lints = { level = "deny", priority = -1 } +unnameable_types = { level = "deny", priority = -1 } +unreachable_pub = { level = "deny", priority = -1 } unsafe_code = { level = "deny", priority = -1 } +unstable_features = { level = "deny", priority = -1 } unused = { level = "deny", priority = -1 } +unused_crate_dependencies = { level = "deny", priority = -1 } +unused_import_braces = { level = "deny", priority = -1 } +unused_lifetimes = { level = "deny", priority = -1 } +unused_qualifications = { level = "deny", priority = -1 } +unused_results = { level = "deny", priority = -1 } +variant_size_differences = { level = "deny", priority = -1 } warnings = { level = "deny", priority = -1 } [lints.clippy] diff --git a/src/hash.rs b/src/hash.rs @@ -34,7 +34,7 @@ pub mod hash_set; /// [`NonDiscoverableCredentialRequestOptions::start_ceremony`] respectively. Since `Challenge` is already based on /// a random `u128`, other `Hasher`s will be slower and likely produce lower-quality hashes (and never /// higher quality). -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct IdentityHasher(u64); // Note it is _not_ required for `write_*` methods to do the same thing as other `write_*` methods // (e.g., `Self::write_u64` may not be the same thing as 8 calls to `Self::write_u8`). diff --git a/src/hash/hash_map.rs b/src/hash/hash_map.rs @@ -170,7 +170,7 @@ impl<K: Eq + Hash, V, S: BuildHasher> FixedCapHashMap<K, V, S> { if full { None } else { - ent.insert(v); + _ = ent.insert(v); Some(None) } } diff --git a/src/hash/hash_set.rs b/src/hash/hash_set.rs @@ -127,7 +127,7 @@ impl<T: Eq + Hash, S: BuildHasher> FixedCapHashSet<T, S> { if full { None } else { - ent.insert(); + _ = ent.insert(); Some(true) } } else { @@ -147,7 +147,7 @@ impl<T: Eq + Hash, S: BuildHasher> FixedCapHashSet<T, S> { } else if self.0.len() == self.0.capacity() { None } else { - self.0.insert(value); + _ = self.0.insert(value); Some(None) } } diff --git a/src/lib.rs b/src/lib.rs @@ -1062,12 +1062,13 @@ impl<'cred, 'user, const USER_LEN: usize, PublicKey> /// /// Errors iff the passed arguments are invalid. Read [`CredentialErr`] /// for more information. + #[expect(single_use_lifetimes, reason = "false positive")] #[cfg_attr(docsrs, doc(cfg(any(feature = "bin", feature = "custom"))))] #[cfg(any(feature = "bin", feature = "custom"))] #[inline] pub fn new<'a: 'cred, 'b: 'user>( id: CredentialId<&'a [u8]>, - user_id: &'user UserHandle<USER_LEN>, + user_id: &'b UserHandle<USER_LEN>, static_state: StaticState<PublicKey>, dynamic_state: DynamicState, ) -> Result<Self, CredentialErr> { diff --git a/src/request.rs b/src/request.rs @@ -193,6 +193,10 @@ pub(super) mod ser_server_state; // `SentChallenge` since it is used during ceremony validation; thus we must keep `Challenge` and `SentChallenge` // as separate types. /// [Cryptographic challenge](https://www.w3.org/TR/webauthn-3/#sctn-cryptographic-challenges). +#[expect( + missing_copy_implementations, + reason = "want to enforce randomly-generated challenges" +)] #[derive(Debug)] pub struct Challenge(u128); impl Challenge { @@ -277,7 +281,7 @@ impl AsciiDomain { .unwrap_or_else(|| unreachable!("there is a bug in AsciiDomain::try_from")) == b'.' { - self.0.pop(); + _ = self.0.pop(); } } } @@ -761,6 +765,7 @@ impl<'b> DomainOrigin<'_, 'b> { /// Origin(Cow::Borrowed("https://www.example.com:443")) /// ); /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[must_use] #[inline] pub const fn new<'c: 'b>(host: &'c str) -> Self { @@ -790,6 +795,7 @@ impl<'b> DomainOrigin<'_, 'b> { /// Origin(Cow::Borrowed("https://www.example.com")) /// ); /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[must_use] #[inline] pub const fn new_ignore_port<'c: 'b>(host: &'c str) -> Self { @@ -1581,6 +1587,7 @@ mod tests { signature::{Keypair, SignatureEncoding}, traits::PublicKeyParts, }; + use serde_json as _; #[cfg(feature = "custom")] const CBOR_UINT: u8 = 0b000_00000; #[cfg(feature = "custom")] @@ -1956,7 +1963,7 @@ mod tests { fn eddsa_auth() -> Result<(), AggErr> { let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); let mut creds = AllowedCredentials::with_capacity(1); - creds.push(AllowedCredential { + _ = creds.push(AllowedCredential { credential: PublicKeyCredentialDescriptor { id: CredentialId::try_from(vec![0; 16])?, transports: AuthTransports::NONE, diff --git a/src/request/auth.rs b/src/request/auth.rs @@ -280,7 +280,7 @@ impl From<Vec<PublicKeyCredentialDescriptor<Vec<u8>>>> for AllowedCredentials { fn from(value: Vec<PublicKeyCredentialDescriptor<Vec<u8>>>) -> Self { let mut creds = Self::with_capacity(value.len()); value.into_iter().fold((), |(), credential| { - creds.push(AllowedCredential { + _ = creds.push(AllowedCredential { credential, extension: CredentialSpecificExtension { prf: None }, }); @@ -317,6 +317,7 @@ impl<'rp_id, 'prf_first, 'prf_second> /// )); /// # Ok::<_, webauthn_rp::AggErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self { @@ -448,6 +449,7 @@ impl<'rp_id, 'prf_first, 'prf_second> /// ); /// # Ok::<_, webauthn_rp::AggErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] pub fn second_factor<'a: 'rp_id>( rp_id: &'a RpId, @@ -548,6 +550,7 @@ impl<'rp_id> PublicKeyCredentialRequestOptions<'rp_id, '_, '_> { /// )); /// # Ok::<_, webauthn_rp::AggErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn passkey<'a: 'rp_id>(rp_id: &'a RpId) -> Self { @@ -576,6 +579,7 @@ impl<'rp_id> PublicKeyCredentialRequestOptions<'rp_id, '_, '_> { /// )); /// # Ok::<_, webauthn_rp::AggErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn second_factor<'a: 'rp_id>(rp_id: &'a RpId) -> Self { @@ -1069,8 +1073,6 @@ impl DiscoverableAuthenticationServerState { #[inline] pub fn verify< 'a, - 'cred_id, - 'user, const USER_LEN: usize, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>, @@ -1083,8 +1085,8 @@ impl DiscoverableAuthenticationServerState { rp_id: &RpId, response: &'a DiscoverableAuthentication<USER_LEN>, cred: &mut AuthenticatedCredential< - 'cred_id, - 'user, + '_, + '_, USER_LEN, CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, >, @@ -1145,8 +1147,6 @@ impl NonDiscoverableAuthenticationServerState { #[inline] pub fn verify< 'a, - 'cred_id, - 'user, const USER_LEN: usize, O: PartialEq<Origin<'a>>, T: PartialEq<Origin<'a>>, @@ -1159,8 +1159,8 @@ impl NonDiscoverableAuthenticationServerState { rp_id: &RpId, response: &'a NonDiscoverableAuthentication<USER_LEN>, cred: &mut AuthenticatedCredential< - 'cred_id, - 'user, + '_, + '_, USER_LEN, CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, >, @@ -1243,8 +1243,6 @@ impl AuthenticationServerState { /// of the settings in `options`. fn verify< 'a, - 'cred_id, - 'user, const USER_LEN: usize, const DISCOVERABLE: bool, O: PartialEq<Origin<'a>>, @@ -1259,8 +1257,8 @@ impl AuthenticationServerState { rp_id: &RpId, response: &'a Authentication<USER_LEN, DISCOVERABLE>, cred: &mut AuthenticatedCredential< - 'cred_id, - 'user, + '_, + '_, USER_LEN, CompressedPubKey<EdKey, P256Key, P384Key, RsaKey>, >, @@ -1662,7 +1660,7 @@ mod tests { fn eddsa_auth_ser() -> Result<(), AggErr> { let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?); let mut creds = AllowedCredentials::with_capacity(1); - creds.push(AllowedCredential { + _ = creds.push(AllowedCredential { credential: PublicKeyCredentialDescriptor { id: CredentialId::try_from(vec![0; 16])?, transports: AuthTransports::NONE, diff --git a/src/request/register.rs b/src/request/register.rs @@ -233,6 +233,7 @@ impl<'a> Nickname<'a> { /// /// Errors iff `value` violates [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266.html#section-2.3) /// or the resulting length exceeds [`Self::RECOMMENDED_MAX_LEN`]. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> { precis_profiles::Nickname::new() @@ -251,6 +252,7 @@ impl<'a> Nickname<'a> { /// /// Errors iff `value` violates [RFC 8266 § 2.3](https://www.rfc-editor.org/rfc/rfc8266.html#section-2.3) /// or the resulting length exceeds [`Self::MAX_LEN`]. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, NicknameErr> { Self::try_from(value) @@ -399,6 +401,7 @@ impl<'a> Username<'a> { /// /// Errors iff `value` violates [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265.html#section-3.4.3) /// or the resulting length exceeds [`Self::RECOMMENDED_MAX_LEN`]. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] pub fn with_recommended_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, UsernameErr> { UsernameCasePreserved::default() @@ -417,6 +420,7 @@ impl<'a> Username<'a> { /// /// Errors iff `value` violates [RFC 8265 § 3.4.3](https://www.rfc-editor.org/rfc/rfc8265.html#section-3.4.3) /// or the resulting length exceeds [`Self::MAX_LEN`]. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] pub fn with_max_len<'b: 'a>(value: Cow<'b, str>) -> Result<Self, UsernameErr> { Self::try_from(value) @@ -1341,6 +1345,7 @@ impl< { /// Sets [`Self::mediation`] to [`CredentialMediationRequirement::default`] and /// [`Self::public_key`] to [`PublicKeyCredentialCreationOptions::passkey`]. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1360,6 +1365,7 @@ impl< /// Convenience function for [`Self::passkey`] passing an empty `Vec`. /// /// This MUST only be used when this is the first credential for a user. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn first_passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1376,6 +1382,7 @@ impl< /// /// Because user information is likely known for existing accounts, this will often only be called during /// greenfield deployments. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn first_passkey_with_blank_user_info<'a: 'rp_id, 'b: 'user_id>( @@ -1386,6 +1393,7 @@ impl< } /// Sets [`Self::mediation`] to [`CredentialMediationRequirement::default`] and /// [`Self::public_key`] to [`PublicKeyCredentialCreationOptions::second_factor`]. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1406,6 +1414,7 @@ impl< /// Convenience function for [`Self::second_factor`] passing an empty `Vec`. /// /// This MUST only be used when this is the first credential for a user. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn first_second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1614,6 +1623,7 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// )); /// # Ok::<_, webauthn_rp::AggErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1643,6 +1653,7 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// Convenience function for [`Self::passkey`] passing an empty `Vec`. /// /// This MUST only be used when this is the first credential for a user. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn first_passkey<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1659,6 +1670,7 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// /// Because user information is likely known for existing accounts, this will often only be called during /// greenfield deployments. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn first_passkey_with_blank_user_info<'a: 'rp_id, 'b: 'user_id>( @@ -1712,6 +1724,7 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// ); /// # Ok::<_, webauthn_rp::AggErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -1731,6 +1744,7 @@ impl<'rp_id, 'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> /// Convenience function for [`Self::second_factor`] passing an empty `Vec`. /// /// This MUST only be used when this is the first credential for a user. + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] #[must_use] pub fn first_second_factor<'a: 'rp_id, 'b: 'user_name, 'c: 'user_display_name, 'd: 'user_id>( @@ -3234,7 +3248,7 @@ mod tests { .as_slice(), ); } - options.min_pin.map(|p| { + _ = options.min_pin.map(|p| { assert!(p.value() <= 23, "bug"); attestation_object.extend_from_slice( [ diff --git a/src/response.rs b/src/response.rs @@ -519,6 +519,7 @@ impl<T> CredentialId<T> { } impl<'a> CredentialId<&'a [u8]> { /// Creates a `CredentialId` from a `slice`. + #[expect(single_use_lifetimes, reason = "false positive")] fn from_slice<'b: 'a>(value: &'b [u8]) -> Result<Self, CredentialIdErr> { if (CRED_ID_MIN_LEN..=CRED_ID_MAX_LEN).contains(&value.len()) { Ok(Self(value)) @@ -790,6 +791,7 @@ impl<'a> CollectedClientData<'a> { /// assert!(!CollectedClientData::from_client_data_json::<false>(br#"{"type":"webauthn.get","challenge":"AAAAAAAAAAAAAAAAAAAAAA","origin":"https://example.com","crossOrigin":false}"#.as_slice())?.cross_origin); /// # Ok::<_, CollectedClientDataErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[inline] pub fn from_client_data_json<'b: 'a, const REGISTRATION: bool>(json: &'b [u8]) -> Result<Self, CollectedClientDataErr> { LimitedVerificationParser::<REGISTRATION>::parse(json) @@ -837,6 +839,7 @@ impl<'a> CollectedClientData<'a> { /// }")?.cross_origin); /// # Ok::<_, SerdeJsonErr>(()) /// ``` + #[expect(single_use_lifetimes, reason = "false positive")] #[cfg_attr(docsrs, doc(cfg(feature = "serde_relaxed")))] #[cfg(feature = "serde_relaxed")] #[inline] diff --git a/src/response/auth/error.rs b/src/response/auth/error.rs @@ -310,7 +310,7 @@ impl Error for AuthCeremonyErr {} /// This can be sent to the client when an authentication ceremony fails due to an unknown [`CredentialId`]. This /// can be due to the user deleting a credential on the RP's side but not deleting it on the authenticator. This /// response can be forwarded to the authenticator which can subsequently delete the credential. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct UnknownCredentialOptions<'rp, 'cred> { /// [`rpId`](https://www.w3.org/TR/webauthn-3/#dictdef-unknowncredentialoptions-rpid). pub rp_id: &'rp RpId, @@ -318,7 +318,7 @@ pub struct UnknownCredentialOptions<'rp, 'cred> { pub credential_id: CredentialId<&'cred [u8]>, } /// Error when a [`UserHandle`] does not exist that is required to. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct MissingUserHandleErr; impl Display for MissingUserHandleErr { #[inline] diff --git a/src/response/register.rs b/src/response/register.rs @@ -2738,6 +2738,7 @@ impl<'a> AttestationObject<'a> { /// [attestation object layout](https://www.w3.org/TR/webauthn-3/#attestation-object) /// returning [`Self`] and the index within `data` that the authenticator data portion /// begins. + #[expect(single_use_lifetimes, reason = "false positive")] #[expect( clippy::panic_in_result_fn, reason = "we want to crash when there is a bug" diff --git a/src/response/register/bin.rs b/src/response/register/bin.rs @@ -368,7 +368,7 @@ impl Encode for Metadata<'_> { } } /// Owned version of [`Metadata`] that exists to [`Self::decode`] the output of [`Metadata::encode`]. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct MetadataOwned { /// [`Metadata::attestation`]. pub attestation: Attestation, diff --git a/src/response/register/ser.rs b/src/response/register/ser.rs @@ -72,7 +72,7 @@ mod spki { /// for an uncompressed ECDSA public key based on secp256r1/P-256. const P256_HEADER_LEN: usize = 27; /// Number of bytes the x-coordinate takes in an uncompressed P-256 public key. - pub const P256_X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; + const P256_X_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; /// Number of bytes the y-coordinate takes in an uncompressed P-256 public key. const P256_Y_LEN: usize = <NistP256 as Curve>::FieldBytesSize::INT; /// Number of bytes the x and y coordinates take in an uncompressed P-256 public key when concatenated together. @@ -92,7 +92,7 @@ mod spki { /// for an uncompressed ECDSA public key based on secp384r1/P-384. const P384_HEADER_LEN: usize = 24; /// Number of bytes the x-coordinate takes in an uncompressed P-384 public key. - pub const P384_X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; + const P384_X_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; /// Number of bytes the y-coordinate takes in an uncompressed P-384 public key. const P384_Y_LEN: usize = <NistP384 as Curve>::FieldBytesSize::INT; /// Number of bytes the x and y coordinates take in an uncompressed P-384 public key when concatenated together. @@ -109,7 +109,7 @@ mod spki { )] const P384_LEN_U8: u8 = P384_LEN as u8; /// Error returned from [`SubjectPublicKeyInfo::from_der`]. - pub enum SubjectPublicKeyInfoErr { + pub(super) enum SubjectPublicKeyInfoErr { /// The DER-encoded `SubjectPublicKeyInfo` had an invalid length. Len, /// The length of the DER-encoded Ed25519 key was invalid. @@ -165,16 +165,18 @@ mod spki { /// Types that can be deserialized from the DER-encoded ASN.1 `SubjectPublicKeyInfo` as defined in /// [RFC 5280](https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.1) and other applicable RFCs /// and documents (e.g., [ITU-T X.690](https://www.itu.int/rec/T-REC-X.690-202102-I/en)). - pub trait SubjectPublicKeyInfo<'a>: Sized { + pub(super) trait SubjectPublicKeyInfo<'a>: Sized { /// Transforms the DER-encoded ASN.1 `SubjectPublicKeyInfo` into `Self`. /// /// # Errors /// /// Errors iff `der` does not conform. + #[expect(single_use_lifetimes, reason = "false positive")] fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr>; } impl<'a> SubjectPublicKeyInfo<'a> for Ed25519PubKey<&'a [u8]> { - fn from_der<'b: 'a>(der: &'a [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { + #[expect(single_use_lifetimes, reason = "false positive")] + fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { /// ```asn /// SubjectPublicKeyInfo ::= SEQUENCE { /// algorithm AlgorithmIdentifier, @@ -230,6 +232,7 @@ mod spki { } } impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP256PubKey<'a> { + #[expect(single_use_lifetimes, reason = "false positive")] fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { // ```asn // SubjectPublicKeyInfo ::= SEQUENCE { @@ -321,6 +324,7 @@ mod spki { } } impl<'a> SubjectPublicKeyInfo<'a> for UncompressedP384PubKey<'a> { + #[expect(single_use_lifetimes, reason = "false positive")] fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { // ```asn // SubjectPublicKeyInfo ::= SEQUENCE { @@ -409,6 +413,7 @@ mod spki { } } impl<'a> SubjectPublicKeyInfo<'a> for RsaPubKey<&'a [u8]> { + #[expect(single_use_lifetimes, reason = "false positive")] #[expect( clippy::arithmetic_side_effects, clippy::big_endian_bytes, @@ -643,6 +648,7 @@ mod spki { } } impl<'a> SubjectPublicKeyInfo<'a> for UncompressedPubKey<'a> { + #[expect(single_use_lifetimes, reason = "false positive")] fn from_der<'b: 'a>(der: &'b [u8]) -> Result<Self, SubjectPublicKeyInfoErr> { // The lengths of the three key types do not overlap. match der.len() { @@ -744,7 +750,7 @@ impl<'e> Deserialize<'e> for AttObj { struct AttObjVisitor; impl Visitor<'_> for AttObjVisitor { type Value = AttObj; - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result { formatter.write_str("base64url-encoded attestation object") } #[expect( diff --git a/src/response/register/ser_relaxed.rs b/src/response/register/ser_relaxed.rs @@ -22,7 +22,7 @@ use core::{ }; use serde::de::{Deserialize, Deserializer, Error, MapAccess, Unexpected, Visitor}; /// `newtype` around `CredentialPropertiesOutput` with a "relaxed" [`Self::deserialize`] implementation. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct CredentialPropertiesOutputRelaxed(pub CredentialPropertiesOutput); impl From<CredentialPropertiesOutputRelaxed> for CredentialPropertiesOutput { #[inline] @@ -49,7 +49,7 @@ impl<'de> Deserialize<'de> for CredentialPropertiesOutputRelaxed { } } /// `newtype` around `AuthenticationExtensionsPrfOutputs` with a "relaxed" [`Self::deserialize`] implementation. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct AuthenticationExtensionsPrfOutputsRelaxed(AuthenticationExtensionsPrfOutputs); impl From<AuthenticationExtensionsPrfOutputsRelaxed> for AuthenticationExtensionsPrfOutputs { #[inline] @@ -94,7 +94,7 @@ impl<'de> Deserialize<'de> for AuthenticationExtensionsPrfOutputsRelaxed { } } /// `newtype` around `ClientExtensionsOutputs` with a "relaxed" [`Self::deserialize`] implementation. -#[derive(Debug)] +#[derive(Clone, Copy, Debug)] pub struct ClientExtensionsOutputsRelaxed(pub ClientExtensionsOutputs); impl ClientExtensions for ClientExtensionsOutputsRelaxed { fn empty() -> Self {