mirror of
https://github.com/rustdesk/hbb_common.git
synced 2026-02-16 02:20:43 +00:00
add mobile fallback platform verifier
Signed-off-by: 21pages <sunboeasy@gmail.com>
This commit is contained in:
@@ -59,10 +59,14 @@ tokio-rustls = { version = "0.26", features = [
|
|||||||
"tls12",
|
"tls12",
|
||||||
"ring",
|
"ring",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
rustls-platform-verifier = "0.5"
|
|
||||||
rustls-pki-types = "1.11"
|
rustls-pki-types = "1.11"
|
||||||
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots"] }
|
tokio-tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots"] }
|
||||||
tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots"] }
|
tungstenite = { version = "0.26", features = ["rustls-tls-native-roots", "rustls-tls-webpki-roots"] }
|
||||||
|
rustls-native-certs = "0.8"
|
||||||
|
webpki-roots = "1.0"
|
||||||
|
|
||||||
|
[target.'cfg(any(target_os = "android", target_os = "ios"))'.dependencies]
|
||||||
|
rustls-platform-verifier = "0.6"
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
[target.'cfg(any(target_os = "macos", target_os = "windows"))'.dependencies]
|
||||||
tokio-native-tls = "0.3"
|
tokio-native-tls = "0.3"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::{
|
|||||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Mutex, RwLock},
|
sync::{atomic::AtomicBool, Mutex, RwLock},
|
||||||
time::{Duration, Instant, SystemTime},
|
time::{Duration, Instant, SystemTime},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,6 +72,11 @@ lazy_static::lazy_static! {
|
|||||||
pub static ref BUILTIN_SETTINGS: RwLock<HashMap<String, String>> = Default::default();
|
pub static ref BUILTIN_SETTINGS: RwLock<HashMap<String, String>> = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
pub static ref ANDROID_RUSTLS_PLATFORM_VERIFIER_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||||
|
}
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref APP_DIR: RwLock<String> = Default::default();
|
pub static ref APP_DIR: RwLock<String> = Default::default();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,10 +57,14 @@ pub use toml;
|
|||||||
pub use uuid;
|
pub use uuid;
|
||||||
pub mod fingerprint;
|
pub mod fingerprint;
|
||||||
pub use flexi_logger;
|
pub use flexi_logger;
|
||||||
pub mod websocket;
|
|
||||||
pub mod stream;
|
pub mod stream;
|
||||||
|
pub mod websocket;
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
pub use rustls_platform_verifier;
|
||||||
pub use stream::Stream;
|
pub use stream::Stream;
|
||||||
pub use whoami;
|
pub use whoami;
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||||
|
pub mod verifier;
|
||||||
|
|
||||||
pub type SessionID = uuid::Uuid;
|
pub type SessionID = uuid::Uuid;
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ const MAXIMUM_RESPONSE_HEADERS: usize = 16;
|
|||||||
const DEFINE_TIME_OUT: u64 = 600;
|
const DEFINE_TIME_OUT: u64 = 600;
|
||||||
|
|
||||||
pub trait IntoUrl {
|
pub trait IntoUrl {
|
||||||
|
|
||||||
// Besides parsing as a valid `Url`, the `Url` must be a valid
|
// Besides parsing as a valid `Url`, the `Url` must be a valid
|
||||||
// `http::Uri`, in that it makes sense to use in a network request.
|
// `http::Uri`, in that it makes sense to use in a network request.
|
||||||
fn into_url(self) -> Result<Url, ProxyError>;
|
fn into_url(self) -> Result<Url, ProxyError>;
|
||||||
@@ -456,14 +455,14 @@ impl Proxy {
|
|||||||
T: IntoTargetAddr<'a>,
|
T: IntoTargetAddr<'a>,
|
||||||
{
|
{
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
let verifier = rustls_platform_verifier::tls_config();
|
|
||||||
let url_domain = self.intercept.get_domain()?;
|
|
||||||
|
|
||||||
|
let url_domain = self.intercept.get_domain()?;
|
||||||
let domain = rustls_pki_types::ServerName::try_from(url_domain.as_str())
|
let domain = rustls_pki_types::ServerName::try_from(url_domain.as_str())
|
||||||
.map_err(|e| ProxyError::AddressResolutionFailed(e.to_string()))?
|
.map_err(|e| ProxyError::AddressResolutionFailed(e.to_string()))?
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
let client_config = crate::verifier::client_config()
|
||||||
let tls_connector = TlsConnector::from(std::sync::Arc::new(verifier));
|
.map_err(|e| ProxyError::IoError(std::io::Error::other(e)))?;
|
||||||
|
let tls_connector = TlsConnector::from(std::sync::Arc::new(client_config));
|
||||||
let stream = tls_connector.connect(domain, io).await?;
|
let stream = tls_connector.connect(domain, io).await?;
|
||||||
self.http_connect(stream, target).await
|
self.http_connect(stream, target).await
|
||||||
}
|
}
|
||||||
|
|||||||
190
src/verifier.rs
Normal file
190
src/verifier.rs
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
use crate::ResultType;
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
use rustls_pki_types::{ServerName, UnixTime};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio_rustls::rustls::{self, client::WebPkiServerVerifier, ClientConfig};
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
use tokio_rustls::rustls::{
|
||||||
|
client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier},
|
||||||
|
DigitallySignedStruct, Error as TLSError, SignatureScheme,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A certificate verifier that tries a primary verifier first,
|
||||||
|
/// and falls back to a platform verifier if the primary fails.
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct FallbackPlatformVerifier {
|
||||||
|
primary: Arc<dyn ServerCertVerifier>,
|
||||||
|
fallback: Arc<dyn ServerCertVerifier>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
impl FallbackPlatformVerifier {
|
||||||
|
fn with_platform_fallback(
|
||||||
|
primary: Arc<dyn ServerCertVerifier>,
|
||||||
|
provider: Arc<rustls::crypto::CryptoProvider>,
|
||||||
|
) -> Result<Self, TLSError> {
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
if !crate::config::ANDROID_RUSTLS_PLATFORM_VERIFIER_INITIALIZED
|
||||||
|
.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
{
|
||||||
|
return Err(TLSError::General(
|
||||||
|
"rustls-platform-verifier not initialized".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let fallback = Arc::new(rustls_platform_verifier::Verifier::new(provider)?);
|
||||||
|
Ok(Self { primary, fallback })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
impl ServerCertVerifier for FallbackPlatformVerifier {
|
||||||
|
fn verify_server_cert(
|
||||||
|
&self,
|
||||||
|
end_entity: &rustls_pki_types::CertificateDer<'_>,
|
||||||
|
intermediates: &[rustls_pki_types::CertificateDer<'_>],
|
||||||
|
server_name: &ServerName<'_>,
|
||||||
|
ocsp_response: &[u8],
|
||||||
|
now: UnixTime,
|
||||||
|
) -> Result<ServerCertVerified, TLSError> {
|
||||||
|
match self.primary.verify_server_cert(
|
||||||
|
end_entity,
|
||||||
|
intermediates,
|
||||||
|
server_name,
|
||||||
|
ocsp_response,
|
||||||
|
now,
|
||||||
|
) {
|
||||||
|
Ok(verified) => Ok(verified),
|
||||||
|
Err(primary_err) => {
|
||||||
|
match self.fallback.verify_server_cert(
|
||||||
|
end_entity,
|
||||||
|
intermediates,
|
||||||
|
server_name,
|
||||||
|
ocsp_response,
|
||||||
|
now,
|
||||||
|
) {
|
||||||
|
Ok(verified) => Ok(verified),
|
||||||
|
Err(fallback_err) => {
|
||||||
|
log::error!(
|
||||||
|
"Both primary and fallback verifiers failed to verify server certificate, primary error: {:?}, fallback error: {:?}",
|
||||||
|
primary_err,
|
||||||
|
fallback_err
|
||||||
|
);
|
||||||
|
Err(primary_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls12_signature(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
cert: &rustls_pki_types::CertificateDer<'_>,
|
||||||
|
dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, TLSError> {
|
||||||
|
// Both WebPkiServerVerifier and rustls_platform_verifier use the same signature verification implementation.
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L278
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/crypto/mod.rs#L17
|
||||||
|
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/android.rs#L9
|
||||||
|
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/apple.rs#L6
|
||||||
|
self.primary.verify_tls12_signature(message, cert, dss)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_tls13_signature(
|
||||||
|
&self,
|
||||||
|
message: &[u8],
|
||||||
|
cert: &rustls_pki_types::CertificateDer<'_>,
|
||||||
|
dss: &DigitallySignedStruct,
|
||||||
|
) -> Result<HandshakeSignatureValid, TLSError> {
|
||||||
|
// Same implementation as verify_tls12_signature.
|
||||||
|
self.primary.verify_tls13_signature(message, cert, dss)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
|
||||||
|
// Both WebPkiServerVerifier and rustls_platform_verifier use the same crypto provider,
|
||||||
|
// so their supported signature schemes are identical.
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L172C52-L172C85
|
||||||
|
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/android.rs#L327
|
||||||
|
// https://github.com/rustls/rustls-platform-verifier/blob/1099f161bfc5e3ac7f90aad88b1bf788e72906cb/rustls-platform-verifier/src/verification/apple.rs#L304
|
||||||
|
self.primary.supported_verify_schemes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn webpki_server_verifier(
|
||||||
|
provider: Arc<rustls::crypto::CryptoProvider>,
|
||||||
|
) -> ResultType<Arc<WebPkiServerVerifier>> {
|
||||||
|
// Load root certificates from both bundled webpki_roots and system-native certificate stores.
|
||||||
|
// This approach is consistent with how reqwest and tokio-tungstenite handle root certificates.
|
||||||
|
// https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L95
|
||||||
|
// https://github.com/seanmonstar/reqwest/blob/b126ca49da7897e5d676639cdbf67a0f6838b586/src/async_impl/client.rs#L643
|
||||||
|
let mut root_cert_store = rustls::RootCertStore::empty();
|
||||||
|
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||||
|
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||||
|
rustls_native_certs::load_native_certs();
|
||||||
|
if !errors.is_empty() {
|
||||||
|
log::warn!("native root CA certificate loading errors: {errors:?}");
|
||||||
|
}
|
||||||
|
root_cert_store.add_parsable_certificates(certs);
|
||||||
|
|
||||||
|
// Build verifier using with_root_certificates behavior (WebPkiServerVerifier without CRLs).
|
||||||
|
// Both reqwest and tokio-tungstenite use this approach.
|
||||||
|
// https://github.com/seanmonstar/reqwest/blob/b126ca49da7897e5d676639cdbf67a0f6838b586/src/async_impl/client.rs#L749
|
||||||
|
// https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L127
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/client/builder.rs#L47
|
||||||
|
// with_root_certificates creates a WebPkiServerVerifier without revocation checking:
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L177
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L168
|
||||||
|
// Since no CRL is provided (as is the case here), we must explicitly set allow_unknown_revocation_status()
|
||||||
|
// to match the behavior of with_root_certificates, which allows unknown revocation status by default.
|
||||||
|
// https://github.com/rustls/rustls/blob/1ee126adb3352a2dcd72420dcd6040351a6ddc1e/rustls/src/webpki/server_verifier.rs#L37
|
||||||
|
// Note: build() only returns an error if the root certificate store is empty, which won't happen here.
|
||||||
|
let verifier = rustls::client::WebPkiServerVerifier::builder_with_provider(
|
||||||
|
Arc::new(root_cert_store),
|
||||||
|
provider.clone(),
|
||||||
|
)
|
||||||
|
.allow_unknown_revocation_status()
|
||||||
|
.build()
|
||||||
|
.map_err(|e| anyhow::anyhow!(e))?;
|
||||||
|
Ok(verifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn client_config() -> ResultType<ClientConfig> {
|
||||||
|
// Use the default builder which uses the default protocol versions and crypto provider.
|
||||||
|
// The with_protocol_versions API has been removed in rustls master branch:
|
||||||
|
// https://github.com/rustls/rustls/pull/2599
|
||||||
|
// This approach is consistent with tokio-tungstenite's usage:
|
||||||
|
// https://github.com/snapview/tokio-tungstenite/blob/35d110c24c9d030d1608ec964d70c789dfb27452/src/tls.rs#L126
|
||||||
|
let config_builder = rustls::ClientConfig::builder();
|
||||||
|
let provider = config_builder.crypto_provider().clone();
|
||||||
|
let webpki_verifier = webpki_server_verifier(provider.clone())?;
|
||||||
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
{
|
||||||
|
match FallbackPlatformVerifier::with_platform_fallback(webpki_verifier.clone(), provider) {
|
||||||
|
Ok(fallback_verifier) => {
|
||||||
|
let config = config_builder
|
||||||
|
.dangerous()
|
||||||
|
.with_custom_certificate_verifier(Arc::new(fallback_verifier))
|
||||||
|
.with_no_client_auth();
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to create fallback verifier: {:?}, use webpki verifier instead",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
let config = config_builder
|
||||||
|
.with_webpki_verifier(webpki_verifier)
|
||||||
|
.with_no_client_auth();
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
let config = config_builder
|
||||||
|
.with_webpki_verifier(webpki_verifier)
|
||||||
|
.with_no_client_auth();
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,8 +43,42 @@ impl WsFramedStream {
|
|||||||
.into_client_request()
|
.into_client_request()
|
||||||
.map_err(|e| Error::new(ErrorKind::Other, e))?;
|
.map_err(|e| Error::new(ErrorKind::Other, e))?;
|
||||||
|
|
||||||
let (stream, _) =
|
let stream;
|
||||||
timeout(Duration::from_millis(ms_timeout), connect_async(request)).await??;
|
#[cfg(any(target_os = "android", target_os = "ios"))]
|
||||||
|
{
|
||||||
|
let is_wss = url_str.starts_with("wss://");
|
||||||
|
if is_wss {
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio_tungstenite::{connect_async_tls_with_config, Connector};
|
||||||
|
|
||||||
|
let connector = match crate::verifier::client_config() {
|
||||||
|
Ok(client_config) => Some(Connector::Rustls(Arc::new(client_config))),
|
||||||
|
Err(e) => {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to get client config: {:?}, fallback to default connector",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let (s, _) = timeout(
|
||||||
|
Duration::from_millis(ms_timeout),
|
||||||
|
connect_async_tls_with_config(request, None, false, connector),
|
||||||
|
)
|
||||||
|
.await??;
|
||||||
|
stream = s;
|
||||||
|
} else {
|
||||||
|
let (s, _) =
|
||||||
|
timeout(Duration::from_millis(ms_timeout), connect_async(request)).await??;
|
||||||
|
stream = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "android", target_os = "ios")))]
|
||||||
|
{
|
||||||
|
let (s, _) =
|
||||||
|
timeout(Duration::from_millis(ms_timeout), connect_async(request)).await??;
|
||||||
|
stream = s;
|
||||||
|
}
|
||||||
|
|
||||||
let addr = match stream.get_ref() {
|
let addr = match stream.get_ref() {
|
||||||
MaybeTlsStream::Plain(tcp) => tcp.peer_addr()?,
|
MaybeTlsStream::Plain(tcp) => tcp.peer_addr()?,
|
||||||
|
|||||||
Reference in New Issue
Block a user