mirror of https://github.com/telemt/telemt.git
TLS-F New Methods
This commit is contained in:
parent
4ddbb97908
commit
4677b43c6e
|
|
@ -8,7 +8,9 @@ use tokio::sync::RwLock;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tracing::{debug, warn, info};
|
use tracing::{debug, warn, info};
|
||||||
|
|
||||||
use crate::tls_front::types::{CachedTlsData, ParsedServerHello, TlsFetchResult};
|
use crate::tls_front::types::{
|
||||||
|
CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsFetchResult,
|
||||||
|
};
|
||||||
|
|
||||||
/// Lightweight in-memory + optional on-disk cache for TLS fronting data.
|
/// Lightweight in-memory + optional on-disk cache for TLS fronting data.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -37,6 +39,7 @@ impl TlsFrontCache {
|
||||||
cert_payload: None,
|
cert_payload: None,
|
||||||
app_data_records_sizes: vec![default_len],
|
app_data_records_sizes: vec![default_len],
|
||||||
total_app_data_len: default_len,
|
total_app_data_len: default_len,
|
||||||
|
behavior_profile: TlsBehaviorProfile::default(),
|
||||||
fetched_at: SystemTime::now(),
|
fetched_at: SystemTime::now(),
|
||||||
domain: "default".to_string(),
|
domain: "default".to_string(),
|
||||||
});
|
});
|
||||||
|
|
@ -189,6 +192,7 @@ impl TlsFrontCache {
|
||||||
cert_payload: fetched.cert_payload,
|
cert_payload: fetched.cert_payload,
|
||||||
app_data_records_sizes: fetched.app_data_records_sizes.clone(),
|
app_data_records_sizes: fetched.app_data_records_sizes.clone(),
|
||||||
total_app_data_len: fetched.total_app_data_len,
|
total_app_data_len: fetched.total_app_data_len,
|
||||||
|
behavior_profile: fetched.behavior_profile,
|
||||||
fetched_at: SystemTime::now(),
|
fetched_at: SystemTime::now(),
|
||||||
domain: domain.to_string(),
|
domain: domain.to_string(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::protocol::constants::{
|
||||||
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE, TLS_VERSION,
|
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE, TLS_VERSION,
|
||||||
};
|
};
|
||||||
use crate::protocol::tls::{TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key};
|
use crate::protocol::tls::{TLS_DIGEST_LEN, TLS_DIGEST_POS, gen_fake_x25519_key};
|
||||||
use crate::tls_front::types::{CachedTlsData, ParsedCertificateInfo};
|
use crate::tls_front::types::{CachedTlsData, ParsedCertificateInfo, TlsProfileSource};
|
||||||
|
|
||||||
const MIN_APP_DATA: usize = 64;
|
const MIN_APP_DATA: usize = 64;
|
||||||
const MAX_APP_DATA: usize = 16640; // RFC 8446 §5.2 allows up to 2^14 + 256
|
const MAX_APP_DATA: usize = 16640; // RFC 8446 §5.2 allows up to 2^14 + 256
|
||||||
|
|
@ -108,14 +108,12 @@ pub fn build_emulated_server_hello(
|
||||||
) -> Vec<u8> {
|
) -> Vec<u8> {
|
||||||
// --- ServerHello ---
|
// --- ServerHello ---
|
||||||
let mut extensions = Vec::new();
|
let mut extensions = Vec::new();
|
||||||
// KeyShare (x25519)
|
|
||||||
let key = gen_fake_x25519_key(rng);
|
let key = gen_fake_x25519_key(rng);
|
||||||
extensions.extend_from_slice(&0x0033u16.to_be_bytes()); // key_share
|
extensions.extend_from_slice(&0x0033u16.to_be_bytes());
|
||||||
extensions.extend_from_slice(&(2 + 2 + 32u16).to_be_bytes()); // len
|
extensions.extend_from_slice(&(2 + 2 + 32u16).to_be_bytes());
|
||||||
extensions.extend_from_slice(&0x001du16.to_be_bytes()); // X25519
|
extensions.extend_from_slice(&0x001du16.to_be_bytes());
|
||||||
extensions.extend_from_slice(&(32u16).to_be_bytes());
|
extensions.extend_from_slice(&(32u16).to_be_bytes());
|
||||||
extensions.extend_from_slice(&key);
|
extensions.extend_from_slice(&key);
|
||||||
// supported_versions (TLS1.3)
|
|
||||||
extensions.extend_from_slice(&0x002bu16.to_be_bytes());
|
extensions.extend_from_slice(&0x002bu16.to_be_bytes());
|
||||||
extensions.extend_from_slice(&(2u16).to_be_bytes());
|
extensions.extend_from_slice(&(2u16).to_be_bytes());
|
||||||
extensions.extend_from_slice(&0x0304u16.to_be_bytes());
|
extensions.extend_from_slice(&0x0304u16.to_be_bytes());
|
||||||
|
|
@ -128,7 +126,6 @@ pub fn build_emulated_server_hello(
|
||||||
extensions.push(alpn_proto.len() as u8);
|
extensions.push(alpn_proto.len() as u8);
|
||||||
extensions.extend_from_slice(alpn_proto);
|
extensions.extend_from_slice(alpn_proto);
|
||||||
}
|
}
|
||||||
|
|
||||||
let extensions_len = extensions.len() as u16;
|
let extensions_len = extensions.len() as u16;
|
||||||
|
|
||||||
let body_len = 2 + // version
|
let body_len = 2 + // version
|
||||||
|
|
@ -173,11 +170,22 @@ pub fn build_emulated_server_hello(
|
||||||
];
|
];
|
||||||
|
|
||||||
// --- ApplicationData (fake encrypted records) ---
|
// --- ApplicationData (fake encrypted records) ---
|
||||||
// Use the same number and sizes of ApplicationData records as the cached server.
|
let sizes = match cached.behavior_profile.source {
|
||||||
let mut sizes = cached.app_data_records_sizes.clone();
|
TlsProfileSource::Raw | TlsProfileSource::Merged => cached
|
||||||
if sizes.is_empty() {
|
.app_data_records_sizes
|
||||||
sizes.push(cached.total_app_data_len.max(1024));
|
.first()
|
||||||
}
|
.copied()
|
||||||
|
.or_else(|| cached.behavior_profile.app_data_record_sizes.first().copied())
|
||||||
|
.map(|size| vec![size])
|
||||||
|
.unwrap_or_else(|| vec![cached.total_app_data_len.max(1024)]),
|
||||||
|
_ => {
|
||||||
|
let mut sizes = cached.app_data_records_sizes.clone();
|
||||||
|
if sizes.is_empty() {
|
||||||
|
sizes.push(cached.total_app_data_len.max(1024));
|
||||||
|
}
|
||||||
|
sizes
|
||||||
|
}
|
||||||
|
};
|
||||||
let mut sizes = jitter_and_clamp_sizes(&sizes, rng);
|
let mut sizes = jitter_and_clamp_sizes(&sizes, rng);
|
||||||
let compact_payload = cached
|
let compact_payload = cached
|
||||||
.cert_info
|
.cert_info
|
||||||
|
|
@ -269,7 +277,9 @@ pub fn build_emulated_server_hello(
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use crate::tls_front::types::{CachedTlsData, ParsedServerHello, TlsCertPayload};
|
use crate::tls_front::types::{
|
||||||
|
CachedTlsData, ParsedServerHello, TlsBehaviorProfile, TlsCertPayload, TlsProfileSource,
|
||||||
|
};
|
||||||
|
|
||||||
use super::build_emulated_server_hello;
|
use super::build_emulated_server_hello;
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
|
|
@ -300,6 +310,7 @@ mod tests {
|
||||||
cert_payload,
|
cert_payload,
|
||||||
app_data_records_sizes: vec![64],
|
app_data_records_sizes: vec![64],
|
||||||
total_app_data_len: 64,
|
total_app_data_len: 64,
|
||||||
|
behavior_profile: TlsBehaviorProfile::default(),
|
||||||
fetched_at: SystemTime::now(),
|
fetched_at: SystemTime::now(),
|
||||||
domain: "example.com".to_string(),
|
domain: "example.com".to_string(),
|
||||||
}
|
}
|
||||||
|
|
@ -385,4 +396,34 @@ mod tests {
|
||||||
let payload = first_app_data_payload(&response);
|
let payload = first_app_data_payload(&response);
|
||||||
assert!(payload.starts_with(b"CN=example.com"));
|
assert!(payload.starts_with(b"CN=example.com"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_build_emulated_server_hello_ignores_tail_records_for_raw_profile() {
|
||||||
|
let mut cached = make_cached(None);
|
||||||
|
cached.app_data_records_sizes = vec![27, 3905, 537, 69];
|
||||||
|
cached.total_app_data_len = 4538;
|
||||||
|
cached.behavior_profile.source = TlsProfileSource::Merged;
|
||||||
|
cached.behavior_profile.app_data_record_sizes = vec![27, 3905, 537];
|
||||||
|
cached.behavior_profile.ticket_record_sizes = vec![69];
|
||||||
|
|
||||||
|
let rng = SecureRandom::new();
|
||||||
|
let response = build_emulated_server_hello(
|
||||||
|
b"secret",
|
||||||
|
&[0x12; 32],
|
||||||
|
&[0x34; 16],
|
||||||
|
&cached,
|
||||||
|
false,
|
||||||
|
&rng,
|
||||||
|
None,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let hello_len = u16::from_be_bytes([response[3], response[4]]) as usize;
|
||||||
|
let ccs_start = 5 + hello_len;
|
||||||
|
let app_start = ccs_start + 6;
|
||||||
|
let app_len = u16::from_be_bytes([response[app_start + 3], response[app_start + 4]]) as usize;
|
||||||
|
|
||||||
|
assert_eq!(response[app_start], TLS_RECORD_APPLICATION);
|
||||||
|
assert_eq!(app_start + 5 + app_len, response.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,18 @@ use x509_parser::certificate::X509Certificate;
|
||||||
|
|
||||||
use crate::crypto::SecureRandom;
|
use crate::crypto::SecureRandom;
|
||||||
use crate::network::dns_overrides::resolve_socket_addr;
|
use crate::network::dns_overrides::resolve_socket_addr;
|
||||||
use crate::protocol::constants::{TLS_RECORD_APPLICATION, TLS_RECORD_HANDSHAKE};
|
use crate::protocol::constants::{
|
||||||
|
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
|
||||||
|
};
|
||||||
use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder};
|
use crate::transport::proxy_protocol::{ProxyProtocolV1Builder, ProxyProtocolV2Builder};
|
||||||
use crate::tls_front::types::{
|
use crate::tls_front::types::{
|
||||||
ParsedCertificateInfo,
|
ParsedCertificateInfo,
|
||||||
ParsedServerHello,
|
ParsedServerHello,
|
||||||
|
TlsBehaviorProfile,
|
||||||
TlsCertPayload,
|
TlsCertPayload,
|
||||||
TlsExtension,
|
TlsExtension,
|
||||||
TlsFetchResult,
|
TlsFetchResult,
|
||||||
|
TlsProfileSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
/// No-op verifier: accept any certificate (we only need lengths and metadata).
|
||||||
|
|
@ -282,6 +286,41 @@ fn parse_server_hello(body: &[u8]) -> Option<ParsedServerHello> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn derive_behavior_profile(records: &[(u8, Vec<u8>)]) -> TlsBehaviorProfile {
|
||||||
|
let mut change_cipher_spec_count = 0u8;
|
||||||
|
let mut app_data_record_sizes = Vec::new();
|
||||||
|
|
||||||
|
for (record_type, body) in records {
|
||||||
|
match *record_type {
|
||||||
|
TLS_RECORD_CHANGE_CIPHER => {
|
||||||
|
change_cipher_spec_count = change_cipher_spec_count.saturating_add(1);
|
||||||
|
}
|
||||||
|
TLS_RECORD_APPLICATION => {
|
||||||
|
app_data_record_sizes.push(body.len());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ticket_record_sizes = Vec::new();
|
||||||
|
while app_data_record_sizes
|
||||||
|
.last()
|
||||||
|
.is_some_and(|size| *size <= 256 && ticket_record_sizes.len() < 2)
|
||||||
|
{
|
||||||
|
if let Some(size) = app_data_record_sizes.pop() {
|
||||||
|
ticket_record_sizes.push(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ticket_record_sizes.reverse();
|
||||||
|
|
||||||
|
TlsBehaviorProfile {
|
||||||
|
change_cipher_spec_count: change_cipher_spec_count.max(1),
|
||||||
|
app_data_record_sizes,
|
||||||
|
ticket_record_sizes,
|
||||||
|
source: TlsProfileSource::Raw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_cert_info(certs: &[CertificateDer<'static>]) -> Option<ParsedCertificateInfo> {
|
fn parse_cert_info(certs: &[CertificateDer<'static>]) -> Option<ParsedCertificateInfo> {
|
||||||
let first = certs.first()?;
|
let first = certs.first()?;
|
||||||
let (_rem, cert) = X509Certificate::from_der(first.as_ref()).ok()?;
|
let (_rem, cert) = X509Certificate::from_der(first.as_ref()).ok()?;
|
||||||
|
|
@ -443,39 +482,50 @@ where
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let mut records = Vec::new();
|
let mut records = Vec::new();
|
||||||
// Read up to 4 records: ServerHello, CCS, and up to two ApplicationData.
|
let mut app_records_seen = 0usize;
|
||||||
for _ in 0..4 {
|
// Read a bounded encrypted flight: ServerHello, CCS, certificate-like data,
|
||||||
|
// and a small number of ticket-like tail records.
|
||||||
|
for _ in 0..8 {
|
||||||
match timeout(connect_timeout, read_tls_record(&mut stream)).await {
|
match timeout(connect_timeout, read_tls_record(&mut stream)).await {
|
||||||
Ok(Ok(rec)) => records.push(rec),
|
Ok(Ok(rec)) => {
|
||||||
|
if rec.0 == TLS_RECORD_APPLICATION {
|
||||||
|
app_records_seen += 1;
|
||||||
|
}
|
||||||
|
records.push(rec);
|
||||||
|
}
|
||||||
Ok(Err(e)) => return Err(e),
|
Ok(Err(e)) => return Err(e),
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
}
|
}
|
||||||
if records.len() >= 3 && records.iter().any(|(t, _)| *t == TLS_RECORD_APPLICATION) {
|
if app_records_seen >= 4 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut app_sizes = Vec::new();
|
|
||||||
let mut server_hello = None;
|
let mut server_hello = None;
|
||||||
for (t, body) in &records {
|
for (t, body) in &records {
|
||||||
if *t == TLS_RECORD_HANDSHAKE && server_hello.is_none() {
|
if *t == TLS_RECORD_HANDSHAKE && server_hello.is_none() {
|
||||||
server_hello = parse_server_hello(body);
|
server_hello = parse_server_hello(body);
|
||||||
} else if *t == TLS_RECORD_APPLICATION {
|
|
||||||
app_sizes.push(body.len());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed = server_hello.ok_or_else(|| anyhow!("ServerHello not received"))?;
|
let parsed = server_hello.ok_or_else(|| anyhow!("ServerHello not received"))?;
|
||||||
|
let behavior_profile = derive_behavior_profile(&records);
|
||||||
|
let mut app_sizes = behavior_profile.app_data_record_sizes.clone();
|
||||||
|
app_sizes.extend_from_slice(&behavior_profile.ticket_record_sizes);
|
||||||
let total_app_data_len = app_sizes.iter().sum::<usize>().max(1024);
|
let total_app_data_len = app_sizes.iter().sum::<usize>().max(1024);
|
||||||
|
let app_data_records_sizes = behavior_profile
|
||||||
|
.app_data_record_sizes
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
.or_else(|| behavior_profile.ticket_record_sizes.first().copied())
|
||||||
|
.map(|size| vec![size])
|
||||||
|
.unwrap_or_else(|| vec![total_app_data_len]);
|
||||||
|
|
||||||
Ok(TlsFetchResult {
|
Ok(TlsFetchResult {
|
||||||
server_hello_parsed: parsed,
|
server_hello_parsed: parsed,
|
||||||
app_data_records_sizes: if app_sizes.is_empty() {
|
app_data_records_sizes,
|
||||||
vec![total_app_data_len]
|
|
||||||
} else {
|
|
||||||
app_sizes
|
|
||||||
},
|
|
||||||
total_app_data_len,
|
total_app_data_len,
|
||||||
|
behavior_profile,
|
||||||
cert_info: None,
|
cert_info: None,
|
||||||
cert_payload: None,
|
cert_payload: None,
|
||||||
})
|
})
|
||||||
|
|
@ -608,6 +658,12 @@ where
|
||||||
server_hello_parsed: parsed,
|
server_hello_parsed: parsed,
|
||||||
app_data_records_sizes: app_data_records_sizes.clone(),
|
app_data_records_sizes: app_data_records_sizes.clone(),
|
||||||
total_app_data_len: app_data_records_sizes.iter().sum(),
|
total_app_data_len: app_data_records_sizes.iter().sum(),
|
||||||
|
behavior_profile: TlsBehaviorProfile {
|
||||||
|
change_cipher_spec_count: 1,
|
||||||
|
app_data_record_sizes: app_data_records_sizes,
|
||||||
|
ticket_record_sizes: Vec::new(),
|
||||||
|
source: TlsProfileSource::Rustls,
|
||||||
|
},
|
||||||
cert_info,
|
cert_info,
|
||||||
cert_payload,
|
cert_payload,
|
||||||
})
|
})
|
||||||
|
|
@ -706,6 +762,7 @@ pub async fn fetch_real_tls(
|
||||||
if let Some(mut raw) = raw_result {
|
if let Some(mut raw) = raw_result {
|
||||||
raw.cert_info = rustls_result.cert_info;
|
raw.cert_info = rustls_result.cert_info;
|
||||||
raw.cert_payload = rustls_result.cert_payload;
|
raw.cert_payload = rustls_result.cert_payload;
|
||||||
|
raw.behavior_profile.source = TlsProfileSource::Merged;
|
||||||
debug!(sni = %sni, "Fetched TLS metadata via raw probe + rustls cert chain");
|
debug!(sni = %sni, "Fetched TLS metadata via raw probe + rustls cert chain");
|
||||||
Ok(raw)
|
Ok(raw)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -725,7 +782,11 @@ pub async fn fetch_real_tls(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::encode_tls13_certificate_message;
|
use super::{derive_behavior_profile, encode_tls13_certificate_message};
|
||||||
|
use crate::protocol::constants::{
|
||||||
|
TLS_RECORD_APPLICATION, TLS_RECORD_CHANGE_CIPHER, TLS_RECORD_HANDSHAKE,
|
||||||
|
};
|
||||||
|
use crate::tls_front::types::TlsProfileSource;
|
||||||
|
|
||||||
fn read_u24(bytes: &[u8]) -> usize {
|
fn read_u24(bytes: &[u8]) -> usize {
|
||||||
((bytes[0] as usize) << 16) | ((bytes[1] as usize) << 8) | (bytes[2] as usize)
|
((bytes[0] as usize) << 16) | ((bytes[1] as usize) << 8) | (bytes[2] as usize)
|
||||||
|
|
@ -753,4 +814,20 @@ mod tests {
|
||||||
fn test_encode_tls13_certificate_message_empty_chain() {
|
fn test_encode_tls13_certificate_message_empty_chain() {
|
||||||
assert!(encode_tls13_certificate_message(&[]).is_none());
|
assert!(encode_tls13_certificate_message(&[]).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_derive_behavior_profile_splits_ticket_like_tail_records() {
|
||||||
|
let profile = derive_behavior_profile(&[
|
||||||
|
(TLS_RECORD_HANDSHAKE, vec![0u8; 90]),
|
||||||
|
(TLS_RECORD_CHANGE_CIPHER, vec![0x01]),
|
||||||
|
(TLS_RECORD_APPLICATION, vec![0u8; 1400]),
|
||||||
|
(TLS_RECORD_APPLICATION, vec![0u8; 220]),
|
||||||
|
(TLS_RECORD_APPLICATION, vec![0u8; 180]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
assert_eq!(profile.change_cipher_spec_count, 1);
|
||||||
|
assert_eq!(profile.app_data_record_sizes, vec![1400]);
|
||||||
|
assert_eq!(profile.ticket_record_sizes, vec![220, 180]);
|
||||||
|
assert_eq!(profile.source, TlsProfileSource::Raw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,53 @@ pub struct TlsCertPayload {
|
||||||
pub certificate_message: Vec<u8>,
|
pub certificate_message: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Provenance of the cached TLS behavior profile.
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum TlsProfileSource {
|
||||||
|
/// Built from hardcoded defaults or legacy cache entries.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Derived from raw TLS record capture only.
|
||||||
|
Raw,
|
||||||
|
/// Derived from rustls-only metadata fallback.
|
||||||
|
Rustls,
|
||||||
|
/// Merged from raw TLS capture and rustls certificate metadata.
|
||||||
|
Merged,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Coarse-grained TLS response behavior captured per SNI.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct TlsBehaviorProfile {
|
||||||
|
/// Number of ChangeCipherSpec records observed before encrypted flight.
|
||||||
|
#[serde(default = "default_change_cipher_spec_count")]
|
||||||
|
pub change_cipher_spec_count: u8,
|
||||||
|
/// Sizes of the primary encrypted flight records carrying cert-like payload.
|
||||||
|
#[serde(default)]
|
||||||
|
pub app_data_record_sizes: Vec<usize>,
|
||||||
|
/// Sizes of small tail ApplicationData records that look like tickets.
|
||||||
|
#[serde(default)]
|
||||||
|
pub ticket_record_sizes: Vec<usize>,
|
||||||
|
/// Source of this behavior profile.
|
||||||
|
#[serde(default)]
|
||||||
|
pub source: TlsProfileSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_change_cipher_spec_count() -> u8 {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TlsBehaviorProfile {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
change_cipher_spec_count: default_change_cipher_spec_count(),
|
||||||
|
app_data_record_sizes: Vec::new(),
|
||||||
|
ticket_record_sizes: Vec::new(),
|
||||||
|
source: TlsProfileSource::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Cached data per SNI used by the emulator.
|
/// Cached data per SNI used by the emulator.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct CachedTlsData {
|
pub struct CachedTlsData {
|
||||||
|
|
@ -48,6 +95,8 @@ pub struct CachedTlsData {
|
||||||
pub cert_payload: Option<TlsCertPayload>,
|
pub cert_payload: Option<TlsCertPayload>,
|
||||||
pub app_data_records_sizes: Vec<usize>,
|
pub app_data_records_sizes: Vec<usize>,
|
||||||
pub total_app_data_len: usize,
|
pub total_app_data_len: usize,
|
||||||
|
#[serde(default)]
|
||||||
|
pub behavior_profile: TlsBehaviorProfile,
|
||||||
#[serde(default = "now_system_time", skip_serializing, skip_deserializing)]
|
#[serde(default = "now_system_time", skip_serializing, skip_deserializing)]
|
||||||
pub fetched_at: SystemTime,
|
pub fetched_at: SystemTime,
|
||||||
pub domain: String,
|
pub domain: String,
|
||||||
|
|
@ -63,6 +112,40 @@ pub struct TlsFetchResult {
|
||||||
pub server_hello_parsed: ParsedServerHello,
|
pub server_hello_parsed: ParsedServerHello,
|
||||||
pub app_data_records_sizes: Vec<usize>,
|
pub app_data_records_sizes: Vec<usize>,
|
||||||
pub total_app_data_len: usize,
|
pub total_app_data_len: usize,
|
||||||
|
#[serde(default)]
|
||||||
|
pub behavior_profile: TlsBehaviorProfile,
|
||||||
pub cert_info: Option<ParsedCertificateInfo>,
|
pub cert_info: Option<ParsedCertificateInfo>,
|
||||||
pub cert_payload: Option<TlsCertPayload>,
|
pub cert_payload: Option<TlsCertPayload>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cached_tls_data_deserializes_without_behavior_profile() {
|
||||||
|
let json = r#"
|
||||||
|
{
|
||||||
|
"server_hello_template": {
|
||||||
|
"version": [3, 3],
|
||||||
|
"random": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
|
"session_id": [],
|
||||||
|
"cipher_suite": [19, 1],
|
||||||
|
"compression": 0,
|
||||||
|
"extensions": []
|
||||||
|
},
|
||||||
|
"cert_info": null,
|
||||||
|
"cert_payload": null,
|
||||||
|
"app_data_records_sizes": [1024],
|
||||||
|
"total_app_data_len": 1024,
|
||||||
|
"domain": "example.com"
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let cached: CachedTlsData = serde_json::from_str(json).unwrap();
|
||||||
|
assert_eq!(cached.behavior_profile.change_cipher_spec_count, 1);
|
||||||
|
assert!(cached.behavior_profile.app_data_record_sizes.is_empty());
|
||||||
|
assert!(cached.behavior_profile.ticket_record_sizes.is_empty());
|
||||||
|
assert_eq!(cached.behavior_profile.source, TlsProfileSource::Default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue