zenith-stack 0.1.0

Zenith 全协议栈框架:AF_XDP + eBPF + TLS 1.3 + HTTP/1-2-3 + Web + Proxy + WAF,按需导入
Documentation
//! # fingerprint_ext_test — 双客户端(Windows curl.exe + WSL curl)全链路指纹识别与差异化阻断
//!
//! 真实验证(真实 TLS 1.3 握手 + 真实外部客户端):
//! 1. Windows curl.exe 与 WSL curl 分属不同 TLS 栈 → 应有不同 JA3/JA4 指纹
//! 2. 服务器分别采集到两者的指纹(差异识别)
//! 3. 仅封禁 WSL curl 指纹 → WSL curl 403,但 curl.exe(不同指纹)仍 200
//! 4. 同时封禁两者 → 两者都 403
//! 5. 单独解封 curl.exe → curl.exe 恢复 200,WSL curl 仍 403
//! 6. 全部解封 → 两者都 200
//! 7. 重新封禁 curl.exe 后关闭指纹安全 → 放行(热关)
//! 8. 重开安全 + 热切匹配策略 JA4→JA3(黑名单为 JA4)→ 不命中 → 放行(热切)
//! 9. 热切回 JA4 → 重新命中 → 403(热切回)
//! 10. 白名单:web 安全管线仅 blacklist,无 TLS 指纹白名单 → 未列入黑名单即放行(隐式白名单)

#![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";

/// Windows curl.exe 发起 HTTPS 请求,返回状态码(失败/超时返回 -1)
fn curl_exe_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,
    }
}

