mirror of
https://github.com/telemt/telemt.git
synced 2026-04-18 11:04:09 +03:00
feat(proxy): enhance auth probe handling with IPv6 normalization and eviction logic
This commit is contained in:
@@ -381,7 +381,7 @@ fn validate_tls_handshake_at_time_with_boot_cap(
|
||||
let mut msg = handshake.to_vec();
|
||||
msg[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);
|
||||
|
||||
let mut first_match: Option<TlsValidation> = None;
|
||||
let mut first_match: Option<(&String, u32)> = None;
|
||||
|
||||
for (user, secret) in secrets {
|
||||
let computed = sha256_hmac(secret, &msg);
|
||||
@@ -421,16 +421,16 @@ fn validate_tls_handshake_at_time_with_boot_cap(
|
||||
}
|
||||
|
||||
if first_match.is_none() {
|
||||
first_match = Some(TlsValidation {
|
||||
user: user.clone(),
|
||||
session_id: session_id.clone(),
|
||||
digest,
|
||||
timestamp,
|
||||
});
|
||||
first_match = Some((user, timestamp));
|
||||
}
|
||||
}
|
||||
|
||||
first_match
|
||||
first_match.map(|(user, timestamp)| TlsValidation {
|
||||
user: user.clone(),
|
||||
session_id,
|
||||
digest,
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
|
||||
fn curve25519_prime() -> BigUint {
|
||||
|
||||
@@ -9,12 +9,19 @@ use crate::crypto::sha256_hmac;
|
||||
/// [TLS_DIGEST_POS..+32] : digest = HMAC XOR [0..0 || timestamp_le]
|
||||
/// [TLS_DIGEST_POS+32] : session_id_len = 32
|
||||
/// [TLS_DIGEST_POS+33..+65] : session_id filler (0x42)
|
||||
fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec<u8> {
|
||||
let session_id_len: usize = 32;
|
||||
fn make_valid_tls_handshake_with_session_id(
|
||||
secret: &[u8],
|
||||
timestamp: u32,
|
||||
session_id: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let session_id_len = session_id.len();
|
||||
assert!(session_id_len <= u8::MAX as usize);
|
||||
let len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1 + session_id_len;
|
||||
let mut handshake = vec![0x42u8; len];
|
||||
|
||||
handshake[TLS_DIGEST_POS + TLS_DIGEST_LEN] = session_id_len as u8;
|
||||
let sid_start = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1;
|
||||
handshake[sid_start..sid_start + session_id_len].copy_from_slice(session_id);
|
||||
// Zero the digest slot before computing HMAC (mirrors what validate does).
|
||||
handshake[TLS_DIGEST_POS..TLS_DIGEST_POS + TLS_DIGEST_LEN].fill(0);
|
||||
|
||||
@@ -34,6 +41,10 @@ fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec<u8> {
|
||||
handshake
|
||||
}
|
||||
|
||||
fn make_valid_tls_handshake(secret: &[u8], timestamp: u32) -> Vec<u8> {
|
||||
make_valid_tls_handshake_with_session_id(secret, timestamp, &[0x42; 32])
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Happy-path sanity
|
||||
// ------------------------------------------------------------------
|
||||
@@ -311,6 +322,20 @@ fn too_short_handshake_rejected_without_panic() {
|
||||
assert!(validate_tls_handshake(&[], &secrets, true).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_prefix_lengths_below_minimum_rejected_without_panic() {
|
||||
let min_len = TLS_DIGEST_POS + TLS_DIGEST_LEN + 1;
|
||||
let secrets = vec![("u".to_string(), b"s".to_vec())];
|
||||
|
||||
for len in 0..min_len {
|
||||
let h = vec![0u8; len];
|
||||
assert!(
|
||||
validate_tls_handshake(&h, &secrets, true).is_none(),
|
||||
"prefix length {len} below minimum must be rejected"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claimed_session_id_overflows_buffer_rejected() {
|
||||
let session_id_len: usize = 32;
|
||||
@@ -332,6 +357,30 @@ fn max_session_id_len_255_does_not_panic() {
|
||||
assert!(validate_tls_handshake(&h, &secrets, true).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_byte_session_id_validates_and_is_preserved() {
|
||||
let secret = b"sid_len_1_test";
|
||||
let handshake = make_valid_tls_handshake_with_session_id(secret, 0, &[0xAB]);
|
||||
let secrets = vec![("u".to_string(), secret.to_vec())];
|
||||
|
||||
let result = validate_tls_handshake(&handshake, &secrets, true)
|
||||
.expect("one-byte session_id handshake must validate");
|
||||
assert_eq!(result.session_id, vec![0xAB]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_session_id_len_255_with_valid_digest_is_accepted() {
|
||||
let secret = b"sid_len_255_test";
|
||||
let session_id = vec![0xCCu8; 255];
|
||||
let handshake = make_valid_tls_handshake_with_session_id(secret, 0, &session_id);
|
||||
let secrets = vec![("u".to_string(), secret.to_vec())];
|
||||
|
||||
let result = validate_tls_handshake(&handshake, &secrets, true)
|
||||
.expect("session_id_len=255 with valid digest must validate");
|
||||
assert_eq!(result.session_id.len(), 255);
|
||||
assert_eq!(result.session_id, session_id);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Adversarial digest values
|
||||
// ------------------------------------------------------------------
|
||||
@@ -867,6 +916,23 @@ fn test_parse_tls_record_header() {
|
||||
assert_eq!(result.1, 16384);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_tls_record_header_rejects_invalid_versions() {
|
||||
let invalid = [
|
||||
[0x16, 0x03, 0x00, 0x00, 0x10],
|
||||
[0x16, 0x02, 0x00, 0x00, 0x10],
|
||||
[0x16, 0x03, 0x02, 0x00, 0x10],
|
||||
[0x16, 0x04, 0x00, 0x00, 0x10],
|
||||
];
|
||||
for header in invalid {
|
||||
assert!(
|
||||
parse_tls_record_header(&header).is_none(),
|
||||
"invalid TLS record version {:?} must be rejected",
|
||||
[header[1], header[2]]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gen_fake_x25519_key() {
|
||||
let rng = crate::crypto::SecureRandom::new();
|
||||
@@ -1168,6 +1234,47 @@ fn extract_sni_rejects_when_extension_block_is_truncated() {
|
||||
assert!(extract_sni_from_client_hello(&ch).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_sni_rejects_session_id_len_overflow() {
|
||||
let mut ch = build_client_hello_with_exts(Vec::new(), "example.com");
|
||||
let sid_len_pos = 5 + 4 + 2 + 32;
|
||||
ch[sid_len_pos] = 255;
|
||||
assert!(extract_sni_from_client_hello(&ch).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_sni_rejects_cipher_suites_len_overflow() {
|
||||
let mut ch = build_client_hello_with_exts(Vec::new(), "example.com");
|
||||
let sid_len_pos = 5 + 4 + 2 + 32;
|
||||
let cipher_len_pos = sid_len_pos + 1 + ch[sid_len_pos] as usize;
|
||||
ch[cipher_len_pos] = 0xFF;
|
||||
ch[cipher_len_pos + 1] = 0xFF;
|
||||
assert!(extract_sni_from_client_hello(&ch).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_sni_rejects_compression_methods_len_overflow() {
|
||||
let mut ch = build_client_hello_with_exts(Vec::new(), "example.com");
|
||||
let sid_len_pos = 5 + 4 + 2 + 32;
|
||||
let cipher_len_pos = sid_len_pos + 1 + ch[sid_len_pos] as usize;
|
||||
let cipher_len = u16::from_be_bytes([ch[cipher_len_pos], ch[cipher_len_pos + 1]]) as usize;
|
||||
let comp_len_pos = cipher_len_pos + 2 + cipher_len;
|
||||
ch[comp_len_pos] = 0xFF;
|
||||
assert!(extract_sni_from_client_hello(&ch).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_alpn_returns_empty_on_session_id_len_overflow() {
|
||||
let mut alpn_data = Vec::new();
|
||||
alpn_data.extend_from_slice(&3u16.to_be_bytes());
|
||||
alpn_data.push(2);
|
||||
alpn_data.extend_from_slice(b"h2");
|
||||
let mut ch = build_client_hello_with_exts(vec![(0x0010, alpn_data)], "alpn.test");
|
||||
let sid_len_pos = 5 + 4 + 2 + 32;
|
||||
ch[sid_len_pos] = 255;
|
||||
assert!(extract_alpn_from_client_hello(&ch).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_alpn_rejects_when_extension_block_is_truncated() {
|
||||
let mut ext_blob = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user