Skip to main content

hpx_emulation/fingerprint/
mod.rs

1//! Structured browser fingerprint types.
2//!
3//! This module provides structured, comparable, and serializable representations
4//! of browser fingerprints. Each fingerprint captures the TLS ClientHello parameters,
5//! HTTP/2 SETTINGS, and HTTP headers that a specific browser version would send.
6//!
7//! # Design Goals
8//!
9//! - **Type safety**: Use enums instead of strings for cipher suites, curves, etc.
10//! - **Comparability**: Enable `Eq`/`Hash` for fingerprint identity checks
11//! - **Testability**: Allow unit tests to verify specific fingerprint parameters
12//! - **Extensibility**: Support custom fingerprints alongside predefined ones
13//!
14//! # Example
15//!
16//! ```rust
17//! use hpx_emulation::{Emulation, fingerprint::BrowserFingerprint};
18//!
19//! let fp = BrowserFingerprint::from_emulation(Emulation::Chrome133);
20//! assert_eq!(fp.name, "chrome");
21//! assert_eq!(fp.version, "133");
22//! assert!(fp.tls.curves.contains(&Curve::X25519MLKEM768));
23//! ```
24
25mod cache;
26mod composer;
27mod diff;
28
29pub use cache::{clear_tls_cache, get_or_build_tls, tls_cache_len};
30pub use composer::HeaderComposer;
31pub use diff::{FingerprintDiff, diff_fingerprints};
32
33/// Builds a structured `TlsFingerprint` from a named `TlsPreset`.
34///
35/// This is the bridge between the opaque `tls_options!(N)` macro system
36/// and the new structured fingerprint types. Each preset maps to a
37/// specific combination of TLS features (curves, ECH, permutation, etc.).
38#[cfg(feature = "emulation")]
39pub fn tls_fingerprint_from_preset(preset: TlsPreset) -> TlsFingerprint {
40    // Delegate to the emulation layer's implementation
41    crate::emulation::device::tls_fingerprint_from_preset(preset)
42}
43
44/// Named TLS configuration presets.
45///
46/// Each preset represents a specific TLS fingerprint configuration commonly
47/// used by browser families. These replace the opaque `tls_options!(N)` macro
48/// numbers with self-documenting names.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub enum TlsPreset {
51    /// Base Chrome TLS config: standard curves, no ECH, no permute.
52    ChromeBase,
53    /// Chrome with ECH GREASE enabled.
54    ChromeEchGrease,
55    /// Chrome with extension permutation.
56    ChromePermute,
57    /// Chrome with both ECH GREASE and extension permutation.
58    ChromePermuteEch,
59    /// Chrome with ECH GREASE, permutation, and PSK.
60    ChromePermuteEchPsk,
61    /// Chrome with X25519Kyber768Draft00 post-quantum curves.
62    ChromeKyber,
63    /// Chrome with X25519MLKEM768 post-quantum curves and new ALPS codepoint.
64    ChromeMlkem768,
65    /// Firefox base TLS config.
66    FirefoxBase,
67    /// Firefox with ECH GREASE.
68    FirefoxEchGrease,
69    /// Safari base TLS config.
70    SafariBase,
71    /// OkHttp base TLS config.
72    OkHttpBase,
73}
74
75/// Elliptic curves supported for TLS key exchange.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
77pub enum Curve {
78    X25519,
79    X25519Kyber768Draft00,
80    X25519MLKEM768,
81    Secp256r1,
82    Secp384r1,
83    Secp521r1,
84}
85
86impl Curve {
87    /// Returns the OpenSSL/BoringSSL name for this curve.
88    pub const fn openssl_name(&self) -> &'static str {
89        match self {
90            Curve::X25519 => "X25519",
91            Curve::X25519Kyber768Draft00 => "X25519Kyber768Draft00",
92            Curve::X25519MLKEM768 => "X25519MLKEM768",
93            Curve::Secp256r1 => "P-256",
94            Curve::Secp384r1 => "P-384",
95            Curve::Secp521r1 => "P-521",
96        }
97    }
98}
99
100impl std::fmt::Display for Curve {
101    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102        f.write_str(self.openssl_name())
103    }
104}
105
106/// TLS cipher suites.
107#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
108pub enum CipherSuite {
109    // TLS 1.3
110    Tls13Aes128GcmSha256,
111    Tls13Aes256GcmSha384,
112    Tls13ChaCha20Poly1305Sha256,
113    // TLS 1.2 ECDHE + ECDSA
114    EcdheEcdsaWithAes128GcmSha256,
115    EcdheEcdsaWithAes256GcmSha384,
116    EcdheEcdsaWithChaCha20Poly1305Sha256,
117    EcdheEcdsaWithAes128CbcSha,
118    EcdheEcdsaWithAes256CbcSha,
119    // TLS 1.2 ECDHE + RSA
120    EcdheRsaWithAes128GcmSha256,
121    EcdheRsaWithAes256GcmSha384,
122    EcdheRsaWithChaCha20Poly1305Sha256,
123    EcdheRsaWithAes128CbcSha,
124    EcdheRsaWithAes256CbcSha,
125    // TLS 1.2 RSA
126    RsaWithAes128GcmSha256,
127    RsaWithAes256GcmSha384,
128    RsaWithAes128CbcSha,
129    RsaWithAes256CbcSha,
130}
131
132impl CipherSuite {
133    /// Returns the OpenSSL/BoringSSL name for this cipher suite.
134    pub const fn openssl_name(&self) -> &'static str {
135        match self {
136            CipherSuite::Tls13Aes128GcmSha256 => "TLS_AES_128_GCM_SHA256",
137            CipherSuite::Tls13Aes256GcmSha384 => "TLS_AES_256_GCM_SHA384",
138            CipherSuite::Tls13ChaCha20Poly1305Sha256 => "TLS_CHACHA20_POLY1305_SHA256",
139            CipherSuite::EcdheEcdsaWithAes128GcmSha256 => "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
140            CipherSuite::EcdheEcdsaWithAes256GcmSha384 => "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
141            CipherSuite::EcdheEcdsaWithChaCha20Poly1305Sha256 => {
142                "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256"
143            }
144            CipherSuite::EcdheEcdsaWithAes128CbcSha => "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
145            CipherSuite::EcdheEcdsaWithAes256CbcSha => "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
146            CipherSuite::EcdheRsaWithAes128GcmSha256 => "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
147            CipherSuite::EcdheRsaWithAes256GcmSha384 => "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
148            CipherSuite::EcdheRsaWithChaCha20Poly1305Sha256 => {
149                "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256"
150            }
151            CipherSuite::EcdheRsaWithAes128CbcSha => "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
152            CipherSuite::EcdheRsaWithAes256CbcSha => "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
153            CipherSuite::RsaWithAes128GcmSha256 => "TLS_RSA_WITH_AES_128_GCM_SHA256",
154            CipherSuite::RsaWithAes256GcmSha384 => "TLS_RSA_WITH_AES_256_GCM_SHA384",
155            CipherSuite::RsaWithAes128CbcSha => "TLS_RSA_WITH_AES_128_CBC_SHA",
156            CipherSuite::RsaWithAes256CbcSha => "TLS_RSA_WITH_AES_256_CBC_SHA",
157        }
158    }
159}
160
161/// Signature algorithms for TLS.
162#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
163pub enum SignatureAlgorithm {
164    EcdsaSecp256r1Sha256,
165    RsaPssRsaeSha256,
166    RsaPkcs1Sha256,
167    EcdsaSecp384r1Sha384,
168    RsaPssRsaeSha384,
169    RsaPkcs1Sha384,
170    RsaPssRsaeSha512,
171    RsaPkcs1Sha512,
172}
173
174impl SignatureAlgorithm {
175    /// Returns the OpenSSL/BoringSSL name for this algorithm.
176    pub const fn openssl_name(&self) -> &'static str {
177        match self {
178            SignatureAlgorithm::EcdsaSecp256r1Sha256 => "ecdsa_secp256r1_sha256",
179            SignatureAlgorithm::RsaPssRsaeSha256 => "rsa_pss_rsae_sha256",
180            SignatureAlgorithm::RsaPkcs1Sha256 => "rsa_pkcs1_sha256",
181            SignatureAlgorithm::EcdsaSecp384r1Sha384 => "ecdsa_secp384r1_sha384",
182            SignatureAlgorithm::RsaPssRsaeSha384 => "rsa_pss_rsae_sha384",
183            SignatureAlgorithm::RsaPkcs1Sha384 => "rsa_pkcs1_sha384",
184            SignatureAlgorithm::RsaPssRsaeSha512 => "rsa_pss_rsae_sha512",
185            SignatureAlgorithm::RsaPkcs1Sha512 => "rsa_pkcs1_sha512",
186        }
187    }
188}
189
190/// Certificate compression algorithm.
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
192pub enum CertCompression {
193    Brotli,
194    Zlib,
195    Zstd,
196}
197
198/// ECH (Encrypted Client Hello) mode.
199#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
200pub enum EchMode {
201    Disabled,
202    Grease,
203}
204
205/// TLS fingerprint specification.
206#[derive(Clone, Debug, PartialEq, Eq, Hash)]
207pub struct TlsFingerprint {
208    /// Ordered list of elliptic curves.
209    pub curves: Vec<Curve>,
210    /// Ordered list of cipher suites.
211    pub cipher_suites: Vec<CipherSuite>,
212    /// Ordered list of signature algorithms.
213    pub signature_algorithms: Vec<SignatureAlgorithm>,
214    /// Whether to permute ClientHello extensions.
215    pub permute_extensions: bool,
216    /// ECH mode.
217    pub ech_mode: EchMode,
218    /// Whether to enable PSK (pre-shared key).
219    pub pre_shared_key: bool,
220    /// Certificate compression algorithms.
221    pub cert_compression: Vec<CertCompression>,
222    /// Whether to use the new ALPS codepoint (Chrome 132+).
223    pub alps_use_new_codepoint: bool,
224}
225
226impl Default for TlsFingerprint {
227    fn default() -> Self {
228        Self {
229            curves: vec![Curve::X25519, Curve::Secp256r1, Curve::Secp384r1],
230            cipher_suites: vec![
231                CipherSuite::Tls13Aes128GcmSha256,
232                CipherSuite::Tls13Aes256GcmSha384,
233                CipherSuite::Tls13ChaCha20Poly1305Sha256,
234                CipherSuite::EcdheEcdsaWithAes128GcmSha256,
235                CipherSuite::EcdheRsaWithAes128GcmSha256,
236                CipherSuite::EcdheEcdsaWithAes256GcmSha384,
237                CipherSuite::EcdheRsaWithAes256GcmSha384,
238                CipherSuite::EcdheEcdsaWithChaCha20Poly1305Sha256,
239                CipherSuite::EcdheRsaWithChaCha20Poly1305Sha256,
240                CipherSuite::EcdheRsaWithAes128CbcSha,
241                CipherSuite::EcdheRsaWithAes256CbcSha,
242                CipherSuite::RsaWithAes128GcmSha256,
243                CipherSuite::RsaWithAes256GcmSha384,
244                CipherSuite::RsaWithAes128CbcSha,
245                CipherSuite::RsaWithAes256CbcSha,
246            ],
247            signature_algorithms: vec![
248                SignatureAlgorithm::EcdsaSecp256r1Sha256,
249                SignatureAlgorithm::RsaPssRsaeSha256,
250                SignatureAlgorithm::RsaPkcs1Sha256,
251                SignatureAlgorithm::EcdsaSecp384r1Sha384,
252                SignatureAlgorithm::RsaPssRsaeSha384,
253                SignatureAlgorithm::RsaPkcs1Sha384,
254                SignatureAlgorithm::RsaPssRsaeSha512,
255                SignatureAlgorithm::RsaPkcs1Sha512,
256            ],
257            permute_extensions: false,
258            ech_mode: EchMode::Disabled,
259            pre_shared_key: false,
260            cert_compression: vec![CertCompression::Brotli],
261            alps_use_new_codepoint: false,
262        }
263    }
264}
265
266impl TlsFingerprint {
267    /// Converts the curves list to a colon-separated string for BoringSSL/OpenSSL.
268    pub fn curves_string(&self) -> String {
269        self.curves
270            .iter()
271            .map(|c| c.openssl_name())
272            .collect::<Vec<_>>()
273            .join(":")
274    }
275
276    /// Converts the cipher suites to a colon-separated string.
277    pub fn cipher_suites_string(&self) -> String {
278        self.cipher_suites
279            .iter()
280            .map(|c| c.openssl_name())
281            .collect::<Vec<_>>()
282            .join(":")
283    }
284
285    /// Converts the signature algorithms to a colon-separated string.
286    pub fn signature_algorithms_string(&self) -> String {
287        self.signature_algorithms
288            .iter()
289            .map(|a| a.openssl_name())
290            .collect::<Vec<_>>()
291            .join(":")
292    }
293}
294
295/// HTTP/2 fingerprint specification.
296#[derive(Clone, Debug, PartialEq, Eq, Hash)]
297pub struct Http2Fingerprint {
298    /// SETTINGS frame: initial stream window size.
299    pub initial_window_size: u32,
300    /// SETTINGS frame: initial connection window size.
301    pub initial_connection_window_size: u32,
302    /// SETTINGS frame: max concurrent streams.
303    pub max_concurrent_streams: Option<u32>,
304    /// SETTINGS frame: max header list size.
305    pub max_header_list_size: u32,
306    /// SETTINGS frame: header table size.
307    pub header_table_size: u32,
308    /// SETTINGS frame: enable push.
309    pub enable_push: Option<bool>,
310    /// Pseudo-header order in HEADERS frame.
311    pub pseudo_header_order: PseudoHeaderOrder,
312}
313
314/// HTTP/2 pseudo-header ordering.
315#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
316pub enum PseudoHeaderOrder {
317    /// :method, :authority, :scheme, :path (Chrome default)
318    MethodAuthoritySchemePath,
319}
320
321impl Default for Http2Fingerprint {
322    fn default() -> Self {
323        Self {
324            initial_window_size: 6291456,
325            initial_connection_window_size: 15728640,
326            max_concurrent_streams: Some(1000),
327            max_header_list_size: 262144,
328            header_table_size: 65536,
329            enable_push: None,
330            pseudo_header_order: PseudoHeaderOrder::MethodAuthoritySchemePath,
331        }
332    }
333}
334
335/// A complete browser fingerprint.
336///
337/// Contains all the information needed to replicate a specific browser's
338/// TLS ClientHello, HTTP/2 SETTINGS, and HTTP request headers.
339#[derive(Clone, Debug, PartialEq, Eq)]
340pub struct BrowserFingerprint {
341    /// Browser name (e.g., "chrome", "firefox", "safari").
342    pub name: &'static str,
343    /// Browser version string (e.g., "133", "146").
344    pub version: &'static str,
345    /// TLS ClientHello fingerprint.
346    pub tls: TlsFingerprint,
347    /// HTTP/2 SETTINGS fingerprint.
348    pub http2: Http2Fingerprint,
349    /// Default HTTP headers by OS.
350    pub headers: Vec<(&'static str, &'static str)>,
351}
352
353#[cfg(feature = "emulation-serde")]
354impl serde::Serialize for BrowserFingerprint {
355    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
356    where
357        S: serde::Serializer,
358    {
359        use serde::ser::SerializeStruct;
360        let mut state = serializer.serialize_struct("BrowserFingerprint", 5)?;
361        state.serialize_field("name", &self.name)?;
362        state.serialize_field("version", &self.version)?;
363        state.serialize_field("tls_curves", &self.tls.curves_string())?;
364        state.serialize_field("tls_cipher_suites", &self.tls.cipher_suites_string())?;
365        state.serialize_field(
366            "tls_signature_algorithms",
367            &self.tls.signature_algorithms_string(),
368        )?;
369        state.serialize_field("tls_permute_extensions", &self.tls.permute_extensions)?;
370        state.serialize_field("tls_ech_mode", &format!("{:?}", self.tls.ech_mode))?;
371        state.serialize_field("tls_pre_shared_key", &self.tls.pre_shared_key)?;
372        state.serialize_field(
373            "tls_alps_use_new_codepoint",
374            &self.tls.alps_use_new_codepoint,
375        )?;
376        state.serialize_field("h2_initial_window_size", &self.http2.initial_window_size)?;
377        state.serialize_field(
378            "h2_max_concurrent_streams",
379            &self.http2.max_concurrent_streams,
380        )?;
381        state.serialize_field("h2_max_header_list_size", &self.http2.max_header_list_size)?;
382        state.serialize_field("h2_header_table_size", &self.http2.header_table_size)?;
383        state.end()
384    }
385}
386
387impl BrowserFingerprint {
388    /// Creates a new `BrowserFingerprint`.
389    pub fn new(
390        name: &'static str,
391        version: &'static str,
392        tls: TlsFingerprint,
393        http2: Http2Fingerprint,
394        headers: Vec<(&'static str, &'static str)>,
395    ) -> Self {
396        Self {
397            name,
398            version,
399            tls,
400            http2,
401            headers,
402        }
403    }
404}