Changed response message signing to match accepted message integrity of request.

This commit is contained in:
ghost 2025-12-29 03:04:22 +01:00
parent a79d0f2a95
commit a434a6ad8a
4 changed files with 295 additions and 87 deletions

View File

@ -14,6 +14,7 @@ use crate::stun::{
compute_message_integrity_len_preserved_nozero, compute_message_integrity_len_preserved_nozero,
compute_message_integrity_through_mi_header, compute_message_integrity_through_mi_header,
find_message_integrity, find_message_integrity,
MessageIntegrityMode,
validate_message_integrity, validate_message_integrity,
validate_message_integrity_len_preserved_nozero, validate_message_integrity_len_preserved_nozero,
validate_message_integrity_nozero, validate_message_integrity_nozero,
@ -89,7 +90,11 @@ impl AuthSettings {
/// Result of validating authentication attributes on an incoming STUN/TURN request. /// Result of validating authentication attributes on an incoming STUN/TURN request.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum AuthStatus { pub enum AuthStatus {
Granted { username: String, key: Vec<u8> }, Granted {
username: String,
key: Vec<u8>,
mi_mode: MessageIntegrityMode,
},
Challenge { nonce: String }, Challenge { nonce: String },
StaleNonce { nonce: String }, StaleNonce { nonce: String },
Reject { code: u16, reason: &'static str }, Reject { code: u16, reason: &'static str },
@ -238,7 +243,11 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
let key = self.derive_long_term_key(&username, &password); let key = self.derive_long_term_key(&username, &password);
// Primary: long-term (MD5(username:realm:password)) // Primary: long-term (MD5(username:realm:password))
if validate_message_integrity(msg, &key) { if validate_message_integrity(msg, &key) {
return AuthStatus::Granted { username, key }; return AuthStatus::Granted {
username,
key,
mi_mode: MessageIntegrityMode::Rfc5389,
};
} }
// Interop: some clients appear to compute MESSAGE-INTEGRITY without zeroing the MI bytes. // Interop: some clients appear to compute MESSAGE-INTEGRITY without zeroing the MI bytes.
@ -249,7 +258,11 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
"auth accept via MI nozero username={} realm={} peer={} (interop)", "auth accept via MI nozero username={} realm={} peer={} (interop)",
username, realm, peer username, realm, peer
); );
return AuthStatus::Granted { username, key }; return AuthStatus::Granted {
username,
key,
mi_mode: MessageIntegrityMode::Rfc5389,
};
} }
// Workaround: also accept short-term style (raw password as key) for test clients like turnutils_uclient. // Workaround: also accept short-term style (raw password as key) for test clients like turnutils_uclient.
@ -261,6 +274,7 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
return AuthStatus::Granted { return AuthStatus::Granted {
username, username,
key: short_key.to_vec(), key: short_key.to_vec(),
mi_mode: MessageIntegrityMode::Rfc5389,
}; };
} }
@ -274,6 +288,7 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
return AuthStatus::Granted { return AuthStatus::Granted {
username, username,
key: short_key.to_vec(), key: short_key.to_vec(),
mi_mode: MessageIntegrityMode::Rfc5389,
}; };
} }
@ -281,7 +296,11 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
// try validation without adjusting the header length. // try validation without adjusting the header length.
if validate_message_integrity_len_preserved(msg, &key) { if validate_message_integrity_len_preserved(msg, &key) {
warn!("auth accept via len-preserved MI username={} realm={} peer={} (interop fallback)", username, realm, peer); warn!("auth accept via len-preserved MI username={} realm={} peer={} (interop fallback)", username, realm, peer);
return AuthStatus::Granted { username, key }; return AuthStatus::Granted {
username,
key,
mi_mode: MessageIntegrityMode::Rfc5389,
};
} }
// No acceptance without MI validation. Emit detailed diagnostics. // No acceptance without MI validation. Emit detailed diagnostics.
@ -411,8 +430,25 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
for (label, cand) in variants.iter() { for (label, cand) in variants.iter() {
if let Some(c) = cand { if let Some(c) = cand {
if c.len() >= 20 && &c[..20] == mi_bytes.as_slice() { if c.len() >= 20 && &c[..20] == mi_bytes.as_slice() {
let mi_mode = if *label == "long_before_mi_len_to_mi_end"
|| *label == "short_before_mi_len_to_mi_end"
{
MessageIntegrityMode::BeforeMiLenToMiEnd
} else {
MessageIntegrityMode::Rfc5389
};
let chosen_key = if label.starts_with("short_") {
short_key.to_vec()
} else {
key.clone()
};
warn!("auth accept via MI variant={} username={} realm={} peer={} (interop)", label, username, realm, peer); warn!("auth accept via MI variant={} username={} realm={} peer={} (interop)", label, username, realm, peer);
return AuthStatus::Granted { username, key }; return AuthStatus::Granted {
username,
key: chosen_key,
mi_mode,
};
} }
} }
} }

