diff --git a/Cargo.lock b/Cargo.lock index 1113d493..6cb3f9d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2267,8 +2267,8 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.39" -source = "git+https://github.com/apify/rustls?rev=1ebb6d466a557858cdd8c836ffcbb26d04d7a9f9#1ebb6d466a557858cdd8c836ffcbb26d04d7a9f9" +version = "0.23.43" +source = "git+https://github.com/apify/rustls?rev=23b2c17427c095b768e22ccf0dadb97266860cf1#23b2c17427c095b768e22ccf0dadb97266860cf1" dependencies = [ "aws-lc-rs", "brotli", diff --git a/Cargo.toml b/Cargo.toml index 689a48a3..655d00da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ members = [ [patch.crates-io] h2 = { git = "https://github.com/apify/h2", rev = "7f393a728a8db07cabb1b78d2094772b33943b9a" } -rustls = { git = "https://github.com/apify/rustls", rev="1ebb6d466a557858cdd8c836ffcbb26d04d7a9f9" } +rustls = { git = "https://github.com/apify/rustls", rev="23b2c17427c095b768e22ccf0dadb97266860cf1" } tower-http = { git = "https://github.com/apify/tower-http", rev="f9efc0d9193e774d33aedc1022b922efefc22052" } hyper-util = { git = "https://github.com/apify/hyper-util", rev="9b7795dfd7158fc55e7c84b65bf1dae1d2dea67d" } diff --git a/impit-node/index.d.ts b/impit-node/index.d.ts index 08b21fb9..b4ac8a1b 100644 --- a/impit-node/index.d.ts +++ b/impit-node/index.d.ts @@ -263,6 +263,7 @@ export type Browser = 'chrome'| 'chrome131'| 'chrome136'| 'chrome142'| +'chrome151'| 'firefox'| 'firefox128'| 'firefox133'| diff --git a/impit-node/src/impit_builder.rs b/impit-node/src/impit_builder.rs index 85e6a20e..36a80c5e 100644 --- a/impit-node/src/impit_builder.rs +++ b/impit-node/src/impit_builder.rs @@ -27,6 +27,7 @@ pub enum Browser { Chrome131, Chrome136, Chrome142, + Chrome151, Firefox, Firefox128, Firefox133, @@ -135,6 +136,7 @@ impl From for BrowserFingerprint { Browser::Chrome131 => impit::fingerprint::database::chrome_131::fingerprint(), Browser::Chrome136 => impit::fingerprint::database::chrome_136::fingerprint(), Browser::Chrome142 => impit::fingerprint::database::chrome_142::fingerprint(), + Browser::Chrome151 => impit::fingerprint::database::chrome_151::fingerprint(), Browser::Firefox | Browser::Firefox128 => { impit::fingerprint::database::firefox_128::fingerprint() } diff --git a/impit-python/python/impit/__init__.py b/impit-python/python/impit/__init__.py index dabee4d6..970107a9 100644 --- a/impit-python/python/impit/__init__.py +++ b/impit-python/python/impit/__init__.py @@ -110,6 +110,7 @@ 'chrome131', 'chrome136', 'chrome142', + 'chrome151', 'firefox', 'firefox128', 'firefox133', diff --git a/impit-python/src/fingerprint.rs b/impit-python/src/fingerprint.rs index 19c73a79..7d5edd47 100644 --- a/impit-python/src/fingerprint.rs +++ b/impit-python/src/fingerprint.rs @@ -18,6 +18,7 @@ pub(crate) fn fingerprint_by_name(browser: &str) -> PyResult "chrome131" => database::chrome_131::fingerprint(), "chrome136" => database::chrome_136::fingerprint(), "chrome142" => database::chrome_142::fingerprint(), + "chrome151" => database::chrome_151::fingerprint(), "firefox" | "firefox128" => database::firefox_128::fingerprint(), "firefox133" => database::firefox_133::fingerprint(), "firefox135" => database::firefox_135::fingerprint(), diff --git a/impit/src/fingerprint/database.rs b/impit/src/fingerprint/database.rs index ced5cd05..4719197b 100644 --- a/impit/src/fingerprint/database.rs +++ b/impit/src/fingerprint/database.rs @@ -9,7 +9,7 @@ mod safari; 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_131, chrome_133, chrome_136, chrome_142, chrome_151, }; pub use firefox::{firefox_128, firefox_133, firefox_135, firefox_144}; pub use okhttp::{okhttp3, okhttp4, okhttp5}; diff --git a/impit/src/fingerprint/database/chrome.rs b/impit/src/fingerprint/database/chrome.rs index 47848f3e..4fb5c675 100644 --- a/impit/src/fingerprint/database/chrome.rs +++ b/impit/src/fingerprint/database/chrome.rs @@ -2,6 +2,164 @@ use crate::fingerprint::*; +/// Chrome 151 fingerprint module +/// +/// Source: capture against from consumer Chrome stable +/// 151.0.7922.72 on Windows 11, cross-checked against Chrome for Testing +/// 151.0.7922.71 on the same host. The two agree on everything except +/// `sec-ch-ua`, which Chrome for Testing cannot supply because it is unbranded; +/// that header is taken from the consumer build and is stable across launches. +/// +/// Relative to Chrome 142 the only wire-level TLS difference is that Chrome now +/// leads its signature_algorithms list with the three ML-DSA codepoints. +pub mod chrome_151 { + use super::*; + + /// Returns the complete Chrome 151 fingerprint + pub fn fingerprint() -> BrowserFingerprint { + BrowserFingerprint::new( + "Chrome", + "151", + tls_fingerprint(), + http2_fingerprint(), + headers(), + ) + } + + /// Chrome 151 TLS fingerprint + fn tls_fingerprint() -> TlsFingerprint { + TlsFingerprint::new( + // Cipher suites in Chrome 151 preference order + // GREASE cipher at position 1 (first) - same as Chrome 142 + vec![ + CipherSuite::Grease, + CipherSuite::TLS13_AES_128_GCM_SHA256, + CipherSuite::TLS13_AES_256_GCM_SHA384, + CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + 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 (includes post-quantum hybrid X25519MLKEM768) + // GREASE at position 1 (first) - same as Chrome 142 + vec![ + KeyExchangeGroup::Grease, + KeyExchangeGroup::X25519MLKEM768, + KeyExchangeGroup::X25519, + KeyExchangeGroup::Secp256r1, + KeyExchangeGroup::Secp384r1, + ], + // Signature algorithms + // Chrome leads with the three ML-DSA codepoints (0x0904/0x0905/0x0906); + // the remainder is unchanged from Chrome 142. + vec![ + SignatureAlgorithm::MlDsa44, + SignatureAlgorithm::MlDsa65, + SignatureAlgorithm::MlDsa87, + SignatureAlgorithm::EcdsaSecp256r1Sha256, + SignatureAlgorithm::RsaPssRsaSha256, + SignatureAlgorithm::RsaPkcs1Sha256, + SignatureAlgorithm::EcdsaSecp384r1Sha384, + SignatureAlgorithm::RsaPssRsaSha384, + SignatureAlgorithm::RsaPkcs1Sha384, + SignatureAlgorithm::RsaPssRsaSha512, + SignatureAlgorithm::RsaPkcs1Sha512, + ], + // TLS extensions configuration + // Chrome 151 uses new ALPS codepoint (17613) + 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::Brotli]), // compress_certificate + true, // application_settings + false, // delegated_credentials (Chrome doesn't use) + None, // record_size_limit (Chrome doesn't use) + // Extension order (critical for fingerprinting) + vec![ + ExtensionType::ServerName, + ExtensionType::ExtendedMasterSecret, + ExtensionType::SessionTicket, + ExtensionType::SignatureAlgorithms, + ExtensionType::StatusRequest, + ExtensionType::SupportedGroups, + ExtensionType::ApplicationLayerProtocolNegotiation, + ExtensionType::SignedCertificateTimestamp, + ExtensionType::KeyShare, + ExtensionType::PskKeyExchangeModes, + ExtensionType::SupportedVersions, + ExtensionType::CompressCertificate, + ExtensionType::ApplicationSettings, + ], + ) + .with_new_alps_codepoint(true), + // 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()], + ) + } + + /// Chrome 151 HTTP/2 fingerprint + fn http2_fingerprint() -> Http2Fingerprint { + Http2Fingerprint { + pseudo_header_order: vec![ + ":method".to_string(), + ":authority".to_string(), + ":scheme".to_string(), + ":path".to_string(), + ":protocol".to_string(), + ":status".to_string(), + ], + initial_stream_window_size: Some(6_291_456), + // Chrome's connection window is 15 MiB. This field is the window size, + // not the increment, so the emitted WINDOW_UPDATE is 15728640 - 65535 = + // 15663105, which is what the capture shows on the wire. + initial_connection_window_size: Some(15_728_640), + max_header_list_size: Some(262_144), + } + } + + /// Chrome 151 HTTP headers + fn headers() -> Vec<(String, String)> { + vec![ + ("sec-ch-ua".to_string(), "\"Not=A?Brand\";v=\"99\", \"Google Chrome\";v=\"151\", \"Chromium\";v=\"151\"".to_string()), + ("sec-ch-ua-mobile".to_string(), "?0".to_string()), + ("sec-ch-ua-platform".to_string(), "\"Windows\"".to_string()), + ("upgrade-insecure-requests".to_string(), "1".to_string()), + ("user-agent".to_string(), "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36".to_string()), + ("accept".to_string(), "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7".to_string()), + ("sec-fetch-site".to_string(), "none".to_string()), + ("sec-fetch-mode".to_string(), "navigate".to_string()), + ("sec-fetch-user".to_string(), "?1".to_string()), + ("sec-fetch-dest".to_string(), "document".to_string()), + ("accept-encoding".to_string(), "gzip, deflate, br, zstd".to_string()), + ("accept-language".to_string(), "en-US,en;q=0.9".to_string()), + ("priority".to_string(), "u=0, i".to_string()), + ] + } +} + /// Chrome 142 fingerprint module pub mod chrome_142 { use super::*; diff --git a/impit/src/fingerprint/mod.rs b/impit/src/fingerprint/mod.rs index 73a2ef0d..7b249263 100644 --- a/impit/src/fingerprint/mod.rs +++ b/impit/src/fingerprint/mod.rs @@ -346,6 +346,9 @@ impl TlsFingerprint { SignatureAlgorithm::RsaPkcs1Sha1 => FingerprintSignatureAlgorithm::RsaPkcs1Sha1, SignatureAlgorithm::Ed25519 => FingerprintSignatureAlgorithm::Ed25519, SignatureAlgorithm::Ed448 => FingerprintSignatureAlgorithm::Ed448, + SignatureAlgorithm::MlDsa44 => FingerprintSignatureAlgorithm::MlDsa44, + SignatureAlgorithm::MlDsa65 => FingerprintSignatureAlgorithm::MlDsa65, + SignatureAlgorithm::MlDsa87 => FingerprintSignatureAlgorithm::MlDsa87, SignatureAlgorithm::EcdsaSha1Legacy => { FingerprintSignatureAlgorithm::EcdsaSha1Legacy } diff --git a/impit/src/fingerprint/types.rs b/impit/src/fingerprint/types.rs index d552dae4..1b815ae7 100644 --- a/impit/src/fingerprint/types.rs +++ b/impit/src/fingerprint/types.rs @@ -72,6 +72,12 @@ pub enum SignatureAlgorithm { // EdDSA algorithms Ed25519, Ed448, + // ML-DSA algorithms (draft-ietf-tls-mldsa). Advertised in the ClientHello + // for fingerprint accuracy only: no ML-DSA verifier is available, so a + // server that actually selects one fails the handshake. + MlDsa44, + MlDsa65, + MlDsa87, // Legacy ECDSA with SHA-1 (for backwards compatibility) EcdsaSha1Legacy, }