mirror of https://github.com/telemt/telemt.git
Time-To-Life for TLS Full Certificate
This commit is contained in:
parent
cfe8fc72a5
commit
b5d0564f2a
|
|
@ -229,6 +229,7 @@ tls_domain = "{domain}"
|
|||
mask = true
|
||||
mask_port = 443
|
||||
fake_cert_len = 2048
|
||||
tls_full_cert_ttl_secs = 90
|
||||
|
||||
[access]
|
||||
replay_check_len = 65536
|
||||
|
|
|
|||
|
|
@ -122,6 +122,10 @@ pub(crate) fn default_tls_new_session_tickets() -> u8 {
|
|||
0
|
||||
}
|
||||
|
||||
pub(crate) fn default_tls_full_cert_ttl_secs() -> u64 {
|
||||
90
|
||||
}
|
||||
|
||||
pub(crate) fn default_server_hello_delay_min_ms() -> u64 {
|
||||
0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -474,6 +474,12 @@ pub struct AntiCensorshipConfig {
|
|||
#[serde(default = "default_tls_new_session_tickets")]
|
||||
pub tls_new_session_tickets: u8,
|
||||
|
||||
/// TTL in seconds for sending full certificate payload per client IP.
|
||||
/// First client connection per (SNI domain, client IP) gets full cert payload.
|
||||
/// Subsequent handshakes within TTL use compact cert metadata payload.
|
||||
#[serde(default = "default_tls_full_cert_ttl_secs")]
|
||||
pub tls_full_cert_ttl_secs: u64,
|
||||
|
||||
/// Enforce ALPN echo of client preference.
|
||||
#[serde(default = "default_alpn_enforce")]
|
||||
pub alpn_enforce: bool,
|
||||
|
|
@ -494,6 +500,7 @@ impl Default for AntiCensorshipConfig {
|
|||
server_hello_delay_min_ms: default_server_hello_delay_min_ms(),
|
||||
server_hello_delay_max_ms: default_server_hello_delay_max_ms(),
|
||||
tls_new_session_tickets: default_tls_new_session_tickets(),
|
||||
tls_full_cert_ttl_secs: default_tls_full_cert_ttl_secs(),
|
||||
alpn_enforce: default_alpn_enforce(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
||||
use tracing::{debug, warn, trace, info};
|
||||
use zeroize::Zeroize;
|
||||
|
|
@ -118,7 +119,13 @@ where
|
|||
config.censorship.tls_domain.clone()
|
||||
};
|
||||
let cached_entry = cache.get(&selected_domain).await;
|
||||
let use_full_cert_payload = cache.take_full_cert_budget(&selected_domain).await;
|
||||
let use_full_cert_payload = cache
|
||||
.take_full_cert_budget_for_ip(
|
||||
&selected_domain,
|
||||
peer.ip(),
|
||||
Duration::from_secs(config.censorship.tls_full_cert_ttl_secs),
|
||||
)
|
||||
.await;
|
||||
Some((cached_entry, use_full_cert_payload))
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, Duration};
|
||||
use std::time::{Duration, Instant, SystemTime};
|
||||
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::time::sleep;
|
||||
|
|
@ -14,7 +15,7 @@ use crate::tls_front::types::{CachedTlsData, ParsedServerHello, TlsFetchResult};
|
|||
pub struct TlsFrontCache {
|
||||
memory: RwLock<HashMap<String, Arc<CachedTlsData>>>,
|
||||
default: Arc<CachedTlsData>,
|
||||
full_cert_sent: RwLock<HashSet<String>>,
|
||||
full_cert_sent: RwLock<HashMap<(String, IpAddr), Instant>>,
|
||||
disk_path: PathBuf,
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +48,7 @@ impl TlsFrontCache {
|
|||
Self {
|
||||
memory: RwLock::new(map),
|
||||
default,
|
||||
full_cert_sent: RwLock::new(HashSet::new()),
|
||||
full_cert_sent: RwLock::new(HashMap::new()),
|
||||
disk_path: disk_path.as_ref().to_path_buf(),
|
||||
}
|
||||
}
|
||||
|
|
@ -61,9 +62,41 @@ impl TlsFrontCache {
|
|||
self.memory.read().await.contains_key(domain)
|
||||
}
|
||||
|
||||
/// Returns true only on first request for a domain after process start.
|
||||
pub async fn take_full_cert_budget(&self, domain: &str) -> bool {
|
||||
self.full_cert_sent.write().await.insert(domain.to_string())
|
||||
/// Returns true when full cert payload should be sent for (domain, client_ip)
|
||||
/// according to TTL policy.
|
||||
pub async fn take_full_cert_budget_for_ip(
|
||||
&self,
|
||||
domain: &str,
|
||||
client_ip: IpAddr,
|
||||
ttl: Duration,
|
||||
) -> bool {
|
||||
if ttl.is_zero() {
|
||||
self.full_cert_sent
|
||||
.write()
|
||||
.await
|
||||
.insert((domain.to_string(), client_ip), Instant::now());
|
||||
return true;
|
||||
}
|
||||
|
||||
let now = Instant::now();
|
||||
let mut guard = self.full_cert_sent.write().await;
|
||||
guard.retain(|_, seen_at| now.duration_since(*seen_at) < ttl);
|
||||
|
||||
let key = (domain.to_string(), client_ip);
|
||||
match guard.get_mut(&key) {
|
||||
Some(seen_at) => {
|
||||
if now.duration_since(*seen_at) >= ttl {
|
||||
*seen_at = now;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
None => {
|
||||
guard.insert(key, now);
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set(&self, domain: &str, data: CachedTlsData) {
|
||||
|
|
@ -174,3 +207,50 @@ impl TlsFrontCache {
|
|||
&self.disk_path
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_take_full_cert_budget_for_ip_uses_ttl() {
|
||||
let cache = TlsFrontCache::new(
|
||||
&["example.com".to_string()],
|
||||
1024,
|
||||
"tlsfront-test-cache",
|
||||
);
|
||||
let ip: IpAddr = "127.0.0.1".parse().expect("ip");
|
||||
let ttl = Duration::from_millis(80);
|
||||
|
||||
assert!(cache
|
||||
.take_full_cert_budget_for_ip("example.com", ip, ttl)
|
||||
.await);
|
||||
assert!(!cache
|
||||
.take_full_cert_budget_for_ip("example.com", ip, ttl)
|
||||
.await);
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(90)).await;
|
||||
|
||||
assert!(cache
|
||||
.take_full_cert_budget_for_ip("example.com", ip, ttl)
|
||||
.await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_take_full_cert_budget_for_ip_zero_ttl_always_allows_full_payload() {
|
||||
let cache = TlsFrontCache::new(
|
||||
&["example.com".to_string()],
|
||||
1024,
|
||||
"tlsfront-test-cache",
|
||||
);
|
||||
let ip: IpAddr = "127.0.0.1".parse().expect("ip");
|
||||
let ttl = Duration::ZERO;
|
||||
|
||||
assert!(cache
|
||||
.take_full_cert_budget_for_ip("example.com", ip, ttl)
|
||||
.await);
|
||||
assert!(cache
|
||||
.take_full_cert_budget_for_ip("example.com", ip, ttl)
|
||||
.await);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue