mirror of
https://github.com/flectar/mail.git
synced 2026-11-12 02:05:59 +00:00
chore: initialize Android TLS verification
This commit is contained in:
1 parent
19d449ca39
commit
2ee87d1e07
7 files changed
+99
-1
No files matched your search
Generated
+2
@@ -1803,6 +1803,8 @@ dependencies = [
|
||||
"base64 0.23.1",
|
||||
"flectar-mail",
|
||||
"jni 0.21.1",
|
||||
"jni 0.22.4",
|
||||
"rustls-platform-verifier",
|
||||
"serde_json",
|
||||
"slint",
|
||||
"tokio",
|
||||
|
||||
@@ -30,6 +30,8 @@ flectar-mail = { path = "../..", default-features = false, features = ["remote-c
|
||||
async-trait = "0.1"
|
||||
base64 = "0.23"
|
||||
jni = "0.21"
|
||||
jni-verifier = { package = "jni", version = "0.22" }
|
||||
rustls-platform-verifier = "0.7"
|
||||
serde_json = "1"
|
||||
slint = { version = "1.18.1", default-features = false, features = [
|
||||
"std",
|
||||
|
||||
@@ -86,6 +86,9 @@ tasks.named("preBuild").configure {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("rustls:rustls-platform-verifier:0.1.1")
|
||||
// The verifier AAR does not declare its Kotlin runtime dependency.
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
|
||||
// AuthorizationClient is Google's supported API for authorizing an
|
||||
// Android application to Gmail and other Google user data.
|
||||
implementation("com.google.android.gms:play-services-auth:21.6.0")
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.flectar.mail;
|
||||
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Check that the release dependency includes the JVM API called through Rust JNI. */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public final class CertificateVerifierTest {
|
||||
@Test
|
||||
public void packagedVerifierRejectsMalformedCertificate() throws Exception {
|
||||
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
Class<?> verifier = context.getClassLoader()
|
||||
.loadClass("org.rustls.platformverifier.CertificateVerifier");
|
||||
Method verify = verifier.getDeclaredMethod("verifyCertificateChain", Context.class,
|
||||
String.class, String.class, String[].class, byte[].class, long.class, byte[][].class);
|
||||
verify.setAccessible(true);
|
||||
Object result = verify.invoke(null, context, "example.test", "RSA", new String[0],
|
||||
new byte[0], System.currentTimeMillis() / 1000, new byte[][]{new byte[]{0}});
|
||||
Field code = result.getClass().getDeclaredField("code");
|
||||
code.setAccessible(true);
|
||||
assertNotEquals("Malformed certificates must fail verification", 0, code.getInt(result));
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import groovy.json.JsonSlurper
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
@@ -11,6 +13,29 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
// Resolve the JVM bridge from the exact crate selected by Cargo.lock.
|
||||
// It is shipped with the crate rather than published to Maven Central.
|
||||
val metadata = providers.exec {
|
||||
workingDir(rootDir.parentFile)
|
||||
commandLine(
|
||||
"cargo", "metadata", "--locked", "--format-version", "1",
|
||||
"--filter-platform", "aarch64-linux-android", "--manifest-path", "Cargo.toml",
|
||||
)
|
||||
}.standardOutput.asText.get()
|
||||
val packages = (JsonSlurper().parseText(metadata) as Map<*, *>)["packages"] as List<*>
|
||||
val verifier = packages.map { it as Map<*, *> }
|
||||
.single { it["name"] == "rustls-platform-verifier-android" }
|
||||
check(verifier["version"] == "0.1.1") {
|
||||
"Update the Gradle verifier dependency to match Cargo.lock"
|
||||
}
|
||||
exclusiveContent {
|
||||
forRepository {
|
||||
maven {
|
||||
url = uri(File(File(verifier["manifest_path"] as String).parentFile, "maven"))
|
||||
}
|
||||
}
|
||||
filter { includeModule("rustls", "rustls-platform-verifier") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -111,10 +111,14 @@ fn app_cache_dir(app: &slint::android::AndroidApp) -> Result<std::path::PathBuf,
|
||||
#[unsafe(no_mangle)]
|
||||
fn android_main(app: slint::android::AndroidApp) {
|
||||
observability::install();
|
||||
// SAFETY: AndroidApp owns these live VM and Activity references.
|
||||
unsafe { tls::initialize(app.vm_as_ptr().cast(), app.activity_as_ptr().cast()) }
|
||||
.expect("failed to initialize Android TLS certificate verification");
|
||||
let internal = app
|
||||
.internal_data_path()
|
||||
.expect("Android did not provide an app-private data directory");
|
||||
let cache_dir = app_cache_dir(&app).expect("Android did not provide an app-private cache directory");
|
||||
let cache_dir =
|
||||
app_cache_dir(&app).expect("Android did not provide an app-private cache directory");
|
||||
tracing::info!(internal_data_path = %internal.display(), "resolved Android app-private root");
|
||||
let oauth_redirects = oauth::AndroidOAuthBroker::global(&app, &internal)
|
||||
.expect("failed to initialize Android OAuth services");
|
||||
@@ -140,6 +144,8 @@ fn android_main(app: slint::android::AndroidApp) {
|
||||
mod credentials;
|
||||
#[cfg(target_os = "android")]
|
||||
mod oauth;
|
||||
#[cfg(target_os = "android")]
|
||||
mod tls;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod documents;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//! Initialize the process-wide verifier before any reqwest/JMAP networking.
|
||||
|
||||
use jni_verifier::{JavaVM, errors::Error, objects::JObject};
|
||||
|
||||
/// # Safety
|
||||
/// `vm` must be the live process JVM and `activity` a live JNI reference to an
|
||||
/// Android Context. Both must remain valid until this call returns.
|
||||
pub(super) unsafe fn initialize(
|
||||
vm: *mut jni_verifier::sys::JavaVM,
|
||||
activity: jni_verifier::sys::jobject,
|
||||
) -> Result<(), Error> {
|
||||
// SAFETY: android_main supplies the live VM and NativeActivity global
|
||||
// reference owned by AndroidApp, which outlives this call.
|
||||
let vm = unsafe { JavaVM::from_raw(vm) };
|
||||
vm.attach_current_thread(|env| {
|
||||
// Borrow AndroidApp's global reference without taking ownership.
|
||||
let activity = unsafe { env.as_cast_raw::<JObject>(&activity)? };
|
||||
// Keep the Application, not an Activity, alive for the process-wide
|
||||
// verifier. This also supplies the app class loader for its JVM bridge.
|
||||
let context = env
|
||||
.call_method(
|
||||
&activity,
|
||||
jni_verifier::jni_str!(jni = jni_verifier, "getApplicationContext"),
|
||||
jni_verifier::jni_sig!(jni = jni_verifier, () -> android.content.Context),
|
||||
&[],
|
||||
)?
|
||||
.l()?;
|
||||
rustls_platform_verifier::android::init_with_env(env, context)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user