From a79d0f2a953215bc6dbd85f0941b4eff1ff704a4 Mon Sep 17 00:00:00 2001 From: ghost Date: Mon, 29 Dec 2025 02:44:44 +0100 Subject: [PATCH] Add: More MI variants. --- src/auth.rs | 47 ++++++++++++++++++++-- src/stun.rs | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index d9ad55e..aaf4c12 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -6,10 +6,13 @@ use crate::models::stun::StunMessage; use crate::stun::{ compute_message_integrity_adjusted, compute_message_integrity_adjusted_nozero, + compute_message_integrity_before_mi, compute_message_integrity_full, compute_message_integrity_full_nozero, + compute_message_integrity_full_len_to_mi_end, compute_message_integrity_len_preserved as compute_mi_len_preserved, compute_message_integrity_len_preserved_nozero, + compute_message_integrity_through_mi_header, find_message_integrity, validate_message_integrity, validate_message_integrity_len_preserved_nozero, @@ -356,10 +359,25 @@ impl AuthManager { let mi_short_full_adj_nozero = compute_message_integrity_full_nozero(msg, short_key, true, false).map(hex::encode); let mi_short_full_adj_nozero_zfp = compute_message_integrity_full_nozero(msg, short_key, true, true).map(hex::encode); + let mi_long_full_len_to_mi_end = + compute_message_integrity_full_len_to_mi_end(msg, &key, true, false).map(hex::encode); + let mi_long_full_len_to_mi_end_nozero = + compute_message_integrity_full_len_to_mi_end(msg, &key, false, false).map(hex::encode); + let mi_long_full_len_to_mi_end_nozero_zfp = + compute_message_integrity_full_len_to_mi_end(msg, &key, false, true).map(hex::encode); + let mi_short_full_len_to_mi_end = + compute_message_integrity_full_len_to_mi_end(msg, short_key, true, false).map(hex::encode); + + let mi_long_before_mi_len_to_mi_end = + compute_message_integrity_before_mi(msg, &key, true).map(hex::encode); + let mi_long_before_mi_len_before_mi = + compute_message_integrity_before_mi(msg, &key, false).map(hex::encode); + let mi_long_through_mi_hdr = compute_message_integrity_through_mi_header(msg, &key).map(hex::encode); + // Accept if any variant matches received MI (still requires correct key). if let Some(mi_attr_val) = find_message_integrity(msg) { let mi_bytes = &mi_attr_val.value; - let variants: [(&str, Option>); 12] = [ + let variants: [(&str, Option>); 28] = [ ("long_adj", compute_message_integrity_adjusted(msg, &key)), ("long_len", compute_mi_len_preserved(msg, &key)), ("long_adj_nozero", compute_message_integrity_adjusted_nozero(msg, &key)), @@ -372,6 +390,22 @@ impl AuthManager { ("long_full_len", compute_message_integrity_full(msg, &key, false)), ("short_full_adj", compute_message_integrity_full(msg, short_key, true)), ("short_full_len", compute_message_integrity_full(msg, short_key, false)), + ("long_full_adj_nozero", compute_message_integrity_full_nozero(msg, &key, true, false)), + ("long_full_adj_nozero_zfp", compute_message_integrity_full_nozero(msg, &key, true, true)), + ("short_full_adj_nozero", compute_message_integrity_full_nozero(msg, short_key, true, false)), + ("short_full_adj_nozero_zfp", compute_message_integrity_full_nozero(msg, short_key, true, true)), + ("long_full_len_to_mi_end", compute_message_integrity_full_len_to_mi_end(msg, &key, true, false)), + ("long_full_len_to_mi_end_nozero", compute_message_integrity_full_len_to_mi_end(msg, &key, false, false)), + ("long_full_len_to_mi_end_nozero_zfp", compute_message_integrity_full_len_to_mi_end(msg, &key, false, true)), + ("short_full_len_to_mi_end", compute_message_integrity_full_len_to_mi_end(msg, short_key, true, false)), + ("short_full_len_to_mi_end_nozero", compute_message_integrity_full_len_to_mi_end(msg, short_key, false, false)), + ("short_full_len_to_mi_end_nozero_zfp", compute_message_integrity_full_len_to_mi_end(msg, short_key, false, true)), + ("long_before_mi_len_to_mi_end", compute_message_integrity_before_mi(msg, &key, true)), + ("long_before_mi_len_before_mi", compute_message_integrity_before_mi(msg, &key, false)), + ("long_through_mi_header", compute_message_integrity_through_mi_header(msg, &key)), + ("short_before_mi_len_to_mi_end", compute_message_integrity_before_mi(msg, short_key, true)), + ("short_before_mi_len_before_mi", compute_message_integrity_before_mi(msg, short_key, false)), + ("short_through_mi_header", compute_message_integrity_through_mi_header(msg, short_key)), ]; for (label, cand) in variants.iter() { @@ -385,7 +419,7 @@ impl AuthManager { } warn!( - "auth reject: bad credentials username={} realm={} peer={} a1_md5={} mi_attr={:?} mi_long_adj={:?} mi_long_len={:?} mi_long_adj_nozero={:?} mi_long_len_nozero={:?} mi_short_adj={:?} mi_short_len={:?} mi_short_adj_nozero={:?} mi_short_len_nozero={:?} mi_long_full_adj={:?} mi_long_full_len={:?} mi_short_full_adj={:?} mi_short_full_len={:?} mi_long_full_adj_nozero={:?} mi_long_full_adj_nozero_zfp={:?} mi_short_full_adj_nozero={:?} mi_short_full_adj_nozero_zfp={:?}", + "auth reject: bad credentials username={} realm={} peer={} a1_md5={} mi_attr={:?} mi_long_adj={:?} mi_long_len={:?} mi_long_adj_nozero={:?} mi_long_len_nozero={:?} mi_short_adj={:?} mi_short_len={:?} mi_short_adj_nozero={:?} mi_short_len_nozero={:?} mi_long_full_adj={:?} mi_long_full_len={:?} mi_short_full_adj={:?} mi_short_full_len={:?} mi_long_full_adj_nozero={:?} mi_long_full_adj_nozero_zfp={:?} mi_short_full_adj_nozero={:?} mi_short_full_adj_nozero_zfp={:?} mi_long_full_len_to_mi_end={:?} mi_long_full_len_to_mi_end_nozero={:?} mi_long_full_len_to_mi_end_nozero_zfp={:?} mi_short_full_len_to_mi_end={:?} mi_long_before_mi_len_to_mi_end={:?} mi_long_before_mi_len_before_mi={:?} mi_long_through_mi_hdr={:?}", username, realm, peer, @@ -406,7 +440,14 @@ impl AuthManager { mi_long_full_adj_nozero, mi_long_full_adj_nozero_zfp, mi_short_full_adj_nozero, - mi_short_full_adj_nozero_zfp + mi_short_full_adj_nozero_zfp, + mi_long_full_len_to_mi_end, + mi_long_full_len_to_mi_end_nozero, + mi_long_full_len_to_mi_end_nozero_zfp, + mi_short_full_len_to_mi_end, + mi_long_before_mi_len_to_mi_end, + mi_long_before_mi_len_before_mi, + mi_long_through_mi_hdr ); AuthStatus::Reject { code: 401, diff --git a/src/stun.rs b/src/stun.rs index aed7fd8..3ae25f9 100644 --- a/src/stun.rs +++ b/src/stun.rs @@ -722,6 +722,118 @@ pub fn compute_message_integrity(key: &[u8], msg: &[u8]) -> Vec { mac.finalize().into_bytes().to_vec() } +/// Compute MESSAGE-INTEGRITY over the full raw message, but force the STUN header length +/// field to the end-of-MESSAGE-INTEGRITY value (i.e. exclude any attributes after MI +/// from the length field), while still MAC'ing the full buffer. +/// +/// This is non-standard, but it matches a class of buggy implementations where the +/// length field is adjusted correctly, yet the HMAC input accidentally includes bytes +/// after MESSAGE-INTEGRITY (e.g. FINGERPRINT). +pub fn compute_message_integrity_full_len_to_mi_end( + msg: &StunMessage, + key: &[u8], + zero_mi: bool, + zero_fingerprint: bool, +) -> Option> { + let mi = find_message_integrity(msg)?; + if mi.value.len() != HMAC_SHA1_LEN { + return None; + } + + let mi_end = mi.offset + 4 + HMAC_SHA1_LEN; + if mi_end > msg.raw.len() { + return None; + } + + let mut signed = msg.raw.clone(); + let len = (mi_end - 20) as u16; + signed[2..4].copy_from_slice(&len.to_be_bytes()); + + if zero_mi { + let v = mi.offset + 4; + if v + HMAC_SHA1_LEN > signed.len() { + return None; + } + signed[v..v + HMAC_SHA1_LEN].fill(0); + } + + if zero_fingerprint { + if let Some(fp) = find_fingerprint(msg) { + if fp.value.len() == 4 && fp.offset + 8 == msg.raw.len() { + let v = fp.offset + 4; + if v + 4 <= signed.len() { + signed[v..v + 4].fill(0); + } + } + } + } + + Some(crate::stun::compute_message_integrity(key, &signed)) +} + +/// Compute MESSAGE-INTEGRITY over the bytes *before* the MESSAGE-INTEGRITY attribute. +/// +/// Non-standard interop variant: some clients appear to MAC only the prefix and ignore +/// the MI attribute itself. +/// +/// If `len_to_mi_end` is true, the header length is set to end-of-MI (RFC-ish length) +/// even though the MAC input ends before MI. If false, the header length is set to the +/// prefix length (i.e. start-of-MI - 20). +pub fn compute_message_integrity_before_mi( + msg: &StunMessage, + key: &[u8], + len_to_mi_end: bool, +) -> Option> { + let mi = find_message_integrity(msg)?; + if mi.value.len() != HMAC_SHA1_LEN { + return None; + } + let mi_end = mi.offset + 4 + HMAC_SHA1_LEN; + if mi_end > msg.raw.len() { + return None; + } + if mi.offset < 20 { + return None; + } + + let mut signed = msg.raw[..mi.offset].to_vec(); + let len = if len_to_mi_end { + (mi_end - 20) as u16 + } else { + (mi.offset - 20) as u16 + }; + signed[2..4].copy_from_slice(&len.to_be_bytes()); + Some(crate::stun::compute_message_integrity(key, &signed)) +} + +/// Compute MESSAGE-INTEGRITY over the bytes up to the MI attribute header (type+len), +/// excluding the MI value bytes. +/// +/// Non-standard interop variant: clients that include the MI header but not its value. +pub fn compute_message_integrity_through_mi_header( + msg: &StunMessage, + key: &[u8], +) -> Option> { + let mi = find_message_integrity(msg)?; + if mi.value.len() != HMAC_SHA1_LEN { + return None; + } + let mi_end = mi.offset + 4 + HMAC_SHA1_LEN; + if mi_end > msg.raw.len() { + return None; + } + + let end = mi.offset + 4; + if end > msg.raw.len() { + return None; + } + + let mut signed = msg.raw[..end].to_vec(); + let len = (mi_end - 20) as u16; + signed[2..4].copy_from_slice(&len.to_be_bytes()); + Some(crate::stun::compute_message_integrity(key, &signed)) +} + /// STUN/TURN attribute type for XOR-RELAYED-ADDRESS per RFC5766 /// (use ATTR_XOR_RELAYED_ADDRESS from crate::constants) // no-op; refer to constants::ATTR_XOR_RELAYED_ADDRESS where needed