Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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="d0a6a3eb5526176bd2e0a366f4f1b83598e2cd83" }
rustls = { git = "https://github.com/apify/rustls", rev="1ebb6d466a557858cdd8c836ffcbb26d04d7a9f9" }
tower-http = { git = "https://github.com/apify/tower-http", rev="f9efc0d9193e774d33aedc1022b922efefc22052" }
hyper-util = { git = "https://github.com/apify/hyper-util", rev="9b7795dfd7158fc55e7c84b65bf1dae1d2dea67d" }

Expand Down
3 changes: 2 additions & 1 deletion impit-node/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ export type Browser = 'chrome'|
'okhttp'|
'okhttp3'|
'okhttp4'|
'okhttp5';
'okhttp5'|
'ios18';

export type HttpMethod = 'GET'|
'POST'|
Expand Down
2 changes: 2 additions & 0 deletions impit-node/src/impit_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum Browser {
OkHttp3,
OkHttp4,
OkHttp5,
Ios18,
}

/// Options for configuring an {@link Impit} instance.
Expand Down Expand Up @@ -143,6 +144,7 @@ impl From<Browser> for BrowserFingerprint {
Browser::OkHttp3 => impit::fingerprint::database::okhttp3::fingerprint(),
Browser::OkHttp | Browser::OkHttp4 => impit::fingerprint::database::okhttp4::fingerprint(),
Browser::OkHttp5 => impit::fingerprint::database::okhttp5::fingerprint(),
Browser::Ios18 => impit::fingerprint::database::ios_18::fingerprint(),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion impit-python/python/impit/impit.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ from collections.abc import Iterator, AsyncIterator
from contextlib import AbstractAsyncContextManager, AbstractContextManager


Browser = Literal['chrome', 'firefox', 'chrome125', 'chrome100', 'chrome101', 'chrome104', 'chrome107', 'chrome110', 'chrome116', 'chrome131', 'chrome136', 'chrome142', 'firefox128', 'firefox133', 'firefox135', 'firefox144']
Browser = Literal['chrome', 'firefox', 'chrome125', 'chrome100', 'chrome101', 'chrome104', 'chrome107', 'chrome110', 'chrome116', 'chrome131', 'chrome136', 'chrome142', 'firefox128', 'firefox133', 'firefox135', 'firefox144', 'ios18']

USE_CLIENT_DEFAULT: str
"""Sentinel that, when passed as a per-request ``timeout``, causes the client-level default timeout to be used.
Expand Down
2 changes: 2 additions & 0 deletions impit-python/src/async_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ impl AsyncClient {
.with_fingerprint(impit::fingerprint::database::okhttp4::fingerprint()),
"okhttp5" => builder
.with_fingerprint(impit::fingerprint::database::okhttp5::fingerprint()),
"ios18" => builder
.with_fingerprint(impit::fingerprint::database::ios_18::fingerprint()),
_ => {
return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
"Unsupported browser",
Expand Down
2 changes: 2 additions & 0 deletions impit-python/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ impl Client {
.with_fingerprint(impit::fingerprint::database::okhttp4::fingerprint()),
"okhttp5" => builder
.with_fingerprint(impit::fingerprint::database::okhttp5::fingerprint()),
"ios18" => builder
.with_fingerprint(impit::fingerprint::database::ios_18::fingerprint()),
_ => panic!("Unsupported browser"),
},
None => builder,
Expand Down
2 changes: 2 additions & 0 deletions impit/src/fingerprint/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
mod chrome;
mod firefox;
mod okhttp;
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,
};
pub use firefox::{firefox_128, firefox_133, firefox_135, firefox_144};
pub use okhttp::{okhttp3, okhttp4, okhttp5};
pub use safari::ios_18;
159 changes: 159 additions & 0 deletions impit/src/fingerprint/database/safari.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//! iOS system TLS fingerprints
//!
//! On iOS, Apple's App Store policy forces every app (Safari, Chrome iOS,
//! Firefox iOS, Edge iOS, and any native app using `NSURLSession` /
//! `Network.framework` / `WKWebView`) to use the OS networking stack, so a
//! single iOS TLS profile transparently covers the entire iOS browser and
//! native-app ecosystem.
//!
//! Source: capture against <https://tls.peet.ws> from an iPhone running
//! iOS 18.7 (Safari 26.5). Verified identical to a Chrome iOS 148 capture on
//! the same iOS version (same JA3, JA4, peetprint, and Akamai HTTP/2
//! fingerprints), confirming the fingerprint is the OS stack rather than the
//! browser.

use crate::fingerprint::*;

/// iOS 18 system TLS fingerprint module
pub mod ios_18 {
use super::*;

pub fn fingerprint() -> BrowserFingerprint {
BrowserFingerprint::new(
"Safari",
"iOS 18",
tls_fingerprint(),
http2_fingerprint(),
headers(),
)
}

fn tls_fingerprint() -> TlsFingerprint {
TlsFingerprint::new(
vec![
CipherSuite::Grease,
CipherSuite::TLS13_AES_256_GCM_SHA384,
CipherSuite::TLS13_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS13_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite::TLS_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite::TLS_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite::TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA,
],
vec![
KeyExchangeGroup::Grease,
KeyExchangeGroup::X25519MLKEM768,
KeyExchangeGroup::X25519,
KeyExchangeGroup::Secp256r1,
KeyExchangeGroup::Secp384r1,
KeyExchangeGroup::Secp521r1,
],
// The duplicate RsaPssRsaSha384 entry is intentional — every
// observed iOS capture sends this list with the duplicate at
// indexes 4 and 5.
vec![
SignatureAlgorithm::EcdsaSecp256r1Sha256,
SignatureAlgorithm::RsaPssRsaSha256,
SignatureAlgorithm::RsaPkcs1Sha256,
SignatureAlgorithm::EcdsaSecp384r1Sha384,
SignatureAlgorithm::RsaPssRsaSha384,
SignatureAlgorithm::RsaPssRsaSha384,
SignatureAlgorithm::RsaPkcs1Sha384,
SignatureAlgorithm::RsaPssRsaSha512,
SignatureAlgorithm::RsaPkcs1Sha512,
SignatureAlgorithm::RsaPkcs1Sha1,
],
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]), // compress_certificate
false, // application_settings
false, // delegated_credentials
None, // record_size_limit
vec![
ExtensionType::Grease,
ExtensionType::ServerName,
ExtensionType::ExtendedMasterSecret,
ExtensionType::RenegotiationInfo,
ExtensionType::SupportedGroups,
ExtensionType::EcPointFormats,
ExtensionType::ApplicationLayerProtocolNegotiation,
ExtensionType::StatusRequest,
ExtensionType::SignatureAlgorithms,
ExtensionType::SignedCertificateTimestamp,
ExtensionType::KeyShare,
ExtensionType::PskKeyExchangeModes,
ExtensionType::SupportedVersions,
ExtensionType::CompressCertificate,
ExtensionType::Grease,
],
)
.with_session_ticket(false),
None,
vec![b"h2".to_vec(), b"http/1.1".to_vec()],
)
}

fn http2_fingerprint() -> Http2Fingerprint {
Http2Fingerprint {
// iOS sends :method :scheme :authority :path on requests.
// :protocol (extended CONNECT) and :status (response) are
// required by the impit h2 fork to be in the order list even
// when not used on a given message.
pseudo_header_order: vec![
":method".to_string(),
":scheme".to_string(),
":authority".to_string(),
":path".to_string(),
":protocol".to_string(),
":status".to_string(),
],
initial_stream_window_size: Some(2_097_152),
// 65_535 (h2 default) + 10_420_225 WINDOW_UPDATE = 10_485_760.
initial_connection_window_size: Some(10_485_760),
max_header_list_size: None,
}
}

fn headers() -> Vec<(String, String)> {
vec![
(
"user-agent".to_string(),
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.5 Mobile/15E148 Safari/604.1".to_string(),
),
(
"accept".to_string(),
"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8".to_string(),
),
("sec-fetch-site".to_string(), "none".to_string()),
("sec-fetch-mode".to_string(), "navigate".to_string()),
("sec-fetch-dest".to_string(), "document".to_string()),
("accept-language".to_string(), "en-US,en;q=0.9".to_string()),
("priority".to_string(), "u=0, i".to_string()),
(
"accept-encoding".to_string(),
"gzip, deflate, br, zstd".to_string(),
),
]
}
}
9 changes: 9 additions & 0 deletions impit/src/fingerprint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,15 @@ impl TlsFingerprint {
CipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA => {
FingerprintCipherSuite::TLS_RSA_WITH_AES_256_CBC_SHA
}
CipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA => {
FingerprintCipherSuite::TLS_RSA_WITH_3DES_EDE_CBC_SHA
}
CipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA => {
FingerprintCipherSuite::TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
}
CipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA => {
FingerprintCipherSuite::TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
}
CipherSuite::Grease => FingerprintCipherSuite::Grease,
})
.collect();
Expand Down
5 changes: 5 additions & 0 deletions impit/src/fingerprint/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ pub enum CipherSuite {
TLS_RSA_WITH_AES_256_GCM_SHA384,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_256_CBC_SHA,
// Legacy 3DES suites: advertised in ClientHello for fingerprint
// accuracy only. Never actually negotiated (aws-lc-rs has no 3DES).
TLS_RSA_WITH_3DES_EDE_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
/// GREASE cipher suite for fingerprinting
Grease,
}
Expand Down
Loading