From d106d97b996117d0f0ec4065d3e595a66c841174 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 30 Oct 2025 13:59:00 +0800 Subject: [PATCH] mobile verify both webpki and installed CA (#13272) Signed-off-by: 21pages --- Cargo.lock | 34 ++++++++-------- flutter/android/app/build.gradle | 39 +++++++++++++++++-- flutter/android/app/proguard-rules | 5 ++- .../android/app/src/main/AndroidManifest.xml | 1 + .../flutter_hbb/RustDeskApplication.kt | 17 ++++++++ flutter/android/app/src/main/kotlin/ffi.kt | 1 + libs/hbb_common | 2 +- libs/scrap/src/android/ffi.rs | 38 ++++++++++++++++++ src/hbbs_http/http_client.rs | 30 +++++++++----- 9 files changed, 137 insertions(+), 30 deletions(-) create mode 100644 flutter/android/app/src/main/kotlin/com/carriez/flutter_hbb/RustDeskApplication.kt diff --git a/Cargo.lock b/Cargo.lock index 352db2399..fd55bd78c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3345,6 +3345,7 @@ dependencies = [ "protobuf-codegen", "rand 0.8.5", "regex", + "rustls-native-certs", "rustls-pki-types", "rustls-platform-verifier", "serde 1.0.203", @@ -3365,6 +3366,7 @@ dependencies = [ "tungstenite", "url", "uuid", + "webpki-roots 1.0.0", "whoami", "winapi 0.3.9", "zstd 0.13.1", @@ -6697,9 +6699,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "log", "once_cell", @@ -6719,7 +6721,7 @@ dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.2.0", + "security-framework 3.5.1", ] [[package]] @@ -6742,9 +6744,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.5.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5467026f437b4cb2a533865eaa73eb840019a0916f4b9ec563c6e617e086c9" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys 0.8.7", @@ -6755,7 +6757,7 @@ dependencies = [ "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki", - "security-framework 3.2.0", + "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", "windows-sys 0.52.0", @@ -6763,15 +6765,15 @@ dependencies = [ [[package]] name = "rustls-platform-verifier-android" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84e217e7fdc8466b5b35d30f8c0a30febd29173df4a3a0c2115d306b9c4117ad" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -6901,9 +6903,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.2.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ "bitflags 2.9.1", "core-foundation 0.10.1", @@ -6914,9 +6916,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys 0.8.7", "libc", @@ -8846,9 +8848,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "0.26.8" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" +checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" dependencies = [ "rustls-pki-types", ] diff --git a/flutter/android/app/build.gradle b/flutter/android/app/build.gradle index c55425165..830cbc2dd 100644 --- a/flutter/android/app/build.gradle +++ b/flutter/android/app/build.gradle @@ -1,4 +1,6 @@ import com.google.protobuf.gradle.* +import groovy.json.JsonSlurper + plugins { id "com.google.protobuf" version "0.9.4" id "com.android.application" @@ -30,8 +32,37 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -dependencies { - implementation 'com.google.protobuf:protobuf-javalite:3.20.1' +// Add rustls-platform-verifier Android support +String findRustlsPlatformVerifierMavenDir() { + def dependencyText = providers.exec { + it.workingDir = new File("../..") + commandLine("cargo", "metadata", "--format-version", "1") + }.standardOutput.asText.get() + + def dependencyJson = new JsonSlurper().parseText(dependencyText) + def pkg = dependencyJson.packages.find { it.name == "rustls-platform-verifier-android" } + + if (pkg == null) { + throw new GradleException("rustls-platform-verifier-android package not found in cargo metadata!") + } + + def manifestPath = file(pkg.manifest_path) + def mavenDir = new File(manifestPath.parentFile, "maven") + + if (!mavenDir.exists()) { + throw new GradleException("Maven directory not found at: ${mavenDir.path}") + } + + println("✓ Found rustls-platform-verifier maven repo at: ${mavenDir.path}") + return mavenDir.path +} + + +repositories { + maven { + url = findRustlsPlatformVerifierMavenDir() + metadataSources.artifact() + } } protobuf { @@ -67,7 +98,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.carriez.flutter_hbb" - minSdkVersion 21 + minSdkVersion 22 targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -97,8 +128,10 @@ flutter { } dependencies { + implementation 'com.google.protobuf:protobuf-javalite:3.20.1' implementation "androidx.media:media:1.6.0" implementation 'com.github.getActivity:XXPermissions:18.5' implementation("org.jetbrains.kotlin:kotlin-stdlib") { version { strictly("1.9.10") } } implementation 'com.caverock:androidsvg-aar:1.4' + implementation "rustls:rustls-platform-verifier:0.1.1" } diff --git a/flutter/android/app/proguard-rules b/flutter/android/app/proguard-rules index 0b12a6cda..517402567 100644 --- a/flutter/android/app/proguard-rules +++ b/flutter/android/app/proguard-rules @@ -1,4 +1,7 @@ # Keep class members from protobuf generated code. -keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite { ; -} \ No newline at end of file +} + +# Keep rustls-platform-verifier classes for JNI +-keep, includedescriptorclasses class org.rustls.platformverifier.** { *; } \ No newline at end of file diff --git a/flutter/android/app/src/main/AndroidManifest.xml b/flutter/android/app/src/main/AndroidManifest.xml index 47533612b..9986208fa 100644 --- a/flutter/android/app/src/main/AndroidManifest.xml +++ b/flutter/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ > = RwLock::new(None); static ref MAIN_SERVICE_CTX: RwLock> = RwLock::new(None); // MainService -> video service / audio service / info + static ref APPLICATION_CONTEXT: RwLock> = RwLock::new(None); static ref VIDEO_RAW: Mutex = Mutex::new(FrameRaw::new("video", MAX_VIDEO_FRAME_TIMEOUT)); static ref AUDIO_RAW: Mutex = Mutex::new(FrameRaw::new("audio", MAX_AUDIO_FRAME_TIMEOUT)); static ref NDK_CONTEXT_INITED: Mutex = Default::default(); @@ -462,6 +463,23 @@ fn init_ndk_context(java_vm: *mut c_void, context_jobject: *mut c_void) { *lock = true; } +fn try_init_rustls_platform_verifier(env: &mut JNIEnv, context_jobject: *mut c_void) { + use hbb_common::config::ANDROID_RUSTLS_PLATFORM_VERIFIER_INITIALIZED as INITIALIZED; + use std::sync::atomic::Ordering; + let initialized = INITIALIZED.load(Ordering::Relaxed); + if !initialized { + let ctx_for_rustls = unsafe { JObject::from_raw(context_jobject as jni::sys::jobject) }; + if let Err(e) = + hbb_common::rustls_platform_verifier::android::init_hosted(env, ctx_for_rustls) + { + log::error!("Failed to initialize rustls-platform-verifier: {:?}", e); + } else { + INITIALIZED.store(true, Ordering::Relaxed); + log::info!("rustls-platform-verifier initialized successfully"); + } + } +} + // https://cjycode.com/flutter_rust_bridge/guides/how-to/ndk-init #[no_mangle] pub extern "C" fn JNI_OnLoad(vm: jni::JavaVM, res: *mut std::os::raw::c_void) -> jni::sys::jint { @@ -471,3 +489,23 @@ pub extern "C" fn JNI_OnLoad(vm: jni::JavaVM, res: *mut std::os::raw::c_void) -> } jni::JNIVersion::V6.into() } + +#[no_mangle] +pub extern "system" fn Java_ffi_FFI_onAppStart(mut env: JNIEnv, _class: JClass, ctx: JObject) { + if ctx.is_null() { + log::error!("application context is null"); + return; + } + if APPLICATION_CONTEXT.read().unwrap().is_some() { + log::info!("application context already initialized"); + return; + } + if let Ok(jvm) = env.get_java_vm() { + if let Ok(context) = env.new_global_ref(ctx) { + let java_vm = jvm.get_java_vm_pointer() as *mut c_void; + let context_jobject = context.as_obj().as_raw() as *mut c_void; + *APPLICATION_CONTEXT.write().unwrap() = Some(context); + try_init_rustls_platform_verifier(&mut env, context_jobject); + } + } +} diff --git a/src/hbbs_http/http_client.rs b/src/hbbs_http/http_client.rs index 8d6f529b7..a9eb67c29 100644 --- a/src/hbbs_http/http_client.rs +++ b/src/hbbs_http/http_client.rs @@ -9,15 +9,30 @@ macro_rules! configure_http_client { // https://github.com/rustdesk/rustdesk/issues/11569 // https://docs.rs/reqwest/latest/reqwest/struct.ClientBuilder.html#method.no_proxy let mut builder = $builder.no_proxy(); + #[cfg(any(target_os = "android", target_os = "ios"))] + match hbb_common::verifier::client_config() { + Ok(client_config) => { + builder = builder.use_preconfigured_tls(client_config); + } + Err(e) => { + hbb_common::log::error!("Failed to get client config: {}", e); + } + } let client = if let Some(conf) = Config::get_socks() { let proxy_result = Proxy::from_conf(&conf, None); match proxy_result { Ok(proxy) => { let proxy_setup = match &proxy.intercept { - ProxyScheme::Http { host, .. } =>{ reqwest::Proxy::all(format!("http://{}", host))}, - ProxyScheme::Https { host, .. } => {reqwest::Proxy::all(format!("https://{}", host))}, - ProxyScheme::Socks5 { addr, .. } => { reqwest::Proxy::all(&format!("socks5://{}", addr)) } + ProxyScheme::Http { host, .. } => { + reqwest::Proxy::all(format!("http://{}", host)) + } + ProxyScheme::Https { host, .. } => { + reqwest::Proxy::all(format!("https://{}", host)) + } + ProxyScheme::Socks5 { addr, .. } => { + reqwest::Proxy::all(&format!("socks5://{}", addr)) + } }; match proxy_setup { @@ -28,12 +43,9 @@ macro_rules! configure_http_client { format!("Basic {}", auth.get_basic_authorization()); if let Ok(auth) = basic_auth.parse() { builder = builder.default_headers( - vec![( - reqwest::header::PROXY_AUTHORIZATION, - auth, - )] - .into_iter() - .collect(), + vec![(reqwest::header::PROXY_AUTHORIZATION, auth)] + .into_iter() + .collect(), ); } }