#![deny(unsafe_code)]
use std::net::{SocketAddr, TcpListener};
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;
use zenith_stack::web::app::{success_response, App};
use zenith_stack::web::server::{ProtocolServer, ServerConfig};
use zenith_stack::tls::{CertGeneration, TlsAcceptor, TlsConfig};
const CERT_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUEUD9CUfA86J7odmW+9fgH5y3ziIwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDcyOTIyMDAxMFoXDTI3MDcy
OTIyMDAxMFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAuAcuUu9Ajdh//C3n75jRuI0CAfI1EdX/SzDAUZoK1Vbi
+7ZQJ2y7YXvJEa03/CYMn7Qr7Cj/NW+7HZNvxD4o6gAMrNdB4qIEOEM/mSQwXryH
ELJb/it/mC66Sklm3hYjNx0naPLf/5ZGlXuBjr1Um5dT5V1H9BG/tssO2jFbcGpo
MnbZ16J0LO4Jgh8ojaRqzu408CpFnYLUueNwNeG/T+3LUxFpdWNf5uvu4ErW0R4/
rWH4dPlS6/UktQ92jxHHukikPj8hnDkv0H4TfNn+H3cIRIA76wNqif6UTPYPLtkU
4WfvUKHPsto4SQSvG2nk31wtWJZ7PFIS611KpnbXYQIDAQABo1MwUTAdBgNVHQ4E
FgQUxsaRnqWYFIFm8wcXRUMeUGWNOdYwHwYDVR0jBBgwFoAUxsaRnqWYFIFm8wcX
RUMeUGWNOdYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAgAzn
7GtDuMU1hLiQdXohLzZh2bu1ySmas3ZoAm/Ydbs6+wQA7CaGZ+Q0gTb0CZZUR60r
6qQvD3JkYqNuakUHZlDWZ6NboO9O3F4SybRR3F9Y08kjog7jORAFqh/QpfVgh2Ep
psDAK2TxIFKaLG4It4wE68x9FhHQZfJIekSe1qJXAd6CYmhbZRCIu/5xScijCpJ7
BO0mTj7WU1u3bj8/7jld4ct5dbtEIHpF4rq/5uSCbqPNvA8sqA4i4+beK3aEiXl1
yCPpOR133rFfOg/t0FiqKy1Vtj+kDJgVhzjPrw1w3L50h6iRmug52ozLYMdyOQOI
rEpfPnlOnBBdLz8hjA==
-----END CERTIFICATE-----
";
const KEY_PEM: &[u8] = b"-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC4By5S70CN2H/8
LefvmNG4jQIB8jUR1f9LMMBRmgrVVuL7tlAnbLthe8kRrTf8JgyftCvsKP81b7sd
k2/EPijqAAys10HiogQ4Qz+ZJDBevIcQslv+K3+YLrpKSWbeFiM3HSdo8t//lkaV
e4GOvVSbl1PlXUf0Eb+2yw7aMVtwamgydtnXonQs7gmCHyiNpGrO7jTwKkWdgtS5
43A14b9P7ctTEWl1Y1/m6+7gStbRHj+tYfh0+VLr9SS1D3aPEce6SKQ+PyGcOS/Q
fhN82f4fdwhEgDvrA2qJ/pRM9g8u2RThZ+9Qoc+y2jhJBK8baeTfXC1Ylns8UhLr
XUqmdtdhAgMBAAECggEAEh9y6mvxWYa2o+kJbEkKbjhEuFhX7Ze7enYkmmSnKHdU
ByHfJuLIWUNNe9YpK0W7/IZLxQgMigCk1rbMTPEqKlEy7lqMfHskGz5UJwqvUMUU
MArAkHlMKXqAkgxEex6G/Uh7txQkBxGPhe0RxzLSADiY5H+ZNGoDDUdWARrXPGyz
ZWlNY/nwg5qKO8zQePNAtUvHUWtNq3G8achi96NsFuuxuXwLKOx60JCMcg5BpRpb
2v8F+2r0ZjngkhI+wYGE7E5E/BdbbooHb1MFyajc+C/GH+sOhSfMMthjwKiSSaIB
/Jpqp9ETJ4GsxTxZTTOszyM5yPBIiPIeuwR0LwXRWQKBgQDjwBJuUqNl0CNMj7I7
6FmPrylzY7JKQB3q1SXqVPKeuPArd2BNBjGX6Ic9k0lLtmcDcsHnKNSXZaTb6UQw
APFlsdWBkWfaNeVh7SLlhez1MWwryuWCw/V8RdMR8MiOcZ+QfjPBLpUgJi/e6CLE
kvv8806vSKwhXQf1Ho43cmhrvwKBgQDO2sOBcYHupfjqOzWewJfR+rOs9F3/YYr/
fZQpdRu4z8zPfTfeR7O8NhvTtNJuzwi/yOjas1DEriBql8zUVDRHJZibj43qvuG6
Ua/cTmbcnyiEv15V0nmv91rEPgTtaf6/iz/zVvlKYT0Zj3wIxpX3rTiw6Ba3wD7I
81EM4K4E3wKBgQCVtmgnP4mL3xOlO3y1ptpg+ossAChuaNGB0lXXQbovnnC6kgGr
AFxPeJqWXqC69Y+oE6LlWtDNKRMDQMcDK0uERy+LudLj/bPo+KKM8MnAsJlj/D99
A2X3KEtEqtybzpNOv7cz0XRUKuYjCMP6JokhUauyy/njAK2/czOXvUxpLwKBgQCX
h+haRdViBpGevPsdrYZKGzZOR8EoGMOjP9IuwIwrSYaGpPstSSdgg97EqpzQ8bc+
DyaNN3i+a7RxgXxaOskFKYRuyK20vlpLjBWg9IojqjAbdrjbc9ES18fVJH2lkdU9
afvR/e+mzi7dL6A0KY2on2t9JLenqhwURzIjld/EzwKBgEoHKFsBZQqYoTBjw46g
VP3U0WBNPxtLlFNFWTh7SVAXTDkSOi4j5YeSEzGt4EHdd+RSVSjap0Yq+kLE3ZzB
hDJ2OdlHsaaSJcZ9+Vnnj44fSAJUKZcAQhq9CfZKcxvQiDuxTP3GZRl0urUdiedO
n6Pgv5yWjDwcNLUlPWhljvBK
-----END PRIVATE KEY-----
";
const CURL_EXE: &str = "/mnt/c/Users/20981/AppData/Local/Microsoft/WinGet/Packages/cURL.cURL_Microsoft.Winget.Source_8wekyb3d8bbwe/curl-8.20.0_5-win64-mingw/bin/curl.exe";
fn curl_status(url: &str) -> i32 {
let out = Command::new(CURL_EXE)
.args(["--http1.1", "-k", "-s", "-o", "NUL", "-w", "%{http_code}", "--max-time", "5", url])
.output();
match out {
Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).trim().parse().unwrap_or(-1),
_ => -1,
}
}
fn build_app() -> App {
let mut app = App::new();
app.get("/", |_r, _m| Ok(success_response("fp ok\n", "text/plain; charset=utf-8")));
app.get("/hello/:name", |_r, rm| {
let name = rm.get("name").unwrap_or("x");
Ok(success_response(format!("Hello {name}!\n"), "text/plain; charset=utf-8"))
});
let _ = app.validate();
app
}
fn main() {
let _ = tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")))
.try_init();
let app = build_app();
let cfg = ServerConfig::new()
.with_waf(true)
.with_fingerprint_security(true)
.with_fingerprint_match(zenith_stack::web::FingerprintMatch::Ja4);
let server = ProtocolServer::try_with_config(app, cfg).expect("server");
let server = Arc::new(server);
let cert = CertGeneration::from_pem(CERT_PEM, KEY_PEM).expect("cert");
let alpn: Vec<Vec<u8>> = vec![b"http/1.1".to_vec(), b"h2".to_vec()];
let mut tls_config = TlsConfig::new().with_alpn(alpn);
tls_config.min_version = Some(zenith_stack::tls::TlsVersion::Tls12);
tls_config.max_version = Some(zenith_stack::tls::TlsVersion::Tls13);
let mut acceptor = TlsAcceptor::new(tls_config);
acceptor.set_default_cert(cert.clone()).unwrap();
acceptor.set_cert_for_domain("localhost", cert).unwrap();
let listener = TcpListener::bind("0.0.0.0:18083").unwrap();
println!("[fp] TLS 服务器监听 0.0.0.0:18083,指纹安全=ON");
let url = "https://192.168.124.60:18083/hello/fp";
let srv = server.clone();
std::thread::spawn(move || {
let mut acc = acceptor;
for stream in listener.incoming() {
let Ok(stream) = stream else { continue };
let peer = stream.peer_addr().unwrap_or(SocketAddr::from(([0,0,0,0],0)));
let _ = srv.serve_std_tcp_conn(stream, peer, Some(&mut acc));
}
});
std::thread::sleep(Duration::from_millis(400));
let mut pass = 0u32;
let mut fail = 0u32;
let first = curl_status(url);
println!("[fp] curl.exe 首次连接(未入黑名单)状态码: {first}");
std::thread::sleep(Duration::from_millis(300));
let fps = server.security().fingerprint_snapshot();
println!("[fp] 已采集 curl.exe 指纹数: {}", fps.len());
if fps.is_empty() {
println!(" [FAIL] 未采集到 curl.exe TLS 指纹");
fail += 1;
} else {
let f = fps.last().unwrap();
println!(" [PASS] 采集到 curl.exe TLS 指纹: ja3={} ja4={}", f.ja3_hash, f.ja4_hash);
pass += 1;
}
let block_ja3 = fps.last().map(|f| f.ja4_hash.clone());
if let Some(ja4) = &block_ja3 {
server.security().add_fingerprint_to_blocklist(ja4.clone());
println!("[fp] 已加入黑名单: ja4={}", ja4);
let bl = server.security().fingerprint_blocklist_snapshot();
if bl.contains(ja4) {
println!(" [PASS] 黑名单包含该 JA4(热更新指纹阻断规则生效)");
pass += 1;
} else {
println!(" [FAIL] 黑名单未包含该 JA4");
fail += 1;
}
}
std::thread::sleep(Duration::from_millis(200));
let second = curl_status(url);
println!("[fp] curl.exe 二次连接(已在黑名单)状态码: {second}");
let all: Vec<(String, String)> = server.security().fingerprint_snapshot().iter().map(|f| (f.ja3_hash.clone(), f.ja3_string.clone())).collect();
println!("[fp] 当前已采集指纹数={} 全部={:?}", all.len(), all);
if second == 403 {
println!(" [PASS] 黑名单 JA3 被 403 阻断");
pass += 1;
} else {
println!(" [FAIL] 黑名单 JA3 未被阻断,实际状态码: {second}");
fail += 1;
}
if let Some(ja4) = &block_ja3 {
server.security().remove_fingerprint_from_blocklist(ja4);
println!("[fp] 已移除黑名单");
std::thread::sleep(Duration::from_millis(200));
let third = curl_status(url);
println!("[fp] curl.exe 移除黑名单后状态码: {third}");
if third == 200 {
println!(" [PASS] 移除黑名单后恢复 200");
pass += 1;
} else {
println!(" [FAIL] 移除黑名单后未恢复,实际: {third}");
fail += 1;
}
}
server.update_runtime(|rt| rt.set_fingerprint_security(false));
if let Some(ja4) = &block_ja3 {
server.security().add_fingerprint_to_blocklist(ja4.clone());
std::thread::sleep(Duration::from_millis(200));
let off = curl_status(url);
println!("[fp] curl.exe 关闭指纹安全后(黑名单仍在)状态码: {off}");
if off == 200 {
println!(" [PASS] 指纹安全关闭后放行(运行时热关)");
pass += 1;
} else {
println!(" [FAIL] 指纹安全关闭后仍被阻: {off}");
fail += 1;
}
}
server.update_runtime(|rt| rt.set_fingerprint_security(true));
server.update_runtime(|rt| rt.set_fingerprint_match(zenith_stack::web::FingerprintMatch::Ja3));
if let Some(ja4) = &block_ja3 {
server.security().add_fingerprint_to_blocklist(ja4.clone());
std::thread::sleep(Duration::from_millis(200));
let hot = curl_status(url);
println!("[fp] 运行时热切策略=JA3(黑名单为JA4)状态码: {hot}");
if hot == 200 {
println!(" [PASS] 策略热切到 Ja3 后,JA4 黑名单不再命中(自由切换生效)");
pass += 1;
} else {
println!(" [INFO] 策略热切到 Ja3 后状态码: {hot}(JA3 恰好命中则可能仍被阻断)");
}
}
println!("\n============================================================");
println!(" fingerprint_block_test: {} 通过, {} 失败", pass, fail);
println!("============================================================");
if fail > 0 { std::process::exit(1); }
}