diff --git a/impit-node/index.d.ts b/impit-node/index.d.ts index 3aa7935c..bcbd6be1 100644 --- a/impit-node/index.d.ts +++ b/impit-node/index.d.ts @@ -269,6 +269,7 @@ export type Browser = 'chrome'| 'firefox133'| 'firefox135'| 'firefox144'| +'firefox158'| 'okhttp'| 'okhttp3'| 'okhttp4'| diff --git a/impit-node/src/impit_builder.rs b/impit-node/src/impit_builder.rs index edf427dc..0051e7e9 100644 --- a/impit-node/src/impit_builder.rs +++ b/impit-node/src/impit_builder.rs @@ -33,6 +33,7 @@ pub enum Browser { Firefox133, Firefox135, Firefox144, + Firefox158, OkHttp, OkHttp3, OkHttp4, @@ -143,6 +144,7 @@ impl From for BrowserFingerprint { Browser::Firefox133 => impit::fingerprint::database::firefox_133::fingerprint(), Browser::Firefox135 => impit::fingerprint::database::firefox_135::fingerprint(), Browser::Firefox144 => impit::fingerprint::database::firefox_144::fingerprint(), + Browser::Firefox158 => impit::fingerprint::database::firefox_158::fingerprint(), Browser::OkHttp3 => impit::fingerprint::database::okhttp3::fingerprint(), Browser::OkHttp | Browser::OkHttp4 => impit::fingerprint::database::okhttp4::fingerprint(), Browser::OkHttp5 => impit::fingerprint::database::okhttp5::fingerprint(), diff --git a/impit-python/python/impit/__init__.py b/impit-python/python/impit/__init__.py index 970107a9..71c80b4d 100644 --- a/impit-python/python/impit/__init__.py +++ b/impit-python/python/impit/__init__.py @@ -116,6 +116,7 @@ 'firefox133', 'firefox135', 'firefox144', + 'firefox158', 'okhttp', 'okhttp3', 'okhttp4', diff --git a/impit-python/src/fingerprint.rs b/impit-python/src/fingerprint.rs index 7d5edd47..232936c6 100644 --- a/impit-python/src/fingerprint.rs +++ b/impit-python/src/fingerprint.rs @@ -23,6 +23,7 @@ pub(crate) fn fingerprint_by_name(browser: &str) -> PyResult "firefox133" => database::firefox_133::fingerprint(), "firefox135" => database::firefox_135::fingerprint(), "firefox144" => database::firefox_144::fingerprint(), + "firefox158" => database::firefox_158::fingerprint(), "okhttp3" => database::okhttp3::fingerprint(), "okhttp" | "okhttp4" => database::okhttp4::fingerprint(), "okhttp5" => database::okhttp5::fingerprint(), diff --git a/impit/src/fingerprint/database.rs b/impit/src/fingerprint/database.rs index 72c0d8fa..1788cadb 100644 --- a/impit/src/fingerprint/database.rs +++ b/impit/src/fingerprint/database.rs @@ -11,7 +11,7 @@ pub use chrome::{ chrome_100, chrome_101, chrome_104, chrome_107, chrome_110, chrome_116, chrome_124, chrome_125, chrome_131, chrome_133, chrome_136, chrome_142, chrome_151, }; -pub use firefox::{firefox_128, firefox_133, firefox_135, firefox_144}; +pub use firefox::{firefox_128, firefox_133, firefox_135, firefox_144, firefox_158}; pub use okhttp::{okhttp3, okhttp4, okhttp5}; pub use safari::ios_18; @@ -71,6 +71,7 @@ mod tests { ("firefox_133", firefox_133::fingerprint()), ("firefox_135", firefox_135::fingerprint()), ("firefox_144", firefox_144::fingerprint()), + ("firefox_158", firefox_158::fingerprint()), ("okhttp3", okhttp3::fingerprint()), ("okhttp4", okhttp4::fingerprint()), ("okhttp5", okhttp5::fingerprint()), diff --git a/impit/src/fingerprint/database/firefox.rs b/impit/src/fingerprint/database/firefox.rs index c6772d61..7d040ca9 100644 --- a/impit/src/fingerprint/database/firefox.rs +++ b/impit/src/fingerprint/database/firefox.rs @@ -482,6 +482,150 @@ pub mod firefox_144 { } } +/// Firefox 158 fingerprint module +/// +/// Captured from Firefox 158.0 (official Mozilla build, Windows) on real sites, +/// with the traffic decrypted in Wireshark via `SSLKEYLOGFILE`, and +/// cross-checked against . Compared with Firefox 144, it +/// no longer offers the `0xc009`/`0xc00a` cipher suites or the FFDHE groups, +/// and the second `accept-language` entry has `q=0.9`. The HTTP/2 settings are +/// unchanged. Its user agent is the Windows one; firefox133–144 use macOS's. +pub mod firefox_158 { + use super::*; + + /// Returns the complete Firefox 158 fingerprint + pub fn fingerprint() -> BrowserFingerprint { + BrowserFingerprint::new( + "Firefox", + "158", + tls_fingerprint(), + firefox_135::http2_fingerprint(), + headers(), + ) + } + + /// Firefox 158 TLS fingerprint + fn tls_fingerprint() -> TlsFingerprint { + TlsFingerprint::new( + // Cipher suites in Firefox 158 preference order + vec![ + CipherSuite::TLS13_AES_128_GCM_SHA256, + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS13_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA, + CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA, + ], + // Key exchange groups (post-quantum hybrid first, no FFDHE) + vec![ + KeyExchangeGroup::X25519MLKEM768, + KeyExchangeGroup::X25519, + KeyExchangeGroup::Secp256r1, + KeyExchangeGroup::Secp384r1, + KeyExchangeGroup::Secp521r1, + ], + // Signature algorithms + vec![ + SignatureAlgorithm::EcdsaSecp256r1Sha256, + SignatureAlgorithm::EcdsaSecp384r1Sha384, + SignatureAlgorithm::EcdsaSecp521r1Sha512, + SignatureAlgorithm::RsaPssRsaSha256, + SignatureAlgorithm::RsaPssRsaSha384, + SignatureAlgorithm::RsaPssRsaSha512, + SignatureAlgorithm::RsaPkcs1Sha256, + SignatureAlgorithm::RsaPkcs1Sha384, + SignatureAlgorithm::RsaPkcs1Sha512, + SignatureAlgorithm::EcdsaSha1Legacy, + SignatureAlgorithm::RsaPkcs1Sha1, + ], + // TLS extensions configuration + TlsExtensions::new( + true, // server_name + true, // status_request + true, // supported_groups + true, // signature_algorithms + true, // application_layer_protocol_negotiation + true, // signed_certificate_timestamp + true, // key_share + true, // psk_key_exchange_modes + true, // supported_versions + Some(vec![ + CertificateCompressionAlgorithm::Zlib, + CertificateCompressionAlgorithm::Brotli, + CertificateCompressionAlgorithm::Zstd, + ]), // compress_certificate + false, // application_settings + true, // delegated_credentials + Some(16385), // record_size_limit (0x4001) + // Extension order (critical for fingerprinting) + vec![ + ExtensionType::ServerName, + ExtensionType::ExtendedMasterSecret, + ExtensionType::RenegotiationInfo, + ExtensionType::SupportedGroups, + ExtensionType::EcPointFormats, + ExtensionType::SessionTicket, + ExtensionType::ApplicationLayerProtocolNegotiation, + ExtensionType::StatusRequest, + ExtensionType::DelegatedCredentials, + ExtensionType::SignedCertificateTimestamp, + ExtensionType::KeyShare, + ExtensionType::SupportedVersions, + ExtensionType::SignatureAlgorithms, + ExtensionType::PskKeyExchangeModes, + ExtensionType::RecordSizeLimit, + ExtensionType::CompressCertificate, + ], + ), + // ECH configuration (GREASE mode) + Some(EchConfig::new( + EchMode::Grease { + hpke_suite: HpkeKemId::DhKemX25519HkdfSha256, + }, + None, + )), + // ALPN protocols + vec![b"h2".to_vec(), b"http/1.1".to_vec()], + ) + } + + /// Firefox 158 HTTP headers + fn headers() -> Vec<(String, String)> { + vec![ + ( + "user-agent".to_string(), + "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:158.0) Gecko/20100101 Firefox/158.0" + .to_string(), + ), + ( + "accept".to_string(), + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8".to_string(), + ), + ("accept-language".to_string(), "en-US,en;q=0.9".to_string()), + ( + "accept-encoding".to_string(), + "gzip, deflate, br, zstd".to_string(), + ), + ("upgrade-insecure-requests".to_string(), "1".to_string()), + ("sec-fetch-dest".to_string(), "document".to_string()), + ("sec-fetch-mode".to_string(), "navigate".to_string()), + ("sec-fetch-site".to_string(), "none".to_string()), + ("sec-fetch-user".to_string(), "?1".to_string()), + ("priority".to_string(), "u=0, i".to_string()), + ("te".to_string(), "trailers".to_string()), + ] + } +} + #[cfg(test)] mod tests { use super::*; @@ -489,10 +633,11 @@ mod tests { use std::sync::Arc; // JA3 strings (before hashing) of real Firefox's cold TCP ClientHello, from decrypted Wireshark captures of - // Firefox 128, 133, 135 and 144 on Windows (October 2026). Firefox 144's is the same as 135's. + // Firefox 128, 133, 135, 144 and 158 on Windows (October 2026). Firefox 144's is the same as 135's. const JA3_128: &str = "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-51-43-13-45-28-65037,29-23-24-25-256-257,0"; const JA3_133: &str = "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-51-43-13-45-28-27-65037,4588-29-23-24-25-256-257,0"; const JA3_135: &str = "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49162-49161-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-18-51-43-13-45-28-27-65037,4588-29-23-24-25-256-257,0"; + const JA3_158: &str = "771,4865-4867-4866-49195-49199-52393-52392-49196-49200-49171-49172-156-157-47-53,0-23-65281-10-11-35-16-5-34-18-51-43-13-45-28-27-65037,4588-29-23-24-25,0"; /// The ClientHello that impit sends for `fingerprint`: legacy version, cipher suites, and each extension's type /// and body in wire order. @@ -574,6 +719,7 @@ mod tests { ("firefox_133", firefox_133::fingerprint(), JA3_133), ("firefox_135", firefox_135::fingerprint(), JA3_135), ("firefox_144", firefox_144::fingerprint(), JA3_135), + ("firefox_158", firefox_158::fingerprint(), JA3_158), ]; let mut mismatches = Vec::new(); @@ -600,6 +746,7 @@ mod tests { ("firefox_133", firefox_133::fingerprint()), ("firefox_135", firefox_135::fingerprint()), ("firefox_144", firefox_144::fingerprint()), + ("firefox_158", firefox_158::fingerprint()), ]; let mut wrong = Vec::new();