diff --git a/libwebauthn/src/lib.rs b/libwebauthn/src/lib.rs index 7972f919..d7f4abd2 100644 --- a/libwebauthn/src/lib.rs +++ b/libwebauthn/src/lib.rs @@ -1,11 +1,12 @@ -// Tests and the virt test-utility feature are allowed to use unwrap/expect/panic for convenience. -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::unwrap_used))] -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::expect_used))] -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::panic))] -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::todo))] -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::unreachable))] -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::indexing_slicing))] -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::unwrap_in_result))] +// Production code must not panic. Tests keep unwrap/expect/panic latitude +// through `not(test)`, and the virt test-utility code through local allows. +#![cfg_attr(not(test), deny(clippy::unwrap_used))] +#![cfg_attr(not(test), deny(clippy::expect_used))] +#![cfg_attr(not(test), deny(clippy::panic))] +#![cfg_attr(not(test), deny(clippy::todo))] +#![cfg_attr(not(test), deny(clippy::unreachable))] +#![cfg_attr(not(test), deny(clippy::indexing_slicing))] +#![cfg_attr(not(test), deny(clippy::unwrap_in_result))] #[cfg(all( feature = "nfc", diff --git a/libwebauthn/src/ops/webauthn/client_data.rs b/libwebauthn/src/ops/webauthn/client_data.rs index 27a0d6b2..484d0983 100644 --- a/libwebauthn/src/ops/webauthn/client_data.rs +++ b/libwebauthn/src/ops/webauthn/client_data.rs @@ -41,6 +41,7 @@ impl ClientData { /// Strings are escaped per WebAuthn L3 §5.8.1.2 (CCDToString), via /// `serde_json`'s RFC 8259 string encoder. Field order matches the spec: /// `type`, `challenge`, `origin`, `topOrigin?`, `crossOrigin`. + #[allow(clippy::expect_used)] // serialization of this fixed-shape struct cannot fail pub fn to_json(&self) -> String { let operation = match self.operation { Operation::MakeCredential => "webauthn.create", diff --git a/libwebauthn/src/ops/webauthn/get_assertion.rs b/libwebauthn/src/ops/webauthn/get_assertion.rs index 7e45b51d..1fcbf7d0 100644 --- a/libwebauthn/src/ops/webauthn/get_assertion.rs +++ b/libwebauthn/src/ops/webauthn/get_assertion.rs @@ -61,13 +61,11 @@ impl PrfInputValue { /// WebAuthn L3 PRF: salt = SHA-256("WebAuthn PRF" || 0x00 || ev.{first,second}). pub fn to_hmac_secret_input(&self) -> HMACGetSecretInput { const PREFIX: &[u8] = b"WebAuthn PRF\x00"; - let hash = |slice: &[u8]| { + let hash = |slice: &[u8]| -> [u8; 32] { let mut hasher = Sha256::default(); hasher.update(PREFIX); hasher.update(slice); - let mut out = [0u8; 32]; - out.copy_from_slice(&hasher.finalize()[..32]); - out + hasher.finalize().into() }; HMACGetSecretInput { salt1: hash(&self.first), diff --git a/libwebauthn/src/ops/webauthn/idl/origin.rs b/libwebauthn/src/ops/webauthn/idl/origin.rs index 7be9cce5..b788edcb 100644 --- a/libwebauthn/src/ops/webauthn/idl/origin.rs +++ b/libwebauthn/src/ops/webauthn/idl/origin.rs @@ -315,7 +315,7 @@ pub(crate) fn is_registrable_domain_suffix_or_equal( return false; } let boundary = effective_domain.len() - rp_id.len() - 1; - if effective_domain.as_bytes()[boundary] != b'.' { + if effective_domain.as_bytes().get(boundary) != Some(&b'.') { return false; } if &effective_domain[boundary + 1..] != rp_id { diff --git a/libwebauthn/src/ops/webauthn/psl/mod.rs b/libwebauthn/src/ops/webauthn/psl/mod.rs index 10ed069d..bd2c2da5 100644 --- a/libwebauthn/src/ops/webauthn/psl/mod.rs +++ b/libwebauthn/src/ops/webauthn/psl/mod.rs @@ -20,9 +20,6 @@ //! Most callers should use [`SystemPublicSuffixList::auto`], which probes //! the standard system paths for whichever format is available. -// Module-scoped until the crate-wide indexing_slicing deny lands. -#![cfg_attr(not(any(test, feature = "virt")), deny(clippy::indexing_slicing))] - pub mod dafsa; pub mod dat; mod system; diff --git a/libwebauthn/src/transport/hid/channel.rs b/libwebauthn/src/transport/hid/channel.rs index 6afe084b..1d9328d8 100644 --- a/libwebauthn/src/transport/hid/channel.rs +++ b/libwebauthn/src/transport/hid/channel.rs @@ -244,6 +244,8 @@ impl<'d> HidChannel<'d> { .open_device(&hidapi) .or(Err(Error::Transport(TransportError::ConnectionFailed)))?), #[cfg(feature = "virt")] + #[allow(clippy::unreachable)] + // virtual devices never go through hid_open HidBackendDevice::VirtualDevice(_) => unreachable!(), } } @@ -317,6 +319,8 @@ impl<'d> HidChannel<'d> { response } #[cfg(feature = "virt")] + #[allow(clippy::panic)] + // virt test-utility: a poisoned lock is unrecoverable OpenHidDevice::VirtualDevice(backend) => { let Ok(mut guard) = backend.lock() else { panic!("Poisoned lock on Virtual HID device"); @@ -378,6 +382,8 @@ impl<'d> HidChannel<'d> { })? } #[cfg(feature = "virt")] + #[allow(clippy::panic)] + // virt test-utility: a poisoned lock is unrecoverable OpenHidDevice::VirtualDevice(backend) => { let Ok(mut guard) = backend.lock() else { panic!("Poisoned lock on Virtual HID device"); diff --git a/libwebauthn/src/transport/hid/framing.rs b/libwebauthn/src/transport/hid/framing.rs index da086a70..3ebbcd0b 100644 --- a/libwebauthn/src/transport/hid/framing.rs +++ b/libwebauthn/src/transport/hid/framing.rs @@ -141,7 +141,10 @@ impl HidMessageParser { if self.packets.is_empty() { // First packet must be an initialization packet: high bit of // byte 4 set (CTAP 2.2 §11.2.4). - if packet[4] & PACKET_INITIAL_CMD_MASK == 0 { + let is_initialization = packet + .get(4) + .is_some_and(|&byte| byte & PACKET_INITIAL_CMD_MASK != 0); + if !is_initialization { error!("First packet is not an initialization packet"); return Err(IOError::new( IOErrorKind::InvalidData, @@ -151,15 +154,20 @@ impl HidMessageParser { } else { // Continuation packets: same CID as the initial packet, SEQ has // high bit cleared, SEQ starts at 0 and increments monotonically. - let initial = &self.packets[0]; - if packet[..4] != initial[..4] { + let initial_cid = self.packets.first().and_then(|initial| initial.get(..4)); + if packet.get(..4) != initial_cid { error!("Continuation packet CID does not match initial packet"); return Err(IOError::new( IOErrorKind::InvalidData, "Continuation packet CID mismatch", )); } - let seq = packet[4]; + let Some(&seq) = packet.get(4) else { + return Err(IOError::new( + IOErrorKind::InvalidData, + "Packet length is invalid", + )); + }; if seq & PACKET_INITIAL_CMD_MASK != 0 { error!(seq, "Unexpected init packet during continuation"); return Err(IOError::new(