Skip to content

Commit 1d5975e

Browse files
authored
Merge pull request #410 from 21pages/mobile_platform_verifier_fallback
Mobile platform verifier fallback
2 parents 5b2f391 + bbc8e2f commit 1d5975e

File tree

6 files changed

+246
-10
lines changed

6 files changed

+246
-10
lines changed

Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ tokio-rustls = { version = "0.26", features = [
5959
"tls12",
6060
"ring",
6161
], default-features = false }
62-
rustls-platform-verifier = "0.5"
6362
rustls-pki-types = "1.11"
6463
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots"] }
6564
tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots"] }
65+
rustls-native-certs = "0.8"
66+
webpki-roots = "1.0"
67+
68+
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
69+
rustls-platform-verifier = "0.6"
6670

6771
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
6872
tokio-native-tls = "0.3"

src/config.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::{
55
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
66
ops::{Deref, DerefMut},
77
path::{Path, PathBuf},
8-
sync::{Mutex, RwLock},
8+
sync::{atomic::AtomicBool, Mutex, RwLock},
99
time::{Duration, Instant, SystemTime},
1010
};
1111

@@ -72,6 +72,11 @@ lazy_static::lazy_static! {
7272
pub static ref BUILTIN_SETTINGS: RwLock<HashMap<String, String>> = Default::default();
7373
}
7474

75+
#[cfg(target_os = "android")]
76+
lazy_static::lazy_static! {
77+
pub static ref ANDROID_RUSTLS_PLATFORM_VERIFIER_INITIALIZED: AtomicBool = AtomicBool::new(false);
78+
}
79+
7580
lazy_static::lazy_static! {
7681
pub static ref APP_DIR: RwLock<String> = Default::default();
7782
}

src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,14 @@ pub use toml;
5757
pub use uuid;
5858
pub mod fingerprint;
5959
pub use flexi_logger;
60-
pub mod websocket;
6160
pub mod stream;
61+
pub mod websocket;
62+
#[cfg(any(target_os = "android", target_os = "ios"))]
63+
pub use rustls_platform_verifier;
6264
pub use stream::Stream;
6365
pub use whoami;
66+
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
67+
pub mod verifier;
6468

6569
pub type SessionID = uuid::Uuid;
6670

src/proxy.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ const MAXIMUM_RESPONSE_HEADERS: usize = 16;
5656
const DEFINE_TIME_OUT: u64 = 600;
5757

