diff --git a/src/auth.rs b/src/auth.rs index f9e62b4..5ed344e 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -3,7 +3,13 @@ use crate::config::AuthOptions; use crate::constants::{ATTR_NONCE, ATTR_REALM, ATTR_USERNAME}; use crate::models::stun::StunMessage; -use crate::stun::{find_message_integrity, validate_message_integrity, validate_message_integrity_len_preserved}; +use crate::stun::{ + compute_message_integrity_adjusted, + compute_message_integrity_len_preserved as compute_mi_len_preserved, + find_message_integrity, + validate_message_integrity, + validate_message_integrity_len_preserved, +}; use crate::traits::CredentialStore; use async_trait::async_trait; use base64::Engine; @@ -204,12 +210,24 @@ impl AuthManager { return AuthStatus::Granted { username, key }; } - // No acceptance without MI validation. + // No acceptance without MI validation. Emit detailed diagnostics. let mi_attr = find_message_integrity(msg).map(|a| hex::encode(&a.value)); - let mi_long = hex::encode(&crate::stun::compute_message_integrity(&key, msg.raw.as_slice())); - let mi_short = hex::encode(&crate::stun::compute_message_integrity(short_key, msg.raw.as_slice())); - warn!("auth reject: bad credentials username={} realm={} peer={} a1_md5={} mi_attr={:?} mi_long(fullmsg)={} mi_short(fullmsg)={}", - username, realm, peer, hex::encode(&key), mi_attr, mi_long, mi_short); + let mi_long_adj = compute_message_integrity_adjusted(msg, &key).map(hex::encode); + let mi_long_len = compute_mi_len_preserved(msg, &key).map(hex::encode); + let mi_short_adj = compute_message_integrity_adjusted(msg, short_key).map(hex::encode); + let mi_short_len = compute_mi_len_preserved(msg, short_key).map(hex::encode); + warn!( + "auth reject: bad credentials username={} realm={} peer={} a1_md5={} mi_attr={:?} mi_long_adj={:?} mi_long_len={:?} mi_short_adj={:?} mi_short_len={:?}", + username, + realm, + peer, + hex::encode(&key), + mi_attr, + mi_long_adj, + mi_long_len, + mi_short_adj, + mi_short_len + ); AuthStatus::Reject { code: 401, reason: "Bad Credentials", diff --git a/src/stun.rs b/src/stun.rs index 2406196..030bb8e 100644 --- a/src/stun.rs +++ b/src/stun.rs @@ -386,6 +386,51 @@ pub fn validate_message_integrity(msg: &StunMessage, key: &[u8]) -> bool { false } +/// Compute the expected MESSAGE-INTEGRITY value following the RFC-adjusted length rule. +pub fn compute_message_integrity_adjusted(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 mut signed = msg.raw[..mi_end].to_vec(); + let len = (mi_end - 20) as u16; + let len_bytes = len.to_be_bytes(); + signed[2] = len_bytes[0]; + signed[3] = len_bytes[1]; + + let value_start = mi.offset + 4; + for b in &mut signed[value_start..value_start + HMAC_SHA1_LEN] { + *b = 0; + } + + Some(crate::stun::compute_message_integrity(key, &signed)) +} + +/// Compute MESSAGE-INTEGRITY keeping the original header length (interop len-preserved mode). +pub fn compute_message_integrity_len_preserved(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 mut signed = msg.raw[..mi_end].to_vec(); + let value_start = mi.offset + 4; + for b in &mut signed[value_start..value_start + HMAC_SHA1_LEN] { + *b = 0; + } + + Some(crate::stun::compute_message_integrity(key, &signed)) +} + /// Fallback validator: compute MESSAGE-INTEGRITY without adjusting the STUN header length. /// Some clients incorrectly leave the header length unchanged when appending FINGERPRINT; /// this matches that behaviour for interop.