diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c078ac6..88a6afa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,7 +106,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: - toolchain: "1.71.0" # MSRV + toolchain: "1.77.0" # MSRV components: clippy - name: Install cargo-ndk. diff --git a/Cargo.lock b/Cargo.lock index 38f8f6e..71cafa4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,25 +116,39 @@ dependencies = [ [[package]] name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +version = "0.22.0" +source = "git+https://github.com/jni-rs/jni-rs?branch=release-0.22#451b9182a6b40cefa74776fc4439888552bb8489" dependencies = [ "cesu8", "cfg-if", "combine", "jni-sys", "log", + "once_cell", + "paste", + "static_assertions", "thiserror", "walkdir", - "windows-sys 0.45.0", ] [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "c30a312d782b8d56a1e0897d45c1af33f31f9b4a4d13d31207a8675e0223b818" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c199962dfd5610ced8eca382606e349f7940a4ac7d867b58a046123411cbb4" +dependencies = [ + "quote", + "syn 1.0.109", +] [[package]] name = "libc" @@ -166,6 +180,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "proc-macro2" version = "1.0.94" @@ -277,7 +297,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -342,12 +362,29 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.99" @@ -361,22 +398,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.99", ] [[package]] @@ -425,22 +462,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -449,22 +477,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets", ] [[package]] @@ -473,46 +486,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -525,48 +520,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 91d9d30..dc3332d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,6 @@ members = [ "rustls-platform-verifier", ] resolver = "2" + +[patch."crates-io"] +jni = { git = "https://github.com/jni-rs/jni-rs", branch = "release-0.22" } diff --git a/README.md b/README.md index bbe35ef..2188c54 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![crates.io version](https://img.shields.io/crates/v/rustls-platform-verifier.svg)](https://crates.io/crates/rustls-platform-verifier) [![crate documentation](https://docs.rs/rustls-platform-verifier/badge.svg)](https://docs.rs/rustls-platform-verifier) -![MSRV](https://img.shields.io/badge/rustc-1.64+-blue.svg) +![MSRV](https://img.shields.io/badge/rustc-1.77+-blue.svg) [![crates.io downloads](https://img.shields.io/crates/d/rustls-platform-verifier.svg)](https://crates.io/crates/rustls-platform-verifier) ![CI](https://github.com/1Password/rustls-platform-verifier/workflows/CI/badge.svg) diff --git a/rustls-platform-verifier/Cargo.toml b/rustls-platform-verifier/Cargo.toml index c34cbb4..b6fc705 100644 --- a/rustls-platform-verifier/Cargo.toml +++ b/rustls-platform-verifier/Cargo.toml @@ -7,7 +7,7 @@ keywords = ["tls", "certificate", "verification", "os", "native"] repository = "https://github.com/rustls/rustls-platform-verifier" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.71.0" +rust-version = "1.77.0" [lib] name = "rustls_platform_verifier" @@ -32,7 +32,7 @@ docsrs = ["jni", "once_cell"] rustls = { version = "0.23.27", default-features = false, features = ["std"] } log = { version = "0.4" } base64 = { version = "0.22", optional = true } # Only used when the `cert-logging` feature is enabled. -jni = { version = "0.21", default-features = false, optional = true } # Only used during doc generation +jni = { version = "0.22", default-features = false, optional = true } # Only used during doc generation once_cell = { version = "1.9", optional = true } # Only used during doc generation [target.'cfg(all(unix, not(target_os = "android"), not(target_vendor = "apple"), not(target_arch = "wasm32")))'.dependencies] @@ -42,7 +42,7 @@ webpki = { package = "rustls-webpki", version = "0.103", default-features = fals [target.'cfg(target_os = "android")'.dependencies] once_cell = "1.9" rustls-platform-verifier-android = { path = "../android-release-support", version = "0.1.0" } -jni = { version = "0.21", default-features = false } +jni = { version = "0.22", default-features = false } webpki = { package = "rustls-webpki", version = "0.103", default-features = false } android_logger = { version = "0.15", optional = true } # Only used during testing. diff --git a/rustls-platform-verifier/src/android.rs b/rustls-platform-verifier/src/android.rs index 8500ee5..59055da 100644 --- a/rustls-platform-verifier/src/android.rs +++ b/rustls-platform-verifier/src/android.rs @@ -20,11 +20,12 @@ //! ``` use jni::errors::Error as JNIError; -use jni::objects::{GlobalRef, JClass, JObject, JValue}; -use jni::{AttachGuard, JNIEnv, JavaVM}; +use jni::objects::{Global, JClass, JClassLoader, JObject}; +use jni::{Env, JavaVM}; use once_cell::sync::OnceCell; +use std::ffi::CStr; -static GLOBAL: OnceCell = OnceCell::new(); +static GLOBAL: OnceCell = OnceCell::new(); /// A layer to access the Android runtime which is hosting the current /// application process. @@ -37,87 +38,75 @@ pub trait Runtime: Send + Sync { /// Returns a reference to the current app's [Context]. /// /// [Context]: - fn context(&self) -> &GlobalRef; + fn context(&self) -> &Global>; /// Returns a reference to the class returned by the current JVM's `getClassLoader` call. - fn class_loader(&self) -> &GlobalRef; + fn class_loader(&self) -> &Global>; } -enum Global { +enum GlobalStorage { Internal { java_vm: JavaVM, - context: GlobalRef, - loader: GlobalRef, + context: Global>, + loader: Global>, }, External(&'static dyn Runtime), } -impl Global { - fn env(&self) -> Result, Error> { - let vm = match self { - Global::Internal { java_vm, .. } => java_vm, - Global::External(global) => global.java_vm(), - }; - Ok(vm.attach_current_thread()?) +impl GlobalStorage { + fn vm(&self) -> &JavaVM { + match self { + GlobalStorage::Internal { java_vm, .. } => java_vm, + GlobalStorage::External(runtime) => runtime.java_vm(), + } } - fn context(&self) -> Result<(GlobalContext, AttachGuard<'_>), Error> { - let env = self.env()?; - + fn context(&self, env: &mut Env) -> Result { let context = match self { - Global::Internal { context, .. } => context, - Global::External(global) => global.context(), + Self::Internal { context, .. } => context, + Self::External(global) => global.context(), }; let loader = match self { - Global::Internal { loader, .. } => loader, - Global::External(global) => global.class_loader(), + Self::Internal { loader, .. } => loader, + Self::External(global) => global.class_loader(), }; - Ok(( - GlobalContext { - context: env.new_global_ref(context)?, - loader: env.new_global_ref(loader)?, - }, - env, - )) + Ok(GlobalContext { + context: env.new_global_ref(context)?, + loader: env.new_global_ref(loader)?, + }) } } -struct GlobalContext { - context: GlobalRef, - loader: GlobalRef, +pub(super) struct GlobalContext { + /// The Android application [Context](https://developer.android.com/reference/android/app/Application). + pub(super) context: Global>, + loader: Global>, } -fn global() -> &'static Global { +fn global() -> &'static GlobalStorage { GLOBAL .get() .expect("Expect rustls-platform-verifier to be initialized") } -/// Initialize given a typical Android NDK [`JNIEnv`] and [`JObject`] context. +/// Initialize given a typical Android NDK [`Env`] and [`JObject`] context. /// /// This method will setup and store an environment locally. This is useful if nothing else in your /// application needs to access the Android runtime. -pub fn init_with_env(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> { +pub fn init_with_env(env: &mut Env, context: JObject) -> Result<(), JNIError> { GLOBAL.get_or_try_init(|| -> Result<_, JNIError> { - let loader = - env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?; + let loader = env.get_object_class(&context)?.get_class_loader(env)?; - Ok(Global::Internal { - java_vm: env.get_java_vm()?, + Ok(GlobalStorage::Internal { + java_vm: env.get_java_vm(), context: env.new_global_ref(context)?, - loader: env.new_global_ref(JObject::try_from(loader)?)?, + loader: env.new_global_ref(loader)?, }) })?; Ok(()) } -/// *Deprecated*: This is the original method name for [`init_with_env`] and is functionally -/// identical. -pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> { - init_with_env(env, context) -} - /// Initialize with a runtime that can dynamically serve references to /// the JVM, context, and class loader. /// @@ -125,13 +114,7 @@ pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> { /// /// This function will never panic. pub fn init_with_runtime(runtime: &'static dyn Runtime) { - GLOBAL.get_or_init(|| Global::External(runtime)); -} - -/// *Deprecated*: This is the original method name for [`init_with_runtime`] and is functionally -/// identical. -pub fn init_external(runtime: &'static dyn Runtime) { - init_with_runtime(runtime); + GLOBAL.get_or_init(|| GlobalStorage::External(runtime)); } /// Initialize with references to the JVM, context, and class loader. @@ -145,19 +128,25 @@ pub fn init_external(runtime: &'static dyn Runtime) { /// /// ``` /// pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> { -/// let mut env = unsafe { jni::JNIEnv::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() }; +/// let mut env = unsafe { jni::EnvUnowned::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() }; /// let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) }; -/// let loader = env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?; +/// let loader = env.call_method(&context, c"getClassLoader", c"()Ljava/lang/ClassLoader;", &[])?; /// -/// rustls_platform_verifier::android::init_with_refs( -/// env.get_java_vm()?, -/// env.new_global_ref(context)?, -/// env.new_global_ref(JObject::try_from(loader)?)?, -/// ); +/// env.with_env(|env| { +/// rustls_platform_verifier::android::init_with_refs( +/// env.get_java_vm(), +/// env.new_global_ref(context)?, +/// env.new_global_ref(JClassLoader::try_from(loader)?)?, +/// ); +/// }); /// } /// ``` -pub fn init_with_refs(java_vm: JavaVM, context: GlobalRef, loader: GlobalRef) { - GLOBAL.get_or_init(|| Global::Internal { +pub fn init_with_refs( + java_vm: JavaVM, + context: Global>, + loader: Global>, +) { + GLOBAL.get_or_init(|| GlobalStorage::Internal { java_vm, context, loader, @@ -173,12 +162,15 @@ impl From for Error { #[track_caller] fn from(cause: JNIError) -> Self { if let JNIError::JavaException = cause { - if let Ok(env) = global().env() { - // These should not fail if we are already in a throwing state unless - // things are very broken. In that case, there isn't much we can do. - let _ = env.exception_describe(); - let _ = env.exception_clear(); - } + // SAFETY: We do not retain the `AttachGuard`, do not have access to the previous guard/env from + // whichever JNI call errored before claling this function, and therefore don't unsafely mutate it. + if let Ok(mut env) = unsafe { global().vm().get_env_attachment() } { + let _ = env.with_env_current_frame(|env| { + env.exception_describe(); + env.exception_clear(); + Ok::<_, Error>(()) + }); + }; } Self @@ -186,9 +178,8 @@ impl From for Error { } pub(super) struct LocalContext<'a, 'env> { - env: &'a mut JNIEnv<'env>, - context: GlobalRef, - loader: GlobalRef, + pub(super) env: &'a mut Env<'env>, + pub(super) global: GlobalContext, } impl<'a, 'env> LocalContext<'a, 'env> { @@ -196,22 +187,11 @@ impl<'a, 'env> LocalContext<'a, 'env> { /// /// This should be used instead of `JNIEnv::find_class` to ensure all classes /// in the application can be found. - fn load_class(&mut self, name: &str) -> Result, Error> { - let name = self.env.new_string(name)?; - let class = self.env.call_method( - &self.loader, - "loadClass", - "(Ljava/lang/String;)Ljava/lang/Class;", - &[JValue::from(&name)], - )?; - - Ok(JObject::try_from(class)?.into()) - } - - /// Borrow the `applicationContext` from the Android application - /// - pub(super) fn application_context(&self) -> &JObject<'_> { - &self.context + fn load_class(&mut self, name: &'static CStr) -> Result, Error> { + self.global + .loader + .load_class(name.as_ref(), self.env) + .map_err(Error::from) } } @@ -220,37 +200,28 @@ impl<'a, 'env> LocalContext<'a, 'env> { /// are cleared. pub(super) fn with_context(f: F) -> Result where - F: FnOnce(&mut LocalContext, &mut JNIEnv) -> Result, + F: FnOnce(&mut LocalContext) -> Result, { - let (global_context, mut binding_env) = global().context()?; - // SAFETY: Any local references created with this env are always destroyed prior to the parent - // frame exiting because we force it to be dropped before the new frame exits and don't allow - // the closure to access the env directly. We don't use it anywhere outside that sub-scope either. - // - // Rust's borrowing rules enforce that any reference that came from this env must be dropped before it is too. - let ctx_env = unsafe { binding_env.unsafe_clone() }; - - // 16 is the default capacity in the JVM, we can make this configurable if necessary - binding_env.with_local_frame(16, |env| { - let mut ctx_env = ctx_env; + let global = global(); + global.vm().attach_current_thread_for_scope(|env| { + let global_context = global.context(env)?; let mut context = LocalContext { - env: &mut ctx_env, - context: global_context.context, - loader: global_context.loader, + env, + global: global_context, }; - f(&mut context, env) + f(&mut context) }) } /// Loads and caches a class on first use pub(super) struct CachedClass { - name: &'static str, - class: OnceCell, + name: &'static CStr, + class: OnceCell>>, } impl CachedClass { /// Creates a lazily initialized class reference to the class with `name`. - pub(super) const fn new(name: &'static str) -> Self { + pub(super) const fn new(name: &'static CStr) -> Self { Self { name, class: OnceCell::new(), @@ -258,13 +229,13 @@ impl CachedClass { } /// Gets the cached class reference, loaded on first use - pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'_>, Error> { + pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'static>, Error> { let class = self.class.get_or_try_init(|| -> Result<_, Error> { let class = cx.load_class(self.name)?; Ok(cx.env.new_global_ref(class)?) })?; - Ok(class.as_obj().into()) + Ok(class) } } diff --git a/rustls-platform-verifier/src/tests/ffi.rs b/rustls-platform-verifier/src/tests/ffi.rs index 23fc54f..35a5359 100644 --- a/rustls-platform-verifier/src/tests/ffi.rs +++ b/rustls-platform-verifier/src/tests/ffi.rs @@ -18,74 +18,67 @@ mod android { use jni::{ objects::{JClass, JObject, JString}, sys::jstring, - JNIEnv, + EnvUnowned, }; - use std::sync::Once; + use std::{ffi::CStr, sync::Once}; static ANDROID_INIT: Once = Once::new(); /// A marker that the Kotlin test runner looks for to determine /// if a set of integration tests passed or not. - const SUCCESS_MARKER: &str = "success"; + const SUCCESS_MARKER: &CStr = c"success"; - fn run_android_test<'a>( - env: &'a mut JNIEnv, + fn run_android_test<'caller>( + env: &mut EnvUnowned<'caller>, cx: JObject, suite_name: &'static str, test_cases: &'static [fn()], - ) -> JString<'a> { - // These can't fail, and even if they did, Android will crash the process like we want. - ANDROID_INIT.call_once(|| { - let log_filter = android_logger::FilterBuilder::new() - .parse("trace") - .filter_module("jni", log::LevelFilter::Off) - .build(); - - android_logger::init_once( - android_logger::Config::default() - .with_max_level(log::Level::Trace.to_level_filter()) - .with_filter(log_filter), - ); - crate::android::init_with_env(env, cx).unwrap(); - std::panic::set_hook(Box::new(|info| { - let msg = if let Some(msg) = info.payload().downcast_ref::<&'static str>() { - msg - } else if let Some(msg) = info.payload().downcast_ref::() { - msg.as_str() - } else { - "no panic info available" - }; - - log::error!("test panic: {}", msg) - })) + ) -> JString<'caller> { + let result = env.with_env(|env| { + // These can't fail, and even if they did, Android will crash the process like we want. + ANDROID_INIT.call_once(|| { + let log_filter = android_logger::FilterBuilder::new() + .parse("trace") + .filter_module("jni", log::LevelFilter::Off) + .build(); + + android_logger::init_once( + android_logger::Config::default() + .with_max_level(log::Level::Trace.to_level_filter()) + .with_filter(log_filter), + ); + crate::android::init_with_env(env, cx).unwrap(); + }); + + for test in test_cases.iter() { + test(); + } + + Ok::<_, jni::errors::Error>(()) }); - let mut failed = false; - for test in test_cases.iter() { - // Use a dedicated thread so panics don't cause crashes. - if std::thread::spawn(test).join().is_err() { - failed = true; + env.with_env(|env| { + let ret_msg = if let Err(jni::errors::Error::PanicCaught(fail_msg)) = result { log::error!("{}: test failed", suite_name); + log::error!("{}", fail_msg); + c"test_failed" } else { log::info!("{}: test passed", suite_name); - } + SUCCESS_MARKER + }; log::info!( "-----------------------------------------------------------------------------" - ) - } + ); - env.new_string(if failed { - "test failed!" - } else { - SUCCESS_MARKER + env.new_string(ret_msg) }) .unwrap() } #[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_mockTests"] - pub extern "C" fn rustls_platform_verifier_mock_test_suite( - mut env: JNIEnv, + pub extern "C" fn rustls_platform_verifier_mock_test_suite<'caller>( + mut env: EnvUnowned<'caller>, _class: JClass, cx: JObject, ) -> jstring { @@ -101,8 +94,8 @@ mod android { } #[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_verifyMockRootUsage"] - pub extern "C" fn rustls_platform_verifier_verify_mock_root_usage( - mut env: JNIEnv, + pub extern "C" fn rustls_platform_verifier_verify_mock_root_usage<'caller>( + mut env: EnvUnowned<'caller>, _class: JClass, cx: JObject, ) -> jstring { @@ -118,8 +111,8 @@ mod android { } #[export_name = "Java_org_rustls_platformverifier_CertificateVerifierTests_realWorldTests"] - pub extern "C" fn rustls_platform_verifier_real_world_test_suite( - mut env: JNIEnv, + pub extern "C" fn rustls_platform_verifier_real_world_test_suite<'caller>( + mut env: EnvUnowned<'caller>, _class: JClass, cx: JObject, ) -> jstring { diff --git a/rustls-platform-verifier/src/verification/android.rs b/rustls-platform-verifier/src/verification/android.rs index 7f602e8..cd8023e 100644 --- a/rustls-platform-verifier/src/verification/android.rs +++ b/rustls-platform-verifier/src/verification/android.rs @@ -1,9 +1,8 @@ -use std::sync::Arc; - use jni::{ - objects::{JObject, JValue}, - strings::JavaStr, - JNIEnv, + objects::{JByteArray, JObject, JString, JValue}, + refs::Reference, + strings::JNIString, + Env, }; use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerifier}; use rustls::crypto::{verify_tls12_signature, verify_tls13_signature, CryptoProvider}; @@ -12,17 +11,17 @@ use rustls::Error::InvalidCertificate; use rustls::{ CertificateError, DigitallySignedStruct, Error as TlsError, OtherError, SignatureScheme, }; +use std::{ffi::CStr, sync::Arc}; use super::{log_server_cert, ALLOWED_EKUS}; use crate::android::{with_context, CachedClass}; static CERT_VERIFIER_CLASS: CachedClass = - CachedClass::new("org/rustls/platformverifier/CertificateVerifier"); + CachedClass::new(c"org/rustls/platformverifier/CertificateVerifier"); // Find the `ByteArray (Uint8 [])` class. -static BYTE_ARRAY_CLASS: CachedClass = CachedClass::new("[B"); - -static STRING_CLASS: CachedClass = CachedClass::new("java/lang/String"); +static BYTE_ARRAY_CLASS: CachedClass = CachedClass::new(c"[B"); +static STRING_CLASS: CachedClass = CachedClass::new(c"java/lang/String"); // Note: Keep these in sync with the Kotlin enum. #[derive(Debug)] @@ -38,7 +37,7 @@ enum VerifierStatus { // Android's certificate verifier ignores this outright and this is considered the // official recommendation. See https://bugs.chromium.org/p/chromium/issues/detail?id=627154. -const AUTH_TYPE: &str = "RSA"; +const AUTH_TYPE: &CStr = c"RSA"; /// A TLS certificate verifier that utilizes the Android platform verifier. #[derive(Debug)] @@ -52,8 +51,10 @@ pub struct Verifier { #[cfg(any(test, feature = "ffi-testing"))] impl Drop for Verifier { fn drop(&mut self) { - with_context::<_, ()>(|cx, env| { - env.call_static_method(CERT_VERIFIER_CLASS.get(cx)?, "clearMockRoots", "()V", &[])? + with_context::<_, ()>(|cx| { + let cert_verifier_class = CERT_VERIFIER_CLASS.get(cx)?; + cx.env + .call_static_method(cert_verifier_class, c"clearMockRoots", c"()V", &[])? .v()?; Ok(()) }) @@ -104,83 +105,97 @@ impl Verifier { .try_into() .map_err(|_| TlsError::FailedToGetCurrentTime)?; - let verification_result = with_context(|cx, env| { + let verification_result = with_context(|cx| { + let byte_array_class = BYTE_ARRAY_CLASS.get(cx)?; + let string_class = STRING_CLASS.get(cx)?; + let cert_verifier_class = CERT_VERIFIER_CLASS.get(cx)?; + // We don't provide an initial element so that the array filling can be cleaner. // It's valid to provide a `null` value. Ref: https://docs.oracle.com/en/java/javase/13/docs/specs/jni/functions.html -> NewObjectArray let cert_list = { - let array = env.new_object_array( + let array = cx.env.new_object_array( (intermediates.len() + 1).try_into().unwrap(), - BYTE_ARRAY_CLASS.get(cx)?, + byte_array_class, JObject::null(), )?; for (idx, cert) in certificate_chain { - let idx = idx.try_into().unwrap(); - let cert_buffer = env.byte_array_from_slice(cert)?; - env.set_object_array_element(&array, idx, cert_buffer)? + let cert_buffer = cx.env.byte_array_from_slice(cert)?; + array.set_element(idx, cert_buffer, cx.env)?; } array }; let allowed_ekus = { - let array = env.new_object_array( + let array = cx.env.new_object_array( ALLOWED_EKUS.len().try_into().unwrap(), - STRING_CLASS.get(cx)?, + string_class, JObject::null(), )?; for (idx, eku) in ALLOWED_EKUS.iter().enumerate() { - let idx = idx.try_into().unwrap(); - let eku = env.new_string(eku)?; - env.set_object_array_element(&array, idx, eku)?; + let eku = cx.env.new_string(eku)?; + array.set_element(idx, eku, cx.env)? } array }; let ocsp_response = match ocsp_response { - Some(b) => env.byte_array_from_slice(b)?, - None => JObject::null().into(), + Some(b) => cx.env.byte_array_from_slice(b)?, + None => JByteArray::null(), }; #[cfg(any(test, feature = "ffi-testing"))] { if let Some(mock_root) = &self.test_only_root_ca_override { - let mock_root = env.byte_array_from_slice(mock_root)?; - env.call_static_method( - CERT_VERIFIER_CLASS.get(cx)?, - "addMockRoot", - "([B)V", - &[JValue::from(&mock_root)], - )? - .v() - .expect("failed to add test root") + let mock_root = cx.env.byte_array_from_slice(mock_root)?; + cx.env + .call_static_method( + cert_verifier_class, + c"addMockRoot", + c"([B)V", + &[JValue::from(&mock_root)], + )? + .v() + .expect("failed to add test root") } } - const VERIFIER_CALL: &str = concat!( - '(', - "Landroid/content/Context;", - "Ljava/lang/String;", - "Ljava/lang/String;", - "[Ljava/lang/String;", - "[B", - 'J', - "[[B", - ')', - "Lorg/rustls/platformverifier/VerificationResult;" - ); - - let result = env + const VERIFIER_CALL: &CStr = match CStr::from_bytes_with_nul( + concat!( + '(', + "Landroid/content/Context;", + "Ljava/lang/String;", + "Ljava/lang/String;", + "[Ljava/lang/String;", + "[B", + 'J', + "[[B", + ')', + "Lorg/rustls/platformverifier/VerificationResult;", + '\0' + ) + .as_bytes(), + ) { + Ok(v) => v, + Err(_) => panic!(), + }; + + let server_name = cx.env.new_string(JNIString::from(server_name.to_str()))?; + let auth_type = cx.env.new_string(AUTH_TYPE)?; + + let result = cx + .env .call_static_method( - CERT_VERIFIER_CLASS.get(cx)?, - "verifyCertificateChain", + cert_verifier_class, + c"verifyCertificateChain", VERIFIER_CALL, &[ - JValue::from(cx.application_context()), - JValue::from(&env.new_string(server_name.to_str())?), - JValue::from(&env.new_string(AUTH_TYPE)?), + JValue::from(cx.global.context.as_ref()), + JValue::from(&server_name), + JValue::from(&auth_type), JValue::from(&JObject::from(allowed_ekus)), JValue::from(&ocsp_response), JValue::Long(now), @@ -189,7 +204,7 @@ impl Verifier { )? .l()?; - Ok(extract_result_info(env, result)) + Ok(extract_result_info(cx.env, result)) }); match verification_result { @@ -233,12 +248,9 @@ impl Verifier { } } -fn extract_result_info( - env: &mut JNIEnv<'_>, - result: JObject<'_>, -) -> (VerifierStatus, Option) { +fn extract_result_info(env: &mut Env<'_>, result: JObject<'_>) -> (VerifierStatus, Option) { let status_code = env - .get_field(&result, "code", "I") + .get_field(&result, c"code", c"I") .and_then(|code| code.i()) .unwrap(); @@ -255,13 +267,15 @@ fn extract_result_info( // Extract the `String?`. let msg = env - .get_field(result, "message", "Ljava/lang/String;") + .get_field(result, c"message", c"Ljava/lang/String;") .and_then(|m| m.l()) .map(|s| { if s.is_null() { None } else { - JavaStr::from_env(env, &s.into()).ok().map(String::from) + env.cast_local::(s) + .and_then(|s| s.mutf8_chars(env).map(|s| s.to_str().into_owned())) + .ok() } }) .unwrap(); diff --git a/rustls-platform-verifier/src/verification/mod.rs b/rustls-platform-verifier/src/verification/mod.rs index eba96ea..9d947ff 100644 --- a/rustls-platform-verifier/src/verification/mod.rs +++ b/rustls-platform-verifier/src/verification/mod.rs @@ -83,4 +83,4 @@ fn invalid_certificate(reason: impl Into) -> rustls::Error { const ALLOWED_EKUS: &[windows_sys::core::PCSTR] = &[windows_sys::Win32::Security::Cryptography::szOID_PKIX_KP_SERVER_AUTH]; #[cfg(target_os = "android")] -pub const ALLOWED_EKUS: &[&str] = &["1.3.6.1.5.5.7.3.1"]; +pub const ALLOWED_EKUS: &[&std::ffi::CStr] = &[c"1.3.6.1.5.5.7.3.1"];