5858
pub trait IntoUrl {
59-
6059
// Besides parsing as a valid `Url`, the `Url` must be a valid
6160
// `http::Uri`, in that it makes sense to use in a network request.
6261
fn into_url(self) -> Result<Url, ProxyError>;
@@ -456,14 +455,14 @@ impl Proxy {
456455
T: IntoTargetAddr<'a>,
457456
{
458457
use std::convert::TryFrom;
459-
let verifier = rustls_platform_verifier::tls_config();
460-
let url_domain = self.intercept.get_domain()?;
461458

459+
let url_domain = self.intercept.get_domain()?;
462460
let domain = rustls_pki_types::ServerName::try_from(url_domain.as_str())
463461
.map_err(|e| ProxyError::AddressResolutionFailed(e.to_string()))?
464462
.to_owned();
465-
466-
let tls_connector = TlsConnector::from(std::sync::Arc::new(verifier));
463+
let client_config = crate::verifier::client_config()
464+
.map_err(|e| ProxyError::IoError(std::io::Error::other(e)))?;
465+
let tls_connector = TlsConnector::from(std::sync::Arc::new(client_config));
467466
let stream = tls_connector.connect(domain, io).await?;
468467
self.http_connect(stream, target).await
469468
}

src/verifier.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use crate::ResultType;
2+
#[cfg(any(target_os = "android", target_os = "ios"))]
3+
use rustls_pki_types::{ServerName, UnixTime};
4+
use std::sync::Arc;
5+
use tokio_rustls::rustls::{self, client::WebPkiServerVerifier, ClientConfig};
6+
#[cfg(any(target_os = "android", target_os = "ios"))]
7+
use tokio_rustls::rustls::{
8+
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
9+
DigitallySignedStruct, Error as TLSError, SignatureScheme,
10+
};
11+
12+
/// A certificate verifier that tries a primary verifier first,
13+
/// and falls back to a platform verifier if the primary fails.
14+
#[cfg(any(target_os = "android", target_os = "ios"))]
15+
#[derive(Debug)]
16+
struct FallbackPlatformVerifier {
17+
primary: Arc<dyn ServerCertVerifier>,
18+
fallback: Arc<dyn ServerCertVerifier>,
19+
}
20+
21+
#[cfg(any(target_os = "android", target_os = "ios"))]
22+
impl FallbackPlatformVerifier {
23+
fn with_platform_fallback(
24+
primary: Arc<dyn ServerCertVerifier>,
25+
provider: Arc<rustls::crypto::CryptoProvider>,
26+
) -> Result<Self, TLSError> {
27+
#[cfg(target_os = "android")]
28+
if !crate::config::ANDROID_RUSTLS_PLATFORM_VERIFIER_INITIALIZED
29+
.load(std::sync::atomic::Ordering::Relaxed)
30+
{
31+
return Err(TLSError::General(
32+
"rustls-platform-verifier not initialized".to_string(),
33+
));
34+
}
35+
let fallback = Arc::new(rustls_platform_verifier::Verifier::new(provider)?);
36+
Ok(Self { primary, fallback })
37+
}
38+
}
39+
40+
#[cfg(any(target_os = "android", target_os = "ios"))]
41+
impl ServerCertVerifier for FallbackPlatformVerifier {
42+
fn verify_server_cert(
43+
&self,
44+
end_entity: &rustls_pki_types::CertificateDer<'_>,
45+
intermediates: &[rustls_pki_types::CertificateDer<'_>],
46+
server_name: &ServerName<'_>,
47+
ocsp_response: &[u8],
48+
now: UnixTime,
49+
) -> Result<ServerCertVerified, TLSError> {
50+
match self.primary.verify_server_cert(
51+
end_entity,
52+
intermediates,
53+
server_name,
54+
ocsp_response,
55+
now,
56+
) {
57+
Ok(verified) => Ok(verified),
58+
Err(primary_err) => {
59+
match self.fallback.verify_server_cert(
60+
end_entity,
61+
intermediates,
62+
server_name,
63+
ocsp_response,
64+
now,
65+
) {
66+
Ok(verified) => Ok(verified),
67+
Err(fallback_err) => {
68+
log::error!(
69+
"Both primary and fallback verifiers failed to verify server certificate, primary error: {:?}, fallback error: {:?}",
70+
primary_err,
71+
fallback_err
72+
);
73+
Err(primary_err)
74+
}
75+
}
76+
}
77+
}
78+
}
79+
80+
fn verify_tls12_signature(
81+
&self,
82+
message: &[u8],
83+
cert: &rustls_pki_types::CertificateDer<'_>,
84+
dss: &DigitallySignedStruct,
85+
) -> Result<HandshakeSignatureValid, TLSError> {
86+
// Both WebPkiServerVerifier and rustls_platform_verifier use the same signature verification implementation.
87+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L278
88+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/crypto/mod.rs#L17
89+
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/android.rs#L9
90+
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/apple.rs#L6
91+
self.primary.verify_tls12_signature(message, cert, dss)
92+
}
93+
94+
fn verify_tls13_signature(
95+
&self,
96+
message: &[u8],
97+
cert: &rustls_pki_types::CertificateDer<'_>,
98+
dss: &DigitallySignedStruct,
99+
) -> Result<HandshakeSignatureValid, TLSError> {
100+
// Same implementation as verify_tls12_signature.
101+
self.primary.verify_tls13_signature(message, cert, dss)
102+
}
103+
104+
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
105+
// Both WebPkiServerVerifier and rustls_platform_verifier use the same crypto provider,
106+
// so their supported signature schemes are identical.
107+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L172C52-L172C85
108+
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/android.rs#L327
109+
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/apple.rs#L304
110+
self.primary.supported_verify_schemes()
111+
}
112+
}
113+
114+
fn webpki_server_verifier(
115+
provider: Arc<rustls::crypto::CryptoProvider>,
116+
) -> ResultType<Arc<WebPkiServerVerifier>> {
117+
// Load root certificates from both bundled webpki_roots and system-native certificate stores.
118+
// This approach is consistent with how reqwest and tokio-tungstenite handle root certificates.
119+
// https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L95
120+
// https://github.com/seanmonstar/reqwest/blob/b126ca49da7897e5d676639cdbf67a0f6838b586/src/async_impl/client.rs#L643
121+
let mut root_cert_store = rustls::RootCertStore::empty();
122+
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
123+
let rustls_native_certs::CertificateResult { certs, errors, .. } =
124+
rustls_native_certs::load_native_certs();
125+
if !errors.is_empty() {
126+
log::warn!("native root CA certificate loading errors: {errors:?}");
127+
}
128+
root_cert_store.add_parsable_certificates(certs);
129+
130+
// Build verifier using with_root_certificates behavior (WebPkiServerVerifier without CRLs).
131+
// Both reqwest and tokio-tungstenite use this approach.
132+
// https://github.com/seanmonstar/reqwest/blob/b126ca49da7897e5d676639cdbf67a0f6838b586/src/async_impl/client.rs#L749
133+
// https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L127
134+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/client/builder.rs#L47
135+
// with_root_certificates creates a WebPkiServerVerifier without revocation checking:
136+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L177
137+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L168
138+
// Since no CRL is provided (as is the case here), we must explicitly set allow_unknown_revocation_status()
139+
// to match the behavior of with_root_certificates, which allows unknown revocation status by default.
140+
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L37
141+
// Note: build() only returns an error if the root certificate store is empty, which won't happen here.
142+
let verifier = rustls::client::WebPkiServerVerifier::builder_with_provider(
143+
Arc::new(root_cert_store),
144+
provider.clone(),
145+
)
146+
.allow_unknown_revocation_status()
147+
.build()
148+
.map_err(|e| anyhow::anyhow!(e))?;
149+
Ok(verifier)
150+
}
151+
152+
pub fn client_config() -> ResultType<ClientConfig> {
153+
// Use the default builder which uses the default protocol versions and crypto provider.
154+
// The with_protocol_versions API has been removed in rustls master branch:
155+
// https://github.com/rustls/rustls/pull/2599
156+
// This approach is consistent with tokio-tungstenite's usage:
157+
// https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L126
158+
let config_builder = rustls::ClientConfig::builder();
159+
let provider = config_builder.crypto_provider().clone();
160+
let webpki_verifier = webpki_server_verifier(provider.clone())?;
161+
#[cfg(any(target_os = "android", target_os = "ios"))]
162+
{
163+
match FallbackPlatformVerifier::with_platform_fallback(webpki_verifier.clone(), provider) {
164+
Ok(fallback_verifier) => {
165+
let config = config_builder
166+
.dangerous()
167+
.with_custom_certificate_verifier(Arc::new(fallback_verifier))
168+
.with_no_client_auth();
169+
Ok(config)
170+
}
171+
Err(e) => {
172+
log::error!(
173+
"Failed to create fallback verifier: {:?}, use webpki verifier instead",
174+
e
175+
);
176+
let config = config_builder
177+
.with_webpki_verifier(webpki_verifier)
178+
.with_no_client_auth();
179+
Ok(config)
180+
}
181+
}
182+
}
183+
#[cfg(not(any(target_os = "android", target_os = "ios")))]
184+
{
185+
let config = config_builder
186+
.with_webpki_verifier(webpki_verifier)
187+
.with_no_client_auth();
188+
Ok(config)
189+
}
190+
}

src/websocket.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,42 @@ impl WsFramedStream {
4343
.into_client_request()
4444
.map_err(|e| Error::new(ErrorKind::Other, e))?;
4545

46-
let (stream, _) =
47-
timeout(Duration::from_millis(ms_timeout), connect_async(request)).await??;
46+
let stream;
47+
#[cfg(any(target_os = "android", target_os = "ios"))]
48+
{
49+
let is_wss = url_str.starts_with("wss://");
50+
if is_wss {
51+
use std::sync::Arc;
52+
use tokio_tungstenite::{connect_async_tls_with_config, Connector};
53+
54+
let connector = match crate::verifier::client_config() {
55+
Ok(client_config) => Some(Connector::Rustls(Arc::new(client_config))),
56+
Err(e) => {
57+
log::warn!(
58+
"Failed to get client config: {:?}, fallback to default connector",
59+
e
60+
);
61+
None
62+
}
63+
};
64+
let (s, _) = timeout(
65+
Duration::from_millis(ms_timeout),
66+
connect_async_tls_with_config(request, None, false, connector),
67+
)
68+
.await??;
69+
stream = s;
70+
} else {
71+
let (s, _) =
72+
timeout(Duration::from_millis(ms_timeout), connect_async(request)).await??;
73+
stream = s;
74+
}
75+
}
76+
#[cfg(not(any(target_os = "android", target_os = "ios")))]
77+
{
78+
let (s, _) =
79+
timeout(Duration::from_millis(ms_timeout), connect_async(request)).await??;
80+
stream = s;
81+
}
4882

4983
let addr = match stream.get_ref() {
5084
MaybeTlsStream::Plain(tcp) => tcp.peer_addr()?,

0 commit comments

Comments
 (0)