Changed response message signing to match accepted message integrity of request.
This commit is contained in:
parent
a79d0f2a95
commit
a434a6ad8a
46
src/auth.rs
46
src/auth.rs
@ -14,6 +14,7 @@ use crate::stun::{
|
||||
compute_message_integrity_len_preserved_nozero,
|
||||
compute_message_integrity_through_mi_header,
|
||||
find_message_integrity,
|
||||
MessageIntegrityMode,
|
||||
validate_message_integrity,
|
||||
validate_message_integrity_len_preserved_nozero,
|
||||
validate_message_integrity_nozero,
|
||||
@ -89,7 +90,11 @@ impl AuthSettings {
|
||||
/// Result of validating authentication attributes on an incoming STUN/TURN request.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AuthStatus {
|
||||
Granted { username: String, key: Vec<u8> },
|
||||
Granted {
|
||||
username: String,
|
||||
key: Vec<u8>,
|
||||
mi_mode: MessageIntegrityMode,
|
||||
},
|
||||
Challenge { nonce: String },
|
||||
StaleNonce { nonce: String },
|
||||
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);
|
||||
// Primary: long-term (MD5(username:realm:password))
|
||||
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.
|
||||
@ -249,7 +258,11 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
"auth accept via MI nozero username={} realm={} peer={} (interop)",
|
||||
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.
|
||||
@ -261,6 +274,7 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
return AuthStatus::Granted {
|
||||
username,
|
||||
key: short_key.to_vec(),
|
||||
mi_mode: MessageIntegrityMode::Rfc5389,
|
||||
};
|
||||
}
|
||||
|
||||
@ -274,6 +288,7 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
return AuthStatus::Granted {
|
||||
username,
|
||||
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.
|
||||
if validate_message_integrity_len_preserved(msg, &key) {
|
||||
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.
|
||||
@ -411,8 +430,25 @@ impl<S: CredentialStore + Clone> AuthManager<S> {
|
||||
for (label, cand) in variants.iter() {
|
||||
if let Some(c) = cand {
|
||||
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);
|
||||
return AuthStatus::Granted { username, key };
|
||||
return AuthStatus::Granted {
|
||||
username,
|
||||
key: chosen_key,
|
||||
mi_mode,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
124
src/server.rs
124
src/server.rs
@ -9,11 +9,11 @@ use crate::auth::{AuthManager, AuthStatus, InMemoryStore};
|
||||
use crate::constants::*;
|
||||
use crate::rate_limit::RateLimiters;
|
||||
use crate::stun::{
|
||||
build_401_response, build_allocate_success_with_integrity, build_error_response,
|
||||
build_error_response_with_integrity, build_lifetime_success_with_integrity,
|
||||
build_success_response_with_integrity, decode_xor_peer_address, extract_lifetime_seconds,
|
||||
parse_channel_data, extract_requested_transport_protocol, parse_message,
|
||||
validate_fingerprint_if_present,
|
||||
build_401_response, build_allocate_success_with_integrity_mode, build_error_response,
|
||||
build_error_response_with_integrity_mode, build_lifetime_success_with_integrity_mode,
|
||||
build_success_response_with_integrity_mode, decode_xor_peer_address,
|
||||
extract_lifetime_seconds, extract_requested_transport_protocol, parse_channel_data,
|
||||
parse_message, validate_fingerprint_if_present,
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
@ -105,15 +105,19 @@ pub async fn udp_reader_loop_with_limits(
|
||||
);
|
||||
|
||||
if requires_auth {
|
||||
let key = match auth.authenticate(&msg, &peer).await {
|
||||
AuthStatus::Granted { username, key } => {
|
||||
let (key, mi_mode) = match auth.authenticate(&msg, &peer).await {
|
||||
AuthStatus::Granted {
|
||||
username,
|
||||
key,
|
||||
mi_mode,
|
||||
} => {
|
||||
tracing::debug!(
|
||||
"TURN auth ok for {} as {} (0x{:04x})",
|
||||
peer,
|
||||
username,
|
||||
msg.header.msg_type
|
||||
);
|
||||
key
|
||||
(key, mi_mode)
|
||||
}
|
||||
AuthStatus::Challenge { nonce } => {
|
||||
crate::metrics::inc_auth_challenge();
|
||||
@ -164,22 +168,24 @@ pub async fn udp_reader_loop_with_limits(
|
||||
Some(IPPROTO_UDP) => {}
|
||||
Some(_) => {
|
||||
crate::metrics::inc_allocate_fail();
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
442,
|
||||
"Unsupported Transport",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
crate::metrics::inc_allocate_fail();
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Missing REQUESTED-TRANSPORT",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -207,11 +213,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
peer,
|
||||
e
|
||||
);
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
500,
|
||||
"Allocate Failed",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
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 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,
|
||||
&advertised,
|
||||
lifetime_secs,
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
tracing::info!(
|
||||
"allocated relay {} for {} lifetime={}s",
|
||||
@ -244,7 +252,13 @@ pub async fn udp_reader_loop_with_limits(
|
||||
_ => (500, "Allocate Failed"),
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -254,7 +268,13 @@ pub async fn udp_reader_loop_with_limits(
|
||||
if allocs.get_allocation(&peer).is_none() {
|
||||
warn!("create-permission without allocation from {}", peer);
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
@ -289,11 +309,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
e.downcast_ref::<AllocationError>(),
|
||||
Some(AllocationError::PermissionQuotaExceeded)
|
||||
) {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
508,
|
||||
"Insufficient Capacity",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -306,11 +327,17 @@ pub async fn udp_reader_loop_with_limits(
|
||||
}
|
||||
|
||||
if added == 0 {
|
||||
let resp =
|
||||
build_error_response_with_integrity(&msg.header, 400, "No valid XOR-PEER-ADDRESS", &key);
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"No valid XOR-PEER-ADDRESS",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
} 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;
|
||||
}
|
||||
continue;
|
||||
@ -320,8 +347,13 @@ pub async fn udp_reader_loop_with_limits(
|
||||
Some(a) => a,
|
||||
None => {
|
||||
warn!("channel-bind without allocation from {}", peer);
|
||||
let resp =
|
||||
build_error_response_with_integrity(&msg.header, 437, "Allocation Mismatch", &key);
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
}
|
||||
@ -336,11 +368,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
let (channel_attr, peer_attr) = match (channel_attr, peer_attr) {
|
||||
(Some(c), Some(p)) => (c, p),
|
||||
_ => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Missing CHANNEL-NUMBER or XOR-PEER-ADDRESS",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -355,11 +388,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
) {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Invalid XOR-PEER-ADDRESS",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -367,11 +401,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
};
|
||||
|
||||
if !allocation.is_peer_allowed(&peer_addr) {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
403,
|
||||
"Peer Not Permitted",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -391,13 +426,19 @@ pub async fn udp_reader_loop_with_limits(
|
||||
}
|
||||
_ => (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;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
@ -406,8 +447,13 @@ pub async fn udp_reader_loop_with_limits(
|
||||
Some(a) => a,
|
||||
None => {
|
||||
warn!("send indication without allocation from {}", peer);
|
||||
let resp =
|
||||
build_error_response_with_integrity(&msg.header, 437, "Allocation Mismatch", &key);
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
}
|
||||
@ -421,11 +467,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
let (peer_attr, data_attr) = match (peer_attr, data_attr) {
|
||||
(Some(p), Some(d)) => (p, d),
|
||||
_ => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Missing DATA or XOR-PEER-ADDRESS",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -438,11 +485,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
) {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Invalid XOR-PEER-ADDRESS",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -450,11 +498,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
};
|
||||
|
||||
if !allocation.is_peer_allowed(&peer_addr) {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
403,
|
||||
"Peer Not Permitted",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
@ -468,7 +517,8 @@ pub async fn udp_reader_loop_with_limits(
|
||||
peer,
|
||||
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;
|
||||
}
|
||||
Err(e) => {
|
||||
@ -478,11 +528,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
peer_addr,
|
||||
e
|
||||
);
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
500,
|
||||
"Peer Send Failed",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
}
|
||||
@ -504,19 +555,21 @@ pub async fn udp_reader_loop_with_limits(
|
||||
applied.as_secs()
|
||||
);
|
||||
}
|
||||
let resp = build_lifetime_success_with_integrity(
|
||||
let resp = build_lifetime_success_with_integrity_mode(
|
||||
&msg.header,
|
||||
applied.as_secs().min(u32::MAX as u64) as u32,
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
}
|
||||
Err(_) => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
}
|
||||
@ -524,11 +577,12 @@ pub async fn udp_reader_loop_with_limits(
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
420,
|
||||
"Unknown TURN Method",
|
||||
&key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = udp.send_to(&resp, &peer).await;
|
||||
continue;
|
||||
|
||||
91
src/stun.rs
91
src/stun.rs
@ -5,6 +5,15 @@ use crate::models::stun::{StunAttribute, StunHeader, StunMessage};
|
||||
use std::convert::TryInto;
|
||||
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)]
|
||||
pub enum ParseError {
|
||||
#[error("too short")]
|
||||
@ -158,6 +167,17 @@ pub fn build_error_response_with_integrity(
|
||||
code: u16,
|
||||
reason: &str,
|
||||
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> {
|
||||
use bytes::BytesMut;
|
||||
let mut buf = BytesMut::new();
|
||||
@ -182,7 +202,7 @@ pub fn build_error_response_with_integrity(
|
||||
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);
|
||||
buf.to_vec()
|
||||
}
|
||||
@ -227,6 +247,23 @@ pub fn build_allocate_success_with_integrity(
|
||||
relay: &std::net::SocketAddr,
|
||||
lifetime_secs: u32,
|
||||
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> {
|
||||
use bytes::BytesMut;
|
||||
let mut buf = BytesMut::new();
|
||||
@ -252,7 +289,7 @@ pub fn build_allocate_success_with_integrity(
|
||||
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);
|
||||
buf.to_vec()
|
||||
}
|
||||
@ -284,6 +321,16 @@ pub fn build_lifetime_success_with_integrity(
|
||||
req: &StunHeader,
|
||||
lifetime_secs: u32,
|
||||
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> {
|
||||
use bytes::BytesMut;
|
||||
let mut buf = BytesMut::new();
|
||||
@ -301,7 +348,7 @@ pub fn build_lifetime_success_with_integrity(
|
||||
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);
|
||||
buf.to_vec()
|
||||
}
|
||||
@ -578,9 +625,14 @@ pub fn validate_message_integrity_len_preserved(msg: &StunMessage, key: &[u8]) -
|
||||
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
|
||||
// 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(&((HMAC_SHA1_LEN as u16).to_be_bytes()));
|
||||
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();
|
||||
|
||||
// Set length to end-of-MI (excluding any later attributes like FINGERPRINT)
|
||||
let len = (mi_end - 20) as u16;
|
||||
let len_bytes = len.to_be_bytes();
|
||||
buf[2] = len_bytes[0];
|
||||
buf[3] = len_bytes[1];
|
||||
let len_to_mi_end = (mi_end - 20) as u16;
|
||||
|
||||
let hmac = match mi_mode {
|
||||
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]);
|
||||
}
|
||||
|
||||
@ -616,6 +678,15 @@ pub fn build_success_response(req: &StunHeader) -> Vec<u8> {
|
||||
|
||||
/// Build a simple success (200) response including MESSAGE-INTEGRITY and FINGERPRINT.
|
||||
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;
|
||||
let mut buf = BytesMut::new();
|
||||
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(&MAGIC_COOKIE_BYTES);
|
||||
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);
|
||||
buf.to_vec()
|
||||
}
|
||||
|
||||
@ -13,10 +13,11 @@ use crate::alloc::AllocationError;
|
||||
use crate::auth::{AuthManager, AuthStatus, InMemoryStore};
|
||||
use crate::constants::*;
|
||||
use crate::stun::{
|
||||
build_401_response, build_allocate_success_with_integrity, build_error_response,
|
||||
build_error_response_with_integrity, build_lifetime_success_with_integrity,
|
||||
build_success_response_with_integrity, decode_xor_peer_address, extract_lifetime_seconds,
|
||||
parse_message, validate_fingerprint_if_present, extract_requested_transport_protocol,
|
||||
build_401_response, build_allocate_success_with_integrity_mode, build_error_response,
|
||||
build_error_response_with_integrity_mode, build_lifetime_success_with_integrity_mode,
|
||||
build_success_response_with_integrity_mode, decode_xor_peer_address,
|
||||
extract_lifetime_seconds, extract_requested_transport_protocol, parse_message,
|
||||
validate_fingerprint_if_present, MessageIntegrityMode,
|
||||
};
|
||||
|
||||
use crate::rate_limit::RateLimiters;
|
||||
@ -232,10 +233,16 @@ where
|
||||
);
|
||||
|
||||
let mut auth_key: Option<Vec<u8>> = None;
|
||||
let mut auth_mi_mode: Option<MessageIntegrityMode> = None;
|
||||
if requires_auth {
|
||||
match auth.authenticate(&msg, &peer).await {
|
||||
AuthStatus::Granted { username, key } => {
|
||||
AuthStatus::Granted {
|
||||
username,
|
||||
key,
|
||||
mi_mode,
|
||||
} => {
|
||||
auth_key = Some(key);
|
||||
auth_mi_mode = Some(mi_mode);
|
||||
tracing::debug!(
|
||||
"TURN stream auth ok for {} as {} (0x{:04x})",
|
||||
peer,
|
||||
@ -291,28 +298,32 @@ where
|
||||
let key = auth_key
|
||||
.as_deref()
|
||||
.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).
|
||||
match extract_requested_transport_protocol(&msg) {
|
||||
Some(IPPROTO_UDP) => {}
|
||||
Some(_) => {
|
||||
crate::metrics::inc_allocate_fail();
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
442,
|
||||
"Unsupported Transport",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
crate::metrics::inc_allocate_fail();
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Missing REQUESTED-TRANSPORT",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -343,11 +354,12 @@ where
|
||||
peer,
|
||||
e
|
||||
);
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
500,
|
||||
"Allocate Failed",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -358,11 +370,12 @@ where
|
||||
applied.as_secs().min(u32::MAX as u64) as u32;
|
||||
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,
|
||||
&advertised,
|
||||
lifetime_secs,
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
crate::metrics::inc_allocate_success();
|
||||
let _ = tx.send(resp).await;
|
||||
@ -376,11 +389,12 @@ where
|
||||
_ => (500, "Allocate Failed"),
|
||||
};
|
||||
crate::metrics::inc_allocate_fail();
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
code,
|
||||
reason,
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
}
|
||||
@ -390,12 +404,15 @@ where
|
||||
let key = auth_key
|
||||
.as_deref()
|
||||
.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() {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -418,11 +435,12 @@ where
|
||||
e.downcast_ref::<AllocationError>(),
|
||||
Some(AllocationError::PermissionQuotaExceeded)
|
||||
) {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
508,
|
||||
"Insufficient Capacity",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -433,15 +451,17 @@ where
|
||||
}
|
||||
|
||||
if added == 0 {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"No valid XOR-PEER-ADDRESS",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
@ -449,14 +469,17 @@ where
|
||||
let key = auth_key
|
||||
.as_deref()
|
||||
.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) {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -475,11 +498,12 @@ where
|
||||
{
|
||||
(Some(c), Some(p)) => (c, p),
|
||||
_ => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Missing CHANNEL-NUMBER or XOR-PEER-ADDRESS",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -487,11 +511,12 @@ where
|
||||
};
|
||||
|
||||
if channel_attr.value.len() < 2 {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Invalid CHANNEL-NUMBER",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -507,11 +532,12 @@ where
|
||||
) {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Invalid XOR-PEER-ADDRESS",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -519,11 +545,12 @@ where
|
||||
};
|
||||
|
||||
if !allocation.is_peer_allowed(&peer_addr) {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
403,
|
||||
"Peer Not Permitted",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -545,11 +572,12 @@ where
|
||||
}
|
||||
_ => (500, "Channel Bind Failed"),
|
||||
};
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
code,
|
||||
reason,
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -557,21 +585,25 @@ where
|
||||
|
||||
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;
|
||||
}
|
||||
METHOD_SEND => {
|
||||
let key = auth_key
|
||||
.as_deref()
|
||||
.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) {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -586,11 +618,12 @@ where
|
||||
let (peer_attr, data_attr) = match (peer_attr, data_attr) {
|
||||
(Some(p), Some(d)) => (p, d),
|
||||
_ => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Missing DATA or XOR-PEER-ADDRESS",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -603,11 +636,12 @@ where
|
||||
) {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
400,
|
||||
"Invalid XOR-PEER-ADDRESS",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -615,11 +649,12 @@ where
|
||||
};
|
||||
|
||||
if !allocation.is_peer_allowed(&peer_addr) {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
403,
|
||||
"Peer Not Permitted",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
continue;
|
||||
@ -627,7 +662,11 @@ where
|
||||
|
||||
match allocation.send_to_peer(peer_addr, &data_attr.value).await {
|
||||
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;
|
||||
}
|
||||
Err(e) => {
|
||||
@ -637,11 +676,12 @@ where
|
||||
peer_addr,
|
||||
e
|
||||
);
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
500,
|
||||
"Peer Send Failed",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
}
|
||||
@ -651,24 +691,28 @@ where
|
||||
let key = auth_key
|
||||
.as_deref()
|
||||
.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)
|
||||
.map(|secs| Duration::from_secs(secs as u64));
|
||||
|
||||
match allocs.refresh_allocation(peer, requested) {
|
||||
Ok(applied) => {
|
||||
let resp = build_lifetime_success_with_integrity(
|
||||
let resp = build_lifetime_success_with_integrity_mode(
|
||||
&msg.header,
|
||||
applied.as_secs().min(u32::MAX as u64) as u32,
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
}
|
||||
Err(_) => {
|
||||
let resp = build_error_response_with_integrity(
|
||||
let resp = build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
437,
|
||||
"Allocation Mismatch",
|
||||
key,
|
||||
mi_mode,
|
||||
);
|
||||
let _ = tx.send(resp).await;
|
||||
}
|
||||
@ -683,14 +727,17 @@ where
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let resp = match auth_key.as_deref() {
|
||||
Some(key) => build_error_response_with_integrity(
|
||||
&msg.header,
|
||||
420,
|
||||
"Unknown TURN Method",
|
||||
key,
|
||||
),
|
||||
None => build_error_response(
|
||||
let resp = match (auth_key.as_deref(), auth_mi_mode) {
|
||||
(Some(key), Some(mi_mode)) => {
|
||||
build_error_response_with_integrity_mode(
|
||||
&msg.header,
|
||||
420,
|
||||
"Unknown TURN Method",
|
||||
key,
|
||||
mi_mode,
|
||||
)
|
||||
}
|
||||
_ => build_error_response(
|
||||
&msg.header,
|
||||
420,
|
||||
"Unknown TURN Method",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user