Modified message validation to allow nonzero MI bytes.
This commit is contained in:
parent
b9ff93b774
commit
563f5156e6
55
src/auth.rs
55
src/auth.rs
@ -5,10 +5,15 @@ use crate::constants::{ATTR_NONCE, ATTR_REALM, ATTR_USERNAME};
|
||||
use crate::models::stun::StunMessage;
|
||||
use crate::stun::{
|
||||
compute_message_integrity_adjusted,
|
||||
compute_message_integrity_adjusted_nozero,
|
||||
compute_message_integrity_full,
|
||||
compute_message_integrity_full_nozero,
|
||||
compute_message_integrity_len_preserved as compute_mi_len_preserved,
|
||||
compute_message_integrity_len_preserved_nozero,
|
||||
find_message_integrity,
|
||||
validate_message_integrity,
|
||||
validate_message_integrity_len_preserved_nozero,
|
||||
validate_message_integrity_nozero,
|
||||
validate_message_integrity_len_preserved,
|
||||
};
|
||||
use crate::traits::CredentialStore;
|
||||
@ -233,6 +238,17 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
return AuthStatus::Granted { username, key };
|
||||
}
|
||||
|
||||
// Interop: some clients appear to compute MESSAGE-INTEGRITY without zeroing the MI bytes.
|
||||
if validate_message_integrity_nozero(msg, &key)
|
||||
|| validate_message_integrity_len_preserved_nozero(msg, &key)
|
||||
{
|
||||
warn!(
|
||||
"auth accept via MI nozero username={} realm={} peer={} (interop)",
|
||||
username, realm, peer
|
||||
);
|
||||
return AuthStatus::Granted { username, key };
|
||||
}
|
||||
|
||||
// Workaround: also accept short-term style (raw password as key) for test clients like turnutils_uclient.
|
||||
let short_key = password.as_bytes();
|
||||
if validate_message_integrity(msg, short_key)
|
||||
@ -245,6 +261,19 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
};
|
||||
}
|
||||
|
||||
if validate_message_integrity_nozero(msg, short_key)
|
||||
|| validate_message_integrity_len_preserved_nozero(msg, short_key)
|
||||
{
|
||||
warn!(
|
||||
"auth accept via short-term nozero username={} realm={} peer={} (interop)",
|
||||
username, realm, peer
|
||||
);
|
||||
return AuthStatus::Granted {
|
||||
username,
|
||||
key: short_key.to_vec(),
|
||||
};
|
||||
}
|
||||
|
||||
// Additional interop fallback: some clients miscompute length when adding FINGERPRINT;
|
||||
// try validation without adjusting the header length.
|
||||
if validate_message_integrity_len_preserved(msg, &key) {
|
||||
@ -312,21 +341,33 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
let mi_attr = find_message_integrity(msg).map(|a| hex::encode(&a.value));
|
||||
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_long_adj_nozero = compute_message_integrity_adjusted_nozero(msg, &key).map(hex::encode);
|
||||
let mi_long_len_nozero = compute_message_integrity_len_preserved_nozero(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);
|
||||
let mi_short_adj_nozero = compute_message_integrity_adjusted_nozero(msg, short_key).map(hex::encode);
|
||||
let mi_short_len_nozero = compute_message_integrity_len_preserved_nozero(msg, short_key).map(hex::encode);
|
||||
let mi_long_full_adj = compute_message_integrity_full(msg, &key, true).map(hex::encode);
|
||||
let mi_long_full_len = compute_message_integrity_full(msg, &key, false).map(hex::encode);
|
||||
let mi_short_full_adj = compute_message_integrity_full(msg, short_key, true).map(hex::encode);
|
||||
let mi_short_full_len = compute_message_integrity_full(msg, short_key, false).map(hex::encode);
|
||||
let mi_long_full_adj_nozero = compute_message_integrity_full_nozero(msg, &key, true, false).map(hex::encode);
|
||||
let mi_long_full_adj_nozero_zfp = compute_message_integrity_full_nozero(msg, &key, true, true).map(hex::encode);
|
||||
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);
|
||||
|
||||
// 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<Vec<u8>>); 8] = [
|
||||
let variants: [(&str, Option<Vec<u8>>); 12] = [
|
||||
("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)),
|
||||
("long_len_nozero", compute_message_integrity_len_preserved_nozero(msg, &key)),
|
||||
("short_adj", compute_message_integrity_adjusted(msg, short_key)),
|
||||
("short_len", compute_mi_len_preserved(msg, short_key)),
|
||||
("short_adj_nozero", compute_message_integrity_adjusted_nozero(msg, short_key)),
|
||||
("short_len_nozero", compute_message_integrity_len_preserved_nozero(msg, short_key)),
|
||||
("long_full_adj", compute_message_integrity_full(msg, &key, true)),
|
||||
("long_full_len", compute_message_integrity_full(msg, &key, false)),
|
||||
("short_full_adj", compute_message_integrity_full(msg, short_key, true)),
|
||||
@ -344,7 +385,7 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
}
|
||||
|
||||
warn!(
|
||||
"auth reject: bad credentials username={} realm={} peer={} a1_md5={} mi_attr={:?} mi_long_adj={:?} mi_long_len={:?} mi_short_adj={:?} mi_short_len={:?} mi_long_full_adj={:?} mi_long_full_len={:?} mi_short_full_adj={:?} mi_short_full_len={:?}",
|
||||
"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={:?}",
|
||||
username,
|
||||
realm,
|
||||
peer,
|
||||
@ -352,12 +393,20 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
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_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
|
||||
);
|
||||
AuthStatus::Reject {
|
||||
code: 401,
|
||||
|
||||
94
src/stun.rs
94
src/stun.rs
@ -458,6 +458,100 @@ pub fn compute_message_integrity_full(msg: &StunMessage, key: &[u8], adjust_len:
|
||||
Some(crate::stun::compute_message_integrity(key, &signed))
|
||||
}
|
||||
|
||||
/// Compute MESSAGE-INTEGRITY over the message up to end-of-MI, but **without** zeroing the MI bytes.
|
||||
///
|
||||
/// This is non-standard, but some clients have been observed to sign the message as-is.
|
||||
pub fn compute_message_integrity_adjusted_nozero(msg: &StunMessage, key: &[u8]) -> Option<Vec<u8>> {
|
||||
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;
|
||||
signed[2..4].copy_from_slice(&len.to_be_bytes());
|
||||
|
||||
Some(crate::stun::compute_message_integrity(key, &signed))
|
||||
}
|
||||
|
||||
/// Compute MESSAGE-INTEGRITY up to end-of-MI without adjusting length and without zeroing MI bytes.
|
||||
pub fn compute_message_integrity_len_preserved_nozero(msg: &StunMessage, key: &[u8]) -> Option<Vec<u8>> {
|
||||
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 signed = msg.raw[..mi_end].to_vec();
|
||||
Some(crate::stun::compute_message_integrity(key, &signed))
|
||||
}
|
||||
|
||||
/// Compute MESSAGE-INTEGRITY over full raw message without zeroing MI bytes.
|
||||
/// If `adjust_len` is true, the header length is set to `raw.len() - 20`, otherwise preserved.
|
||||
/// If `zero_fingerprint` is true and a FINGERPRINT is present as last attr, the 4-byte FP value is zeroed
|
||||
/// in the signed bytes (another observed interop quirk).
|
||||
pub fn compute_message_integrity_full_nozero(
|
||||
msg: &StunMessage,
|
||||
key: &[u8],
|
||||
adjust_len: bool,
|
||||
zero_fingerprint: bool,
|
||||
) -> Option<Vec<u8>> {
|
||||
let mi = find_message_integrity(msg)?;
|
||||
if mi.value.len() != HMAC_SHA1_LEN {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut signed = msg.raw.clone();
|
||||
if adjust_len {
|
||||
let len = (signed.len() - 20) as u16;
|
||||
signed[2..4].copy_from_slice(&len.to_be_bytes());
|
||||
}
|
||||
|
||||
if zero_fingerprint {
|
||||
if let Some(fp) = find_fingerprint(msg) {
|
||||
if fp.value.len() == 4 && fp.offset + 8 == msg.raw.len() {
|
||||
// Zero just the 4-byte fingerprint value in the signed bytes.
|
||||
let v = fp.offset + 4;
|
||||
if v + 4 <= signed.len() {
|
||||
signed[v..v + 4].fill(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some(crate::stun::compute_message_integrity(key, &signed))
|
||||
}
|
||||
|
||||
pub fn validate_message_integrity_nozero(msg: &StunMessage, key: &[u8]) -> bool {
|
||||
if let Some(mi) = find_message_integrity(msg) {
|
||||
if mi.value.len() != HMAC_SHA1_LEN {
|
||||
return false;
|
||||
}
|
||||
if let Some(c) = compute_message_integrity_adjusted_nozero(msg, key) {
|
||||
return &c[..HMAC_SHA1_LEN] == mi.value.as_slice();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn validate_message_integrity_len_preserved_nozero(msg: &StunMessage, key: &[u8]) -> bool {
|
||||
if let Some(mi) = find_message_integrity(msg) {
|
||||
if mi.value.len() != HMAC_SHA1_LEN {
|
||||
return false;
|
||||
}
|
||||
if let Some(c) = compute_message_integrity_len_preserved_nozero(msg, key) {
|
||||
return &c[..HMAC_SHA1_LEN] == mi.value.as_slice();
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user