rustls_platform_verifier/
android.rs

1//! On Android, initialization must be done before any verification is attempted.
2//!
3//! <div class="warning">
4//! Some manual setup is required outside of cargo to use this crate on Android. In order to use
5//! Android’s certificate verifier, the crate needs to call into the JVM. A small Kotlin component
6//! must be included in your app’s build to support rustls-platform-verifier.
7//!
8//! See the [crate's Android section][crate#android] for more details.
9//! </div>
10//!
11//! # Examples
12//!
13//! ```
14//! // A typical entrypoint signature for obtaining the necessary pointers
15//! pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> {
16//!     let mut env = unsafe { JNIEnv::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() };
17//!     let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) };
18//!     rustls_platform_verifier::android::init_with_env(&mut env, context)?;
19//! }
20//! ```
21
22use jni::errors::Error as JNIError;
23use jni::objects::{GlobalRef, JClass, JObject, JValue};
24use jni::{AttachGuard, JNIEnv, JavaVM};
25use once_cell::sync::OnceCell;
26
27static GLOBAL: OnceCell<Global> = OnceCell::new();
28
29/// A layer to access the Android runtime which is hosting the current
30/// application process.
31///
32/// Generally this trait should be implemented in your Rust app component's FFI
33/// initialization layer.
34pub trait Runtime: Send + Sync {
35    /// Returns a handle to the current process' JVM.
36    fn java_vm(&self) -> &JavaVM;
37    /// Returns a reference to the current app's [Context].
38    ///
39    /// [Context]: <https://developer.android.com/reference/android/content/Context>
40    fn context(&self) -> &GlobalRef;
41    /// Returns a reference to the class returned by the current JVM's `getClassLoader` call.
42    fn class_loader(&self) -> &GlobalRef;
43}
44
45enum Global {
46    Internal {
47        java_vm: JavaVM,
48        context: GlobalRef,
49        loader: GlobalRef,
50    },
51    External(&'static dyn Runtime),
52}
53
54impl Global {
55    fn env(&self) -> Result<AttachGuard<'_>, Error> {
56        let vm = match self {
57            Global::Internal { java_vm, .. } => java_vm,
58            Global::External(global) => global.java_vm(),
59        };
60        Ok(vm.attach_current_thread()?)
61    }
62
63    fn context(&self) -> Result<(GlobalContext, AttachGuard<'_>), Error> {
64        let env = self.env()?;
65
66        let context = match self {
67            Global::Internal { context, .. } => context,
68            Global::External(global) => global.context(),
69        };
70
71        let loader = match self {
72            Global::Internal { loader, .. } => loader,
73            Global::External(global) => global.class_loader(),
74        };
75
76        Ok((
77            GlobalContext {
78                context: env.new_global_ref(context)?,
79                loader: env.new_global_ref(loader)?,
80            },
81            env,
82        ))
83    }
84}
85
86struct GlobalContext {
87    context: GlobalRef,
88    loader: GlobalRef,
89}
90
91fn global() -> &'static Global {
92    GLOBAL
93        .get()
94        .expect("Expect rustls-platform-verifier to be initialized")
95}
96
97/// Initialize given a typical Android NDK [`JNIEnv`] and [`JObject`] context.
98///
99/// This method will setup and store an environment locally. This is useful if nothing else in your
100/// application needs to access the Android runtime.
101pub fn init_with_env(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
102    GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
103        let loader =
104            env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
105
106        Ok(Global::Internal {
107            java_vm: env.get_java_vm()?,
108            context: env.new_global_ref(context)?,
109            loader: env.new_global_ref(JObject::try_from(loader)?)?,
110        })
111    })?;
112    Ok(())
113}
114
115/// *Deprecated*: This is the original method name for [`init_with_env`] and is functionally
116/// identical.
117pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
118    init_with_env(env, context)
119}
120
121/// Initialize with a runtime that can dynamically serve references to
122/// the JVM, context, and class loader.
123///
124/// This is the most flexible option, and is useful for advanced use cases.
125///
126/// This function will never panic.
127pub fn init_with_runtime(runtime: &'static dyn Runtime) {
128    GLOBAL.get_or_init(|| Global::External(runtime));
129}
130
131/// *Deprecated*: This is the original method name for [`init_with_runtime`] and is functionally
132/// identical.
133pub fn init_external(runtime: &'static dyn Runtime) {
134    init_with_runtime(runtime);
135}
136
137/// Initialize with references to the JVM, context, and class loader.
138///
139/// This is useful when you're already interacting with `jni-rs` wrapped objects and want to use
140/// global references to objects for efficiency.
141///
142/// This function will never panic.
143///
144/// # Examples
145///
146/// ```
147/// pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> {
148///     let mut env = unsafe { jni::JNIEnv::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() };
149///     let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) };
150///     let loader = env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
151///
152///     rustls_platform_verifier::android::init_with_refs(
153///         env.get_java_vm()?,
154///         env.new_global_ref(context)?,
155///         env.new_global_ref(JObject::try_from(loader)?)?,
156///     );
157/// }
158/// ```
159pub fn init_with_refs(java_vm: JavaVM, context: GlobalRef, loader: GlobalRef) {
160    GLOBAL.get_or_init(|| Global::Internal {
161        java_vm,
162        context,
163        loader,
164    });
165}
166
167/// Wrapper for JNI errors that will log and clear exceptions
168/// It should generally be preferred to `jni::errors::Error`
169#[derive(Debug)]
170pub(super) struct Error;
171
172impl From<JNIError> for Error {
173    #[track_caller]
174    fn from(cause: JNIError) -> Self {
175        if let JNIError::JavaException = cause {
176            if let Ok(env) = global().env() {
177                // These should not fail if we are already in a throwing state unless
178                // things are very broken. In that case, there isn't much we can do.
179                let _ = env.exception_describe();
180                let _ = env.exception_clear();
181            }
182        }
183
184        Self
185    }
186}
187
188pub(super) struct LocalContext<'a, 'env> {
189    env: &'a mut JNIEnv<'env>,
190    context: GlobalRef,
191    loader: GlobalRef,
192}
193
194impl<'a, 'env> LocalContext<'a, 'env> {
195    /// Load a class from the application class loader
196    ///
197    /// This should be used instead of `JNIEnv::find_class` to ensure all classes
198    /// in the application can be found.
199    fn load_class(&mut self, name: &str) -> Result<JClass<'env>, Error> {
200        let name = self.env.new_string(name)?;
201        let class = self.env.call_method(
202            &self.loader,
203            "loadClass",
204            "(Ljava/lang/String;)Ljava/lang/Class;",
205            &[JValue::from(&name)],
206        )?;
207
208        Ok(JObject::try_from(class)?.into())
209    }
210
211    /// Borrow the `applicationContext` from the Android application
212    /// <https://developer.android.com/reference/android/app/Application>
213    pub(super) fn application_context(&self) -> &JObject<'_> {
214        &self.context
215    }
216}
217
218/// Borrow the Android application context and execute the closure
219/// `with_context, ensuring locals are properly freed and exceptions
220/// are cleared.
221pub(super) fn with_context<F, T: 'static>(f: F) -> Result<T, Error>
222where
223    F: FnOnce(&mut LocalContext, &mut JNIEnv) -> Result<T, Error>,
224{
225    let (global_context, mut binding_env) = global().context()?;
226    // SAFETY: Any local references created with this env are always destroyed prior to the parent
227    // frame exiting because we force it to be dropped before the new frame exits and don't allow
228    // the closure to access the env directly. We don't use it anywhere outside that sub-scope either.
229    //
230    // Rust's borrowing rules enforce that any reference that came from this env must be dropped before it is too.
231    let ctx_env = unsafe { binding_env.unsafe_clone() };
232
233    // 16 is the default capacity in the JVM, we can make this configurable if necessary
234    binding_env.with_local_frame(16, |env| {
235        let mut ctx_env = ctx_env;
236        let mut context = LocalContext {
237            env: &mut ctx_env,
238            context: global_context.context,
239            loader: global_context.loader,
240        };
241        f(&mut context, env)
242    })
243}
244
245/// Loads and caches a class on first use
246pub(super) struct CachedClass {
247    name: &'static str,
248    class: OnceCell<GlobalRef>,
249}
250
251impl CachedClass {
252    /// Creates a lazily initialized class reference to the class with `name`.
253    pub(super) const fn new(name: &'static str) -> Self {
254        Self {
255            name,
256            class: OnceCell::new(),
257        }
258    }
259
260    /// Gets the cached class reference, loaded on first use
261    pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'_>, Error> {
262        let class = self.class.get_or_try_init(|| -> Result<_, Error> {
263            let class = cx.load_class(self.name)?;
264
265            Ok(cx.env.new_global_ref(class)?)
266        })?;
267
268        Ok(class.as_obj().into())
269    }
270}