View File

@ -9,11 +9,11 @@ use crate::auth::{AuthManager, AuthStatus, InMemoryStore};
use crate::constants::*; use crate::constants::*;
use crate::rate_limit::RateLimiters; use crate::rate_limit::RateLimiters;
use crate::stun::{ use crate::stun::{
build_401_response, build_allocate_success_with_integrity, build_error_response, build_401_response, build_allocate_success_with_integrity_mode, build_error_response,
build_error_response_with_integrity, build_lifetime_success_with_integrity, build_error_response_with_integrity_mode, build_lifetime_success_with_integrity_mode,
build_success_response_with_integrity, decode_xor_peer_address, extract_lifetime_seconds, build_success_response_with_integrity_mode, decode_xor_peer_address,
parse_channel_data, extract_requested_transport_protocol, parse_message, extract_lifetime_seconds, extract_requested_transport_protocol, parse_channel_data,
validate_fingerprint_if_present, parse_message, validate_fingerprint_if_present,
}; };
use std::time::Duration; use std::time::Duration;
@ -105,15 +105,19 @@ pub async fn udp_reader_loop_with_limits(
); );
if requires_auth { if requires_auth {
let key = match auth.authenticate(&msg, &peer).await { let (key, mi_mode) = match auth.authenticate(&msg, &peer).await {
AuthStatus::Granted { username, key } => { AuthStatus::Granted {
username,
key,
mi_mode,
} => {
tracing::debug!( tracing::debug!(
"TURN auth ok for {} as {} (0x{:04x})", "TURN auth ok for {} as {} (0x{:04x})",
peer, peer,
username, username,
msg.header.msg_type msg.header.msg_type
); );
key (key, mi_mode)
} }
AuthStatus::Challenge { nonce } => { AuthStatus::Challenge { nonce } => {
crate::metrics::inc_auth_challenge(); crate::metrics::inc_auth_challenge();
@ -164,22 +168,24 @@ pub async fn udp_reader_loop_with_limits(
Some(IPPROTO_UDP) => {} Some(IPPROTO_UDP) => {}
Some(_) => { Some(_) => {
crate::metrics::inc_allocate_fail(); crate::metrics::inc_allocate_fail();
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
442, 442,
"Unsupported Transport", "Unsupported Transport",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
} }
None => { None => {
crate::metrics::inc_allocate_fail(); crate::metrics::inc_allocate_fail();
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Missing REQUESTED-TRANSPORT", "Missing REQUESTED-TRANSPORT",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -207,11 +213,12 @@ pub async fn udp_reader_loop_with_limits(
peer, peer,
e e
); );
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
500, 500,
"Allocate Failed", "Allocate Failed",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -220,11 +227,12 @@ pub async fn udp_reader_loop_with_limits(
let lifetime_secs = applied.as_secs().min(u32::MAX as u64) as u32; let lifetime_secs = applied.as_secs().min(u32::MAX as u64) as u32;
let advertised = allocs.relay_addr_for_response(relay_addr); let advertised = allocs.relay_addr_for_response(relay_addr);
let resp = build_allocate_success_with_integrity( let resp = build_allocate_success_with_integrity_mode(
&msg.header, &msg.header,
&advertised, &advertised,
lifetime_secs, lifetime_secs,
&key, &key,
mi_mode,
); );
tracing::info!( tracing::info!(
"allocated relay {} for {} lifetime={}s", "allocated relay {} for {} lifetime={}s",
@ -244,7 +252,13 @@ pub async fn udp_reader_loop_with_limits(
_ => (500, "Allocate Failed"), _ => (500, "Allocate Failed"),
}; };
crate::metrics::inc_allocate_fail(); crate::metrics::inc_allocate_fail();
let resp = build_error_response_with_integrity(&msg.header, code, reason, &key); let resp = build_error_response_with_integrity_mode(
&msg.header,
code,
reason,
&key,
mi_mode,
);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} }
} }
@ -254,7 +268,13 @@ pub async fn udp_reader_loop_with_limits(
if allocs.get_allocation(&peer).is_none() { if allocs.get_allocation(&peer).is_none() {
warn!("create-permission without allocation from {}", peer); warn!("create-permission without allocation from {}", peer);
let resp = let resp =
build_error_response_with_integrity(&msg.header, 437, "Allocation Mismatch", &key); build_error_response_with_integrity_mode(
&msg.header,
437,
"Allocation Mismatch",
&key,
mi_mode,
);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
} }
@ -289,11 +309,12 @@ pub async fn udp_reader_loop_with_limits(
e.downcast_ref::<AllocationError>(), e.downcast_ref::<AllocationError>(),
Some(AllocationError::PermissionQuotaExceeded) Some(AllocationError::PermissionQuotaExceeded)
) { ) {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
508, 508,
"Insufficient Capacity", "Insufficient Capacity",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -306,11 +327,17 @@ pub async fn udp_reader_loop_with_limits(
} }
if added == 0 { if added == 0 {
let resp = let resp = build_error_response_with_integrity_mode(
build_error_response_with_integrity(&msg.header, 400, "No valid XOR-PEER-ADDRESS", &key); &msg.header,
400,
"No valid XOR-PEER-ADDRESS",
&key,
mi_mode,
);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} else { } else {
let resp = build_success_response_with_integrity(&msg.header, &key); let resp =
build_success_response_with_integrity_mode(&msg.header, &key, mi_mode);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} }
continue; continue;
@ -320,8 +347,13 @@ pub async fn udp_reader_loop_with_limits(
Some(a) => a, Some(a) => a,
None => { None => {
warn!("channel-bind without allocation from {}", peer); warn!("channel-bind without allocation from {}", peer);
let resp = let resp = build_error_response_with_integrity_mode(
build_error_response_with_integrity(&msg.header, 437, "Allocation Mismatch", &key); &msg.header,
437,
"Allocation Mismatch",
&key,
mi_mode,
);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
} }
@ -336,11 +368,12 @@ pub async fn udp_reader_loop_with_limits(
let (channel_attr, peer_attr) = match (channel_attr, peer_attr) { let (channel_attr, peer_attr) = match (channel_attr, peer_attr) {
(Some(c), Some(p)) => (c, p), (Some(c), Some(p)) => (c, p),
_ => { _ => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Missing CHANNEL-NUMBER or XOR-PEER-ADDRESS", "Missing CHANNEL-NUMBER or XOR-PEER-ADDRESS",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -355,11 +388,12 @@ pub async fn udp_reader_loop_with_limits(
) { ) {
Some(addr) => addr, Some(addr) => addr,
None => { None => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Invalid XOR-PEER-ADDRESS", "Invalid XOR-PEER-ADDRESS",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -367,11 +401,12 @@ pub async fn udp_reader_loop_with_limits(
}; };
if !allocation.is_peer_allowed(&peer_addr) { if !allocation.is_peer_allowed(&peer_addr) {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
403, 403,
"Peer Not Permitted", "Peer Not Permitted",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -391,13 +426,19 @@ pub async fn udp_reader_loop_with_limits(
} }
_ => (500, "Channel Bind Failed"), _ => (500, "Channel Bind Failed"),
}; };
let resp = build_error_response_with_integrity(&msg.header, code, reason, &key); let resp = build_error_response_with_integrity_mode(
&msg.header,
code,
reason,
&key,
mi_mode,
);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
} }
crate::metrics::inc_channel_binding_added(); crate::metrics::inc_channel_binding_added();
let resp = build_success_response_with_integrity(&msg.header, &key); let resp = build_success_response_with_integrity_mode(&msg.header, &key, mi_mode);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
} }
@ -406,8 +447,13 @@ pub async fn udp_reader_loop_with_limits(
Some(a) => a, Some(a) => a,
None => { None => {
warn!("send indication without allocation from {}", peer); warn!("send indication without allocation from {}", peer);
let resp = let resp = build_error_response_with_integrity_mode(
build_error_response_with_integrity(&msg.header, 437, "Allocation Mismatch", &key); &msg.header,
437,
"Allocation Mismatch",
&key,
mi_mode,
);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
} }
@ -421,11 +467,12 @@ pub async fn udp_reader_loop_with_limits(
let (peer_attr, data_attr) = match (peer_attr, data_attr) { let (peer_attr, data_attr) = match (peer_attr, data_attr) {
(Some(p), Some(d)) => (p, d), (Some(p), Some(d)) => (p, d),
_ => { _ => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Missing DATA or XOR-PEER-ADDRESS", "Missing DATA or XOR-PEER-ADDRESS",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -438,11 +485,12 @@ pub async fn udp_reader_loop_with_limits(
) { ) {
Some(addr) => addr, Some(addr) => addr,
None => { None => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Invalid XOR-PEER-ADDRESS", "Invalid XOR-PEER-ADDRESS",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -450,11 +498,12 @@ pub async fn udp_reader_loop_with_limits(
}; };
if !allocation.is_peer_allowed(&peer_addr) { if !allocation.is_peer_allowed(&peer_addr) {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
403, 403,
"Peer Not Permitted", "Peer Not Permitted",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;
@ -468,7 +517,8 @@ pub async fn udp_reader_loop_with_limits(
peer, peer,
peer_addr peer_addr
); );
let resp = build_success_response_with_integrity(&msg.header, &key); let resp =
build_success_response_with_integrity_mode(&msg.header, &key, mi_mode);
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} }
Err(e) => { Err(e) => {
@ -478,11 +528,12 @@ pub async fn udp_reader_loop_with_limits(
peer_addr, peer_addr,
e e
); );
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
500, 500,
"Peer Send Failed", "Peer Send Failed",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} }
@ -504,19 +555,21 @@ pub async fn udp_reader_loop_with_limits(
applied.as_secs() applied.as_secs()
); );
} }
let resp = build_lifetime_success_with_integrity( let resp = build_lifetime_success_with_integrity_mode(
&msg.header, &msg.header,
applied.as_secs().min(u32::MAX as u64) as u32, applied.as_secs().min(u32::MAX as u64) as u32,
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} }
Err(_) => { Err(_) => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
437, 437,
"Allocation Mismatch", "Allocation Mismatch",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
} }
@ -524,11 +577,12 @@ pub async fn udp_reader_loop_with_limits(
continue; continue;
} }
_ => { _ => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
420, 420,
"Unknown TURN Method", "Unknown TURN Method",
&key, &key,
mi_mode,
); );
let _ = udp.send_to(&resp, &peer).await; let _ = udp.send_to(&resp, &peer).await;
continue; continue;

View File

@ -5,6 +5,15 @@ use crate::models::stun::{StunAttribute, StunHeader, StunMessage};
use std::convert::TryInto; use std::convert::TryInto;
use uuid::Uuid; use uuid::Uuid;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum MessageIntegrityMode {
/// RFC 5389-compliant MESSAGE-INTEGRITY calculation.
Rfc5389,
/// Interop mode observed with `turnutils_uclient`: HMAC is computed over bytes *before*
/// the MESSAGE-INTEGRITY attribute, with the STUN header length field set to end-of-MI.
BeforeMiLenToMiEnd,
}
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum ParseError { pub enum ParseError {
#[error("too short")] #[error("too short")]
@ -158,6 +167,17 @@ pub fn build_error_response_with_integrity(
code: u16, code: u16,
reason: &str, reason: &str,
key: &[u8], key: &[u8],
) -> Vec<u8> {
build_error_response_with_integrity_mode(req, code, reason, key, MessageIntegrityMode::Rfc5389)
}
/// Build a generic STUN/TURN error response including MESSAGE-INTEGRITY and FINGERPRINT.
pub fn build_error_response_with_integrity_mode(
req: &StunHeader,
code: u16,
reason: &str,
key: &[u8],
mi_mode: MessageIntegrityMode,
) -> Vec<u8> { ) -> Vec<u8> {
use bytes::BytesMut; use bytes::BytesMut;
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
@ -182,7 +202,7 @@ pub fn build_error_response_with_integrity(
buf.extend_from_slice(&[0]); buf.extend_from_slice(&[0]);
} }
append_message_integrity(&mut buf, key); append_message_integrity_with_mode(&mut buf, key, mi_mode);
append_fingerprint(&mut buf); append_fingerprint(&mut buf);
buf.to_vec() buf.to_vec()
} }
@ -227,6 +247,23 @@ pub fn build_allocate_success_with_integrity(
relay: &std::net::SocketAddr, relay: &std::net::SocketAddr,
lifetime_secs: u32, lifetime_secs: u32,
key: &[u8], key: &[u8],
) -> Vec<u8> {
build_allocate_success_with_integrity_mode(
req,
relay,
lifetime_secs,
key,
MessageIntegrityMode::Rfc5389,
)
}
/// Build an Allocate success response including MESSAGE-INTEGRITY and FINGERPRINT.
pub fn build_allocate_success_with_integrity_mode(
req: &StunHeader,
relay: &std::net::SocketAddr,
lifetime_secs: u32,
key: &[u8],
mi_mode: MessageIntegrityMode,
) -> Vec<u8> { ) -> Vec<u8> {
use bytes::BytesMut; use bytes::BytesMut;
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
@ -252,7 +289,7 @@ pub fn build_allocate_success_with_integrity(
buf.extend_from_slice(&[0]); buf.extend_from_slice(&[0]);
} }
append_message_integrity(&mut buf, key); append_message_integrity_with_mode(&mut buf, key, mi_mode);
append_fingerprint(&mut buf); append_fingerprint(&mut buf);
buf.to_vec() buf.to_vec()
} }
@ -284,6 +321,16 @@ pub fn build_lifetime_success_with_integrity(
req: &StunHeader, req: &StunHeader,
lifetime_secs: u32, lifetime_secs: u32,
key: &[u8], key: &[u8],
) -> Vec<u8> {
build_lifetime_success_with_integrity_mode(req, lifetime_secs, key, MessageIntegrityMode::Rfc5389)
}
/// Build a Refresh success response including MESSAGE-INTEGRITY and FINGERPRINT.
pub fn build_lifetime_success_with_integrity_mode(
req: &StunHeader,
lifetime_secs: u32,
key: &[u8],
mi_mode: MessageIntegrityMode,
) -> Vec<u8> { ) -> Vec<u8> {
use bytes::BytesMut; use bytes::BytesMut;
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
@ -301,7 +348,7 @@ pub fn build_lifetime_success_with_integrity(
buf.extend_from_slice(&[0]); buf.extend_from_slice(&[0]);
} }
append_message_integrity(&mut buf, key); append_message_integrity_with_mode(&mut buf, key, mi_mode);
append_fingerprint(&mut buf); append_fingerprint(&mut buf);
buf.to_vec() buf.to_vec()
} }
@ -578,9 +625,14 @@ pub fn validate_message_integrity_len_preserved(msg: &StunMessage, key: &[u8]) -
false false
} }
fn append_message_integrity(buf: &mut bytes::BytesMut, key: &[u8]) { fn append_message_integrity_with_mode(
buf: &mut bytes::BytesMut,
key: &[u8],
mi_mode: MessageIntegrityMode,
) {
// Append attribute header and placeholder; set length to end-of-MI, then compute // Append attribute header and placeholder; set length to end-of-MI, then compute
// HMAC over the message slice up to end-of-MI (with the MI placeholder still zero). // HMAC over the message slice up to end-of-MI (with the MI placeholder still zero).
let mi_attr_offset = buf.len();
buf.extend_from_slice(&ATTR_MESSAGE_INTEGRITY.to_be_bytes()); buf.extend_from_slice(&ATTR_MESSAGE_INTEGRITY.to_be_bytes());
buf.extend_from_slice(&((HMAC_SHA1_LEN as u16).to_be_bytes())); buf.extend_from_slice(&((HMAC_SHA1_LEN as u16).to_be_bytes()));
let mi_val_pos = buf.len(); let mi_val_pos = buf.len();
@ -592,12 +644,22 @@ fn append_message_integrity(buf: &mut bytes::BytesMut, key: &[u8]) {
let mi_end = buf.len(); let mi_end = buf.len();
// Set length to end-of-MI (excluding any later attributes like FINGERPRINT) // Set length to end-of-MI (excluding any later attributes like FINGERPRINT)
let len = (mi_end - 20) as u16; let len_to_mi_end = (mi_end - 20) as u16;
let len_bytes = len.to_be_bytes();
buf[2] = len_bytes[0]; let hmac = match mi_mode {
buf[3] = len_bytes[1]; MessageIntegrityMode::Rfc5389 => {
buf[2..4].copy_from_slice(&len_to_mi_end.to_be_bytes());
compute_message_integrity(key, &buf[..mi_end])
}
MessageIntegrityMode::BeforeMiLenToMiEnd => {
// HMAC input is only the prefix before the MI attribute, but the header length
// is forced to end-of-MI.
let mut signed = buf[..mi_attr_offset].to_vec();
signed[2..4].copy_from_slice(&len_to_mi_end.to_be_bytes());
compute_message_integrity(key, &signed)
}
};
let hmac = compute_message_integrity(key, &buf[..mi_end]);
buf[mi_val_pos..mi_val_pos + HMAC_SHA1_LEN].copy_from_slice(&hmac[..HMAC_SHA1_LEN]); buf[mi_val_pos..mi_val_pos + HMAC_SHA1_LEN].copy_from_slice(&hmac[..HMAC_SHA1_LEN]);
} }
@ -616,6 +678,15 @@ pub fn build_success_response(req: &StunHeader) -> Vec<u8> {
/// Build a simple success (200) response including MESSAGE-INTEGRITY and FINGERPRINT. /// Build a simple success (200) response including MESSAGE-INTEGRITY and FINGERPRINT.
pub fn build_success_response_with_integrity(req: &StunHeader, key: &[u8]) -> Vec<u8> { pub fn build_success_response_with_integrity(req: &StunHeader, key: &[u8]) -> Vec<u8> {
build_success_response_with_integrity_mode(req, key, MessageIntegrityMode::Rfc5389)
}
/// Build a simple success (200) response including MESSAGE-INTEGRITY and FINGERPRINT.
pub fn build_success_response_with_integrity_mode(
req: &StunHeader,
key: &[u8],
mi_mode: MessageIntegrityMode,
) -> Vec<u8> {
use bytes::BytesMut; use bytes::BytesMut;
let mut buf = BytesMut::new(); let mut buf = BytesMut::new();
let msg_type: u16 = req.msg_type | CLASS_SUCCESS; let msg_type: u16 = req.msg_type | CLASS_SUCCESS;
@ -623,7 +694,7 @@ pub fn build_success_response_with_integrity(req: &StunHeader, key: &[u8]) -> Ve
buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes());
buf.extend_from_slice(&MAGIC_COOKIE_BYTES); buf.extend_from_slice(&MAGIC_COOKIE_BYTES);
buf.extend_from_slice(&req.transaction_id); buf.extend_from_slice(&req.transaction_id);
append_message_integrity(&mut buf, key); append_message_integrity_with_mode(&mut buf, key, mi_mode);
append_fingerprint(&mut buf); append_fingerprint(&mut buf);
buf.to_vec() buf.to_vec()
} }

View File

@ -13,10 +13,11 @@ use crate::alloc::AllocationError;
use crate::auth::{AuthManager, AuthStatus, InMemoryStore}; use crate::auth::{AuthManager, AuthStatus, InMemoryStore};
use crate::constants::*; use crate::constants::*;
use crate::stun::{ use crate::stun::{
build_401_response, build_allocate_success_with_integrity, build_error_response, build_401_response, build_allocate_success_with_integrity_mode, build_error_response,
build_error_response_with_integrity, build_lifetime_success_with_integrity, build_error_response_with_integrity_mode, build_lifetime_success_with_integrity_mode,
build_success_response_with_integrity, decode_xor_peer_address, extract_lifetime_seconds, build_success_response_with_integrity_mode, decode_xor_peer_address,
parse_message, validate_fingerprint_if_present, extract_requested_transport_protocol, extract_lifetime_seconds, extract_requested_transport_protocol, parse_message,
validate_fingerprint_if_present, MessageIntegrityMode,
}; };
use crate::rate_limit::RateLimiters; use crate::rate_limit::RateLimiters;
@ -232,10 +233,16 @@ where
); );
let mut auth_key: Option<Vec<u8>> = None; let mut auth_key: Option<Vec<u8>> = None;
let mut auth_mi_mode: Option<MessageIntegrityMode> = None;
if requires_auth { if requires_auth {
match auth.authenticate(&msg, &peer).await { match auth.authenticate(&msg, &peer).await {
AuthStatus::Granted { username, key } => { AuthStatus::Granted {
username,
key,
mi_mode,
} => {
auth_key = Some(key); auth_key = Some(key);
auth_mi_mode = Some(mi_mode);
tracing::debug!( tracing::debug!(
"TURN stream auth ok for {} as {} (0x{:04x})", "TURN stream auth ok for {} as {} (0x{:04x})",
peer, peer,
@ -291,28 +298,32 @@ where
let key = auth_key let key = auth_key
.as_deref() .as_deref()
.expect("auth key must be set after AuthStatus::Granted"); .expect("auth key must be set after AuthStatus::Granted");
let mi_mode = auth_mi_mode
.expect("auth mi mode must be set after AuthStatus::Granted");
// TURN Allocate MUST include REQUESTED-TRANSPORT; WebRTC expects UDP (17). // TURN Allocate MUST include REQUESTED-TRANSPORT; WebRTC expects UDP (17).
match extract_requested_transport_protocol(&msg) { match extract_requested_transport_protocol(&msg) {
Some(IPPROTO_UDP) => {} Some(IPPROTO_UDP) => {}
Some(_) => { Some(_) => {
crate::metrics::inc_allocate_fail(); crate::metrics::inc_allocate_fail();
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
442, 442,
"Unsupported Transport", "Unsupported Transport",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
} }
None => { None => {
crate::metrics::inc_allocate_fail(); crate::metrics::inc_allocate_fail();
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Missing REQUESTED-TRANSPORT", "Missing REQUESTED-TRANSPORT",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -343,11 +354,12 @@ where
peer, peer,
e e
); );
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
500, 500,
"Allocate Failed", "Allocate Failed",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -358,11 +370,12 @@ where
applied.as_secs().min(u32::MAX as u64) as u32; applied.as_secs().min(u32::MAX as u64) as u32;
let advertised = let advertised =
allocs.relay_addr_for_response(relay_addr); allocs.relay_addr_for_response(relay_addr);
let resp = build_allocate_success_with_integrity( let resp = build_allocate_success_with_integrity_mode(
&msg.header, &msg.header,
&advertised, &advertised,
lifetime_secs, lifetime_secs,
key, key,
mi_mode,
); );
crate::metrics::inc_allocate_success(); crate::metrics::inc_allocate_success();
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
@ -376,11 +389,12 @@ where
_ => (500, "Allocate Failed"), _ => (500, "Allocate Failed"),
}; };
crate::metrics::inc_allocate_fail(); crate::metrics::inc_allocate_fail();
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
code, code,
reason, reason,
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
@ -390,12 +404,15 @@ where
let key = auth_key let key = auth_key
.as_deref() .as_deref()
.expect("auth key must be set after AuthStatus::Granted"); .expect("auth key must be set after AuthStatus::Granted");
let mi_mode = auth_mi_mode
.expect("auth mi mode must be set after AuthStatus::Granted");
if allocs.get_allocation(&peer).is_none() { if allocs.get_allocation(&peer).is_none() {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
437, 437,
"Allocation Mismatch", "Allocation Mismatch",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -418,11 +435,12 @@ where
e.downcast_ref::<AllocationError>(), e.downcast_ref::<AllocationError>(),
Some(AllocationError::PermissionQuotaExceeded) Some(AllocationError::PermissionQuotaExceeded)
) { ) {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
508, 508,
"Insufficient Capacity", "Insufficient Capacity",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -433,15 +451,17 @@ where
} }
if added == 0 { if added == 0 {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"No valid XOR-PEER-ADDRESS", "No valid XOR-PEER-ADDRESS",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} else { } else {
let resp = build_success_response_with_integrity(&msg.header, key); let resp =
build_success_response_with_integrity_mode(&msg.header, key, mi_mode);
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
} }
@ -449,14 +469,17 @@ where
let key = auth_key let key = auth_key
.as_deref() .as_deref()
.expect("auth key must be set after AuthStatus::Granted"); .expect("auth key must be set after AuthStatus::Granted");
let mi_mode = auth_mi_mode
.expect("auth mi mode must be set after AuthStatus::Granted");
let allocation = match allocs.get_allocation(&peer) { let allocation = match allocs.get_allocation(&peer) {
Some(a) => a, Some(a) => a,
None => { None => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
437, 437,
"Allocation Mismatch", "Allocation Mismatch",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -475,11 +498,12 @@ where
{ {
(Some(c), Some(p)) => (c, p), (Some(c), Some(p)) => (c, p),
_ => { _ => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Missing CHANNEL-NUMBER or XOR-PEER-ADDRESS", "Missing CHANNEL-NUMBER or XOR-PEER-ADDRESS",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -487,11 +511,12 @@ where
}; };
if channel_attr.value.len() < 2 { if channel_attr.value.len() < 2 {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Invalid CHANNEL-NUMBER", "Invalid CHANNEL-NUMBER",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -507,11 +532,12 @@ where
) { ) {
Some(addr) => addr, Some(addr) => addr,
None => { None => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Invalid XOR-PEER-ADDRESS", "Invalid XOR-PEER-ADDRESS",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -519,11 +545,12 @@ where
}; };
if !allocation.is_peer_allowed(&peer_addr) { if !allocation.is_peer_allowed(&peer_addr) {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
403, 403,
"Peer Not Permitted", "Peer Not Permitted",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -545,11 +572,12 @@ where
} }
_ => (500, "Channel Bind Failed"), _ => (500, "Channel Bind Failed"),
}; };
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
code, code,
reason, reason,
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -557,21 +585,25 @@ where
crate::metrics::inc_channel_binding_added(); crate::metrics::inc_channel_binding_added();
let resp = build_success_response_with_integrity(&msg.header, key); let resp =
build_success_response_with_integrity_mode(&msg.header, key, mi_mode);
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
METHOD_SEND => { METHOD_SEND => {
let key = auth_key let key = auth_key
.as_deref() .as_deref()
.expect("auth key must be set after AuthStatus::Granted"); .expect("auth key must be set after AuthStatus::Granted");
let mi_mode = auth_mi_mode
.expect("auth mi mode must be set after AuthStatus::Granted");
let allocation = match allocs.get_allocation(&peer) { let allocation = match allocs.get_allocation(&peer) {
Some(a) => a, Some(a) => a,
None => { None => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
437, 437,
"Allocation Mismatch", "Allocation Mismatch",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -586,11 +618,12 @@ where
let (peer_attr, data_attr) = match (peer_attr, data_attr) { let (peer_attr, data_attr) = match (peer_attr, data_attr) {
(Some(p), Some(d)) => (p, d), (Some(p), Some(d)) => (p, d),
_ => { _ => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Missing DATA or XOR-PEER-ADDRESS", "Missing DATA or XOR-PEER-ADDRESS",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -603,11 +636,12 @@ where
) { ) {
Some(addr) => addr, Some(addr) => addr,
None => { None => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
400, 400,
"Invalid XOR-PEER-ADDRESS", "Invalid XOR-PEER-ADDRESS",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -615,11 +649,12 @@ where
}; };
if !allocation.is_peer_allowed(&peer_addr) { if !allocation.is_peer_allowed(&peer_addr) {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
403, 403,
"Peer Not Permitted", "Peer Not Permitted",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
continue; continue;
@ -627,7 +662,11 @@ where
match allocation.send_to_peer(peer_addr, &data_attr.value).await { match allocation.send_to_peer(peer_addr, &data_attr.value).await {
Ok(_) => { Ok(_) => {
let resp = build_success_response_with_integrity(&msg.header, key); let resp = build_success_response_with_integrity_mode(
&msg.header,
key,
mi_mode,
);
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
Err(e) => { Err(e) => {
@ -637,11 +676,12 @@ where
peer_addr, peer_addr,
e e
); );
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
500, 500,
"Peer Send Failed", "Peer Send Failed",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
@ -651,24 +691,28 @@ where
let key = auth_key let key = auth_key
.as_deref() .as_deref()
.expect("auth key must be set after AuthStatus::Granted"); .expect("auth key must be set after AuthStatus::Granted");
let mi_mode = auth_mi_mode
.expect("auth mi mode must be set after AuthStatus::Granted");
let requested = extract_lifetime_seconds(&msg) let requested = extract_lifetime_seconds(&msg)
.map(|secs| Duration::from_secs(secs as u64)); .map(|secs| Duration::from_secs(secs as u64));
match allocs.refresh_allocation(peer, requested) { match allocs.refresh_allocation(peer, requested) {
Ok(applied) => { Ok(applied) => {
let resp = build_lifetime_success_with_integrity( let resp = build_lifetime_success_with_integrity_mode(
&msg.header, &msg.header,
applied.as_secs().min(u32::MAX as u64) as u32, applied.as_secs().min(u32::MAX as u64) as u32,
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
Err(_) => { Err(_) => {
let resp = build_error_response_with_integrity( let resp = build_error_response_with_integrity_mode(
&msg.header, &msg.header,
437, 437,
"Allocation Mismatch", "Allocation Mismatch",
key, key,
mi_mode,
); );
let _ = tx.send(resp).await; let _ = tx.send(resp).await;
} }
@ -683,14 +727,17 @@ where
} }
} }
_ => { _ => {
let resp = match auth_key.as_deref() { let resp = match (auth_key.as_deref(), auth_mi_mode) {
Some(key) => build_error_response_with_integrity( (Some(key), Some(mi_mode)) => {
build_error_response_with_integrity_mode(
&msg.header, &msg.header,
420, 420,
"Unknown TURN Method", "Unknown TURN Method",
key, key,
), mi_mode,
None => build_error_response( )
}
_ => build_error_response(
&msg.header, &msg.header,
420, 420,
"Unknown TURN Method", "Unknown TURN Method",