/// WSL 原生 curl(OpenSSL 3.5.5 / curl 8.18.0)发起 HTTPS 请求,返回状态码
fn wsl_curl_status(url: &str) -> i32 {
    let out = Command::new("/usr/bin/curl")
        .args(["--http1.1", "-k", "-s", "-o", "/dev/null", "-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("/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!("[ext] TLS 服务器监听 0.0.0.0:18083,指纹安全=ON (match=Ja4)");

    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 url = "https://192.168.124.60:18083/hello/fp";

    let mut pass = 0u32;
    let mut fail = 0u32;

    // 用最近一次采集到的新指纹(按时间顺序取 last)
    let last_fp = |srv: &ProtocolServer| srv.security().fingerprint_snapshot().into_iter().last();

    // 1. curl.exe 首次连接 → 采集指纹
    let c1 = curl_exe_status(url);
    println!("[1] curl.exe 首次连接(未封禁): {c1}");
    std::thread::sleep(Duration::from_millis(300));
    let fp_win = last_fp(&server).expect("curl.exe 指纹未采集");
    println!("    curl.exe 指纹: ja3={}  ja4={}", fp_win.ja3_hash, fp_win.ja4_hash);
    if c1 == 200 && !fp_win.ja4_hash.is_empty() { println!("  [PASS] curl.exe 采集指纹"); pass += 1; }
    else { println!("  [FAIL] curl.exe 采集失败"); fail += 1; }

    // 2. WSL curl 首次连接 → 采集指纹
    let c2 = wsl_curl_status(url);
    println!("[2] WSL curl 首次连接(未封禁): {c2}");
    std::thread::sleep(Duration::from_millis(300));
    let fp_wsl = last_fp(&server).expect("WSL curl 指纹未采集");
    println!("    WSL curl 指纹: ja3={}  ja4={}", fp_wsl.ja3_hash, fp_wsl.ja4_hash);
    if c2 == 200 && !fp_wsl.ja4_hash.is_empty() { println!("  [PASS] WSL curl 采集指纹"); pass += 1; }
    else { println!("  [FAIL] WSL curl 采集失败"); fail += 1; }

    // 3. 两个客户端指纹必须不同(差异识别)
    if fp_win.ja4_hash != fp_wsl.ja4_hash {
        println!("  [PASS] 双客户端 JA4 指纹不同(差异识别成功)");
        pass += 1;
    } else {
        println!("  [FAIL] 双客户端 JA4 指纹相同: {}", fp_win.ja4_hash);
        fail += 1;
    }

    // 4. 仅封禁 WSL curl 的 JA4 → WSL curl 403,curl.exe 仍 200
    server.security().add_fingerprint_to_blocklist(fp_wsl.ja4_hash.clone());
    std::thread::sleep(Duration::from_millis(200));
    let c4w = wsl_curl_status(url);
    let c4e = curl_exe_status(url);
    println!("[4] 仅封 WSL curl: WSL curl={c4w}, curl.exe={c4e} (期望 403/200)");
    if c4w == 403 && c4e == 200 { println!("  [PASS] 差异化阻断生效"); pass += 1; }
    else { println!("  [FAIL] 差异化阻断: WSL={c4w} EXE={c4e}"); fail += 1; }

    // 5. 追加封禁 curl.exe → 两者都 403
    server.security().add_fingerprint_to_blocklist(fp_win.ja4_hash.clone());
    std::thread::sleep(Duration::from_millis(200));
    let c5w = wsl_curl_status(url);
    let c5e = curl_exe_status(url);
    println!("[5] 双封禁: WSL curl={c5w}, curl.exe={c5e} (期望 403/403)");
    if c5w == 403 && c5e == 403 { println!("  [PASS] 两者都被 403 阻断"); pass += 1; }
    else { println!("  [FAIL] 双封禁: WSL={c5w} EXE={c5e}"); fail += 1; }

    // 6. 解封 curl.exe → curl.exe 200,WSL curl 仍 403
    server.security().remove_fingerprint_from_blocklist(&fp_win.ja4_hash);
    std::thread::sleep(Duration::from_millis(200));
    let c6w = wsl_curl_status(url);
    let c6e = curl_exe_status(url);
    println!("[6] 解封 curl.exe: WSL curl={c6w}, curl.exe={c6e} (期望 403/200)");
    if c6w == 403 && c6e == 200 { println!("[PASS] 单独解封生效"); pass += 1; }
    else { println!("[FAIL] 单独解封: WSL={c6w} EXE={c6e}"); fail += 1; }

    // 7. 全部解封 → 两者都 200
    server.security().remove_fingerprint_from_blocklist(&fp_wsl.ja4_hash);
    std::thread::sleep(Duration::from_millis(200));
    let c7w = wsl_curl_status(url);
    let c7e = curl_exe_status(url);
    println!("[7] 全部解封: WSL curl={c7w}, curl.exe={c7e} (期望 200/200)");
    if c7w == 200 && c7e == 200 { println!("[PASS] 全部恢复 200"); pass += 1; }
    else { println!("[FAIL] 全部解封: WSL={c7w} EXE={c7e}"); fail += 1; }

    // 8. 重新封禁 curl.exe,运行时关闭指纹安全 → 放行(黑名单仍在)
    server.security().add_fingerprint_to_blocklist(fp_win.ja4_hash.clone());
    server.update_runtime(|rt| rt.set_fingerprint_security(false));
    std::thread::sleep(Duration::from_millis(200));
    let c8 = curl_exe_status(url);
    println!("[8] 关闭指纹安全(黑名单仍在): curl.exe={c8} (期望 200)");
    if c8 == 200 { println!("[PASS] 安全关闭后放行(热关)"); pass += 1; }
    else { println!("[FAIL] 安全关闭后仍被阻: {c8}"); fail += 1; }

    // 9. 重开安全 + 热切匹配策略到 Ja3(黑名单为 JA4)→ 不命中 → 放行
    server.update_runtime(|rt| rt.set_fingerprint_security(true));
    server.update_runtime(|rt| rt.set_fingerprint_match(zenith_stack::web::FingerprintMatch::Ja3));
    std::thread::sleep(Duration::from_millis(200));
    let c9 = curl_exe_status(url);
    println!("[9] 重开安全+热切Ja3(黑名单JA4): curl.exe={c9} (期望 200)");
    if c9 == 200 { println!("[PASS] 策略热切 Ja3 后 JA4 黑名单不命中(自由切换)"); pass += 1; }
    else { println!("[FAIL] 热切 Ja3 后状态码: {c9}"); fail += 1; }

    // 10. 热切回 Ja4 → 重新命中 → 403(无需重启)
    server.update_runtime(|rt| rt.set_fingerprint_match(zenith_stack::web::FingerprintMatch::Ja4));
    std::thread::sleep(Duration::from_millis(200));
    let c10 = curl_exe_status(url);
    println!("[10] 热切回 Ja4(黑名单JA4): curl.exe={c10} (期望 403)");
    if c10 == 403 { println!("[PASS] 策略热切回 Ja4 后重新阻断"); pass += 1; }
    else { println!("[FAIL] 热切回 Ja4 状态码: {c10}"); fail += 1; }

    // 恢复干净状态
    server.security().remove_fingerprint_from_blocklist(&fp_win.ja4_hash);
    server.update_runtime(|rt| rt.set_fingerprint_security(true));
    server.update_runtime(|rt| rt.set_fingerprint_match(zenith_stack::web::FingerprintMatch::Ja4));

    println!("\n最后黑名单: {:?}", server.security().fingerprint_blocklist_snapshot());
    println!("最后已采集指纹数: {}", server.security().fingerprint_count());

    println!("\n============================================================");
    println!("  fingerprint_ext_test: {} 通过, {} 失败", pass, fail);
    println!("============================================================");
    if fail > 0 { std::process::exit(1); }
}