mirror of
https://github.com/telemt/telemt.git
synced 2026-10-07 18:05:57 +03:00
WEB
Co-Authored-By: brekotis <93345790+brekotis@users.noreply.github.com> Co-Authored-By: John Preston <17900494+john-preston@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,12 @@ pub(super) struct MtprotoCandidateValidation {
|
||||
pub(super) encryptor: AesCtr,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) enum MtprotoModePolicy {
|
||||
Configured,
|
||||
Web(WebSecretMode),
|
||||
}
|
||||
|
||||
pub(super) fn sni_hint_hash(sni: &str) -> u64 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
for byte in sni.bytes() {
|
||||
@@ -146,6 +152,7 @@ pub(super) fn validate_mtproto_secret_candidate(
|
||||
secret: &[u8; ACCESS_SECRET_BYTES],
|
||||
config: &ProxyConfig,
|
||||
is_tls: bool,
|
||||
mode_policy: MtprotoModePolicy,
|
||||
) -> Option<MtprotoCandidateValidation> {
|
||||
let mut dec_key_input = Zeroizing::new(Vec::with_capacity(PREKEY_LEN + secret.len()));
|
||||
dec_key_input.extend_from_slice(dec_prekey);
|
||||
@@ -163,7 +170,7 @@ pub(super) fn validate_mtproto_secret_candidate(
|
||||
decrypted[PROTO_TAG_POS + 3],
|
||||
];
|
||||
let proto_tag = ProtoTag::from_bytes(tag_bytes)?;
|
||||
if !mode_enabled_for_proto(config, proto_tag, is_tls) {
|
||||
if !mode_enabled_for_proto_with_policy(config, proto_tag, is_tls, mode_policy) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -267,6 +274,28 @@ pub(super) fn mode_enabled_for_proto(
|
||||
proto_tag: ProtoTag,
|
||||
is_tls: bool,
|
||||
) -> bool {
|
||||
mode_enabled_for_proto_with_policy(
|
||||
config,
|
||||
proto_tag,
|
||||
is_tls,
|
||||
MtprotoModePolicy::Configured,
|
||||
)
|
||||
}
|
||||
|
||||
fn mode_enabled_for_proto_with_policy(
|
||||
config: &ProxyConfig,
|
||||
proto_tag: ProtoTag,
|
||||
is_tls: bool,
|
||||
policy: MtprotoModePolicy,
|
||||
) -> bool {
|
||||
if let MtprotoModePolicy::Web(secret_mode) = policy {
|
||||
return match secret_mode {
|
||||
WebSecretMode::Plain => {
|
||||
matches!(proto_tag, ProtoTag::Intermediate | ProtoTag::Abridged)
|
||||
}
|
||||
WebSecretMode::Dd => matches!(proto_tag, ProtoTag::Secure),
|
||||
};
|
||||
}
|
||||
match proto_tag {
|
||||
ProtoTag::Secure => {
|
||||
if is_tls {
|
||||
@@ -279,6 +308,46 @@ pub(super) fn mode_enabled_for_proto(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod web_mode_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn web_secret_mode_isolates_inner_protocol_tags() {
|
||||
let config = ProxyConfig::default();
|
||||
assert!(mode_enabled_for_proto_with_policy(
|
||||
&config,
|
||||
ProtoTag::Abridged,
|
||||
false,
|
||||
MtprotoModePolicy::Web(WebSecretMode::Plain),
|
||||
));
|
||||
assert!(mode_enabled_for_proto_with_policy(
|
||||
&config,
|
||||
ProtoTag::Intermediate,
|
||||
false,
|
||||
MtprotoModePolicy::Web(WebSecretMode::Plain),
|
||||
));
|
||||
assert!(!mode_enabled_for_proto_with_policy(
|
||||
&config,
|
||||
ProtoTag::Secure,
|
||||
false,
|
||||
MtprotoModePolicy::Web(WebSecretMode::Plain),
|
||||
));
|
||||
assert!(mode_enabled_for_proto_with_policy(
|
||||
&config,
|
||||
ProtoTag::Secure,
|
||||
false,
|
||||
MtprotoModePolicy::Web(WebSecretMode::Dd),
|
||||
));
|
||||
assert!(!mode_enabled_for_proto_with_policy(
|
||||
&config,
|
||||
ProtoTag::Intermediate,
|
||||
false,
|
||||
MtprotoModePolicy::Web(WebSecretMode::Dd),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn decode_user_secrets_in(
|
||||
shared: &ProxySharedState,
|
||||
config: &ProxyConfig,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
|
||||
/// Handle MTProto obfuscation handshake
|
||||
/// Handles an MTProto obfuscation handshake with isolated test state.
|
||||
#[cfg(test)]
|
||||
pub async fn handle_mtproto_handshake<R, W>(
|
||||
handshake: &[u8; HANDSHAKE_LEN],
|
||||
@@ -26,11 +26,14 @@ where
|
||||
replay_checker,
|
||||
is_tls,
|
||||
preferred_user,
|
||||
None,
|
||||
MtprotoModePolicy::Configured,
|
||||
shared.as_ref(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Handles an MTProto obfuscation handshake with process-shared defenses.
|
||||
pub async fn handle_mtproto_handshake_with_shared<R, W>(
|
||||
handshake: &[u8; HANDSHAKE_LEN],
|
||||
reader: R,
|
||||
@@ -55,6 +58,40 @@ where
|
||||
replay_checker,
|
||||
is_tls,
|
||||
preferred_user,
|
||||
None,
|
||||
MtprotoModePolicy::Configured,
|
||||
shared,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Authenticates one WEB logical stream against exactly one user and secret mode.
|
||||
pub(crate) async fn handle_mtproto_handshake_for_web_user<R, W>(
|
||||
handshake: &[u8; HANDSHAKE_LEN],
|
||||
reader: R,
|
||||
writer: W,
|
||||
peer: SocketAddr,
|
||||
config: &ProxyConfig,
|
||||
replay_checker: &ReplayChecker,
|
||||
exact_user: &str,
|
||||
secret_mode: WebSecretMode,
|
||||
shared: &ProxySharedState,
|
||||
) -> HandshakeResult<(CryptoReader<R>, CryptoWriter<W>, HandshakeSuccess), R, W>
|
||||
where
|
||||
R: AsyncRead + Unpin + Send,
|
||||
W: AsyncWrite + Unpin + Send,
|
||||
{
|
||||
handle_mtproto_handshake_impl(
|
||||
handshake,
|
||||
reader,
|
||||
writer,
|
||||
peer,
|
||||
config,
|
||||
replay_checker,
|
||||
false,
|
||||
None,
|
||||
Some(exact_user),
|
||||
MtprotoModePolicy::Web(secret_mode),
|
||||
shared,
|
||||
)
|
||||
.await
|
||||
@@ -69,6 +106,8 @@ async fn handle_mtproto_handshake_impl<R, W>(
|
||||
replay_checker: &ReplayChecker,
|
||||
is_tls: bool,
|
||||
preferred_user: Option<&str>,
|
||||
exact_user: Option<&str>,
|
||||
mode_policy: MtprotoModePolicy,
|
||||
shared: &ProxySharedState,
|
||||
) -> HandshakeResult<(CryptoReader<R>, CryptoWriter<W>, HandshakeSuccess), R, W>
|
||||
where
|
||||
@@ -113,8 +152,11 @@ where
|
||||
let sticky_ip_hint = sticky_hint_get_by_ip(shared, peer.ip());
|
||||
let sticky_prefix_hint = sticky_hint_get_by_ip_prefix(shared, peer.ip());
|
||||
let preferred_user_id = preferred_user.and_then(|user| snapshot.user_id_by_name(user));
|
||||
let has_hint =
|
||||
sticky_ip_hint.is_some() || sticky_prefix_hint.is_some() || preferred_user_id.is_some();
|
||||
let exact_user_id = exact_user.and_then(|user| snapshot.user_id_by_name(user));
|
||||
let has_hint = sticky_ip_hint.is_some()
|
||||
|| sticky_prefix_hint.is_some()
|
||||
|| preferred_user_id.is_some()
|
||||
|| exact_user_id.is_some();
|
||||
let overload = auth_probe_saturation_is_throttled_in(shared, Instant::now());
|
||||
let candidate_budget = budget_for_validation(snapshot.entries().len(), overload, has_hint);
|
||||
|
||||
@@ -145,6 +187,7 @@ where
|
||||
&entry.secret,
|
||||
config,
|
||||
is_tls,
|
||||
mode_policy,
|
||||
) {
|
||||
matched_user = entry.user.clone();
|
||||
matched_user_id = Some($user_id);
|
||||
@@ -159,20 +202,20 @@ where
|
||||
}};
|
||||
}
|
||||
|
||||
let mut matched = false;
|
||||
if let Some(user_id) = sticky_ip_hint {
|
||||
let mut matched = exact_user_id.is_some_and(|user_id| try_user_id!(user_id));
|
||||
if exact_user.is_none() && let Some(user_id) = sticky_ip_hint {
|
||||
matched = try_user_id!(user_id);
|
||||
}
|
||||
|
||||
if !matched && let Some(user_id) = preferred_user_id {
|
||||
if exact_user.is_none() && !matched && let Some(user_id) = preferred_user_id {
|
||||
matched = try_user_id!(user_id);
|
||||
}
|
||||
|
||||
if !matched && let Some(user_id) = sticky_prefix_hint {
|
||||
if exact_user.is_none() && !matched && let Some(user_id) = sticky_prefix_hint {
|
||||
matched = try_user_id!(user_id);
|
||||
}
|
||||
|
||||
if !matched && !budget_exhausted {
|
||||
if exact_user.is_none() && !matched && !budget_exhausted {
|
||||
let ring = &shared.handshake.recent_user_ring;
|
||||
if !ring.is_empty() {
|
||||
let next_seq = shared
|
||||
@@ -197,7 +240,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
if !matched && !budget_exhausted {
|
||||
if exact_user.is_none() && !matched && !budget_exhausted {
|
||||
for idx in 0..snapshot.entries().len() {
|
||||
let Some(user_id) = u32::try_from(idx).ok() else {
|
||||
break;
|
||||
@@ -317,7 +360,16 @@ where
|
||||
success,
|
||||
));
|
||||
} else {
|
||||
let decoded_users = decode_user_secrets_in(shared, config, preferred_user);
|
||||
let decoded_users = match exact_user {
|
||||
Some(user) => config
|
||||
.access
|
||||
.users
|
||||
.get(user)
|
||||
.and_then(|secret| decode_user_secret(shared, user, secret))
|
||||
.map(|secret| vec![(user.to_string(), secret)])
|
||||
.unwrap_or_default(),
|
||||
None => decode_user_secrets_in(shared, config, preferred_user),
|
||||
};
|
||||
let mut validation_checks = 0usize;
|
||||
|
||||
for (user, secret) in decoded_users {
|
||||
@@ -337,6 +389,7 @@ where
|
||||
&secret_arr,
|
||||
config,
|
||||
is_tls,
|
||||
mode_policy,
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user