Skip to main content

bao_stealth/
lib.rs

1#![allow(dead_code, unused_imports)]
2// REQ-STL-007: Stealth engine integration and CDP stealth  @trace REQ-STL-001 [entity:TlsProfile] [entity:StealthProfile]
3mod behavior;
4mod canvas;
5pub mod engine_props;
6pub mod hooks;
7mod http2;
8mod navigator;
9mod profile;
10pub mod stealth_wire;
11mod tls;
12mod webgl_audio;
13
14pub use behavior::{BehaviorConfig, BehaviorSimulator, ClickEvent, ClickEventType, TypingEvent};
15pub use canvas::CanvasNoise;
16pub use hooks::StealthHooks;
17pub use http2::Http2Fingerprint;
18pub use http2::{PriorityFrame, PriorityFrameMode};
19pub use http2::{global_http2_fingerprint, set_global_http2_fingerprint};
20pub use navigator::{NavigatorProfile, ScreenProfile};
21pub use profile::StealthProfile;
22pub use profile::{
23    BatteryConfig, ClientRectsConfig, FontConfig, ScreenDisplayConfig, TimingConfig, WebRtcMode,
24};
25pub use stealth_wire::StealthTlsWireConfig;
26pub use tls::TlsFingerprint;
27pub use tls::TlsFingerprintConfig;
28// IANA → OpenSSL/BoringSSL name mapping — single source of truth shared by
29// every TLS stack in Bao (servo `net::connector`, `bao_runtime`/`bun_http`).
30pub use tls::boringssl_cipher_list_string;
31pub use tls::boringssl_curves_list_string;
32pub use tls::boringssl_sigalgs_list_string;
33pub use tls::cipher_suite_boringssl_supported;
34pub use tls::cipher_suite_openssl_name;
35pub use tls::group_boringssl_supported;
36pub use tls::group_openssl_name;
37pub use tls::sigalg_openssl_name;
38pub use webgl_audio::{AudioProfile, WebGLProfile};
39
40pub struct StealthEngine {
41    profile: StealthProfile,
42}
43
44impl StealthEngine {
45    pub fn new(profile: StealthProfile) -> Self {
46        StealthEngine { profile }
47    }
48
49    #[allow(clippy::new_ret_no_self)]
50    pub fn default_engine() -> Self {
51        Self::new(StealthProfile::firefox_default())
52    }
53
54    pub fn profile(&self) -> &StealthProfile {
55        &self.profile
56    }
57
58    pub fn tls_config(&self) -> &TlsFingerprint {
59        &self.profile.tls
60    }
61
62    pub fn http2_config(&self) -> &Http2Fingerprint {
63        &self.profile.http2
64    }
65
66    pub fn canvas_noise(&self) -> &CanvasNoise {
67        &self.profile.canvas
68    }
69
70    pub fn navigator(&self) -> &NavigatorProfile {
71        &self.profile.navigator
72    }
73
74    pub fn screen(&self) -> &ScreenProfile {
75        &self.profile.screen
76    }
77
78    pub fn webgl(&self) -> &WebGLProfile {
79        &self.profile.webgl
80    }
81
82    pub fn audio(&self) -> &AudioProfile {
83        &self.profile.audio
84    }
85
86    pub fn behavior(&self) -> &BehaviorSimulator {
87        &self.profile.behavior
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn new_stores_profile() {
97        let profile = StealthProfile::firefox_default();
98        let engine = StealthEngine::new(profile.clone());
99        assert_eq!(
100            engine.profile().navigator.user_agent,
101            profile.navigator.user_agent
102        );
103    }
104
105    #[test]
106    fn default_engine_is_firefox() {
107        let engine = StealthEngine::default_engine();
108        let firefox = StealthProfile::firefox_default();
109        assert_eq!(
110            engine.profile().navigator.user_agent,
111            firefox.navigator.user_agent
112        );
113    }
114
115    #[test]
116    fn tls_config_matches_profile() {
117        let engine = StealthEngine::default_engine();
118        assert_eq!(engine.tls_config().ja3_hash, engine.profile().tls.ja3_hash);
119    }
120
121    #[test]
122    fn http2_config_matches_profile() {
123        let engine = StealthEngine::default_engine();
124        assert_eq!(
125            engine.http2_config().header_table_size,
126            engine.profile().http2.header_table_size
127        );
128    }
129
130    #[test]
131    fn canvas_noise_matches_profile() {
132        let engine = StealthEngine::default_engine();
133        assert_eq!(engine.canvas_noise().seed(), engine.profile().canvas.seed());
134    }
135
136    #[test]
137    fn navigator_matches_profile() {
138        let engine = StealthEngine::default_engine();
139        assert_eq!(
140            engine.navigator().user_agent,
141            engine.profile().navigator.user_agent
142        );
143    }
144
145    #[test]
146    fn screen_matches_profile() {
147        let engine = StealthEngine::default_engine();
148        assert_eq!(engine.screen().width, engine.profile().screen.width);
149        assert_eq!(engine.screen().height, engine.profile().screen.height);
150    }
151
152    #[test]
153    fn webgl_matches_profile() {
154        let engine = StealthEngine::default_engine();
155        assert_eq!(engine.webgl().vendor, engine.profile().webgl.vendor);
156        assert_eq!(engine.webgl().renderer, engine.profile().webgl.renderer);
157    }
158
159    #[test]
160    fn audio_matches_profile() {
161        let engine = StealthEngine::default_engine();
162        assert!(
163            (engine.audio().noise_amplitude() - engine.profile().audio.noise_amplitude()).abs()
164                < f64::EPSILON
165        );
166    }
167
168    #[test]
169    fn behavior_matches_profile() {
170        let engine = StealthEngine::default_engine();
171        assert_eq!(engine.behavior().seed(), engine.profile().behavior.seed());
172    }
173}