stamparm/maltrail · info

hmac accepts any key length

Error message

hmac accepts any key length

What it means

`hkdf_extract` initializes an HMAC-SHA256 instance from the salt via `Mac::new_from_slice(salt).expect("hmac accepts any key length")`. HMAC (and the `hmac`/`digest` crates' `Mac` trait) accepts keys of any length, so `new_from_slice` is infallible in practice and returns `Result` only for API symmetry; the `expect` documents that invariant. A panic here would indicate the HMAC implementation changed or the salt/ikm arguments were swapped into an invalid position.

Solutions

  1. Confirm the `hmac` crate version still guarantees arbitrary key lengths (it does for HMAC per RFC 2104); pin/adjust Cargo.toml if an upgrade altered behavior.
  2. If the primitive was swapped, replace `expect` with proper error handling: match on `new_from_slice` and propagate `Err` to the QUIC key-derivation caller.
  3. Keep the `expect` but add a comment/test asserting arbitrary-length salts (0-byte and very long keys) derive without panic.
  4. Run the QUIC initial-key derivation test vectors (RFC 9001) to verify the derive path is intact.

Example fix

// before
let mut mac = <HmacSha256 as Mac>::new_from_slice(salt).expect("hmac accepts any key length");
// after (robust if the MAC type may enforce key lengths)
let mut mac = <HmacSha256 as Mac>::new_from_slice(salt)
    .expect("HMAC accepts any key length per RFC 2104"); // or map_err and return Result<[u8;32], MacError>
Defensive patterns

Strategy: type-guard

Validate before calling

// HMAC accepts any key length (RFC 2104); assert in a unit test
#[test] fn hmac_any_salt_len() { for n in [0usize, 1, 32, 1024] { let _ = hkdf_extract(&vec![0u8; n], b"ikm"); } }

Type guard

fn salt_is_bytes(s: &[u8]) -> bool { true } // any &[u8] is a valid HMAC key

Try / catch

let mut mac = <HmacSha256 as Mac>::new_from_slice(salt)
    .map_err(|e| CryptoError::MacInit(e))?; // only if MAC type may restrict keys

Prevention

When it happens

Trigger: Panic occurs only if `Hmac::<Sha256>::new_from_slice` returns `Err`, which the current `hmac` crate never does for any byte slice. Practically triggered by upgrading `hmac`/`digest` to a version with different trait semantics, or by a refactor that replaces `HmacSha256` with a MAC that does enforce key-length limits (e.g. a fixed-key cipher-based MAC).

Common situations: Dependency upgrades changing the `Mac` trait; swapping the primitive in `type HmacSha256` for something with a key-length constraint; misuse where a caller passes a typed non-byte argument that fails conversion.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of stamparm/maltrail@77cfb06d76 (2026-09-13). Data as JSON: /api/errors/9683524a2b48a1b3. Report an issue: GitHub.

Appendix: source

Thrown at sensor/src/protocols/quic.rs:36

/// `core/quic_sni.py:MAX_INITIAL_DECRYPT`
pub const MAX_INITIAL_DECRYPT: usize = 2048;

/// RFC 9001 (QUIC v1) initial salt
const INITIAL_SALT_V1: [u8; 20] = [
    0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f,
    0x0a,
];
/// RFC 9369 (QUIC v2) initial salt
const INITIAL_SALT_V2: [u8; 20] = [
    0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e,
    0xd9,
];

type HmacSha256 = Hmac<Sha256>;

fn hkdf_extract(salt: &[u8], ikm: &[u8]) -> [u8; 32] {
    let mut mac = <HmacSha256 as Mac>::new_from_slice(salt).expect("hmac accepts any key length");
    mac.update(ikm);
    mac.finalize().into_bytes().into()
}

/// Every output this schedule asks for is 32 bytes or fewer, so the buffers live on the stack.
///
/// The general form allocated a Vec for the output, another for the running block, and one more
/// per HMAC round via `to_vec()` - and `hkdf_expand_label` added two more building its info
/// string. Four labels are derived for every QUIC Initial packet, so that was a dozen small
/// allocations on a path that runs per packet. Identical bytes out: the loop is unchanged, it
/// just writes into fixed storage.
const HKDF_MAX: usize = 32;

fn hkdf_expand_into(prk: &[u8], info: &[u8], length: usize, out: &mut [u8; HKDF_MAX]) {
    debug_assert!(length <= HKDF_MAX);
    let mut written = 0usize;
    let mut have_prev = false;
    let mut prev = [0u8; 32];

View on GitHub (pinned to 77cfb06d76)