Skip to main content

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::{Global, JClass, JClassLoader, JObject};
24use jni::strings::JNIStr;
25use jni::{jni_sig, jni_str, Env, JavaVM};
26use once_cell::sync::OnceCell;
27
28static GLOBAL: OnceCell<GlobalStorage> = OnceCell::new();
29
30/// A layer to access the Android runtime which is hosting the current
31/// application process.
32///
33/// Generally this trait should be implemented in your Rust app component's FFI
34/// initialization layer.
35pub trait Runtime: Send + Sync {
36    /// Returns a handle to the current process' JVM.
37    fn java_vm(&self) -> &JavaVM;
38    /// Returns a reference to the current app's [Context].
39    ///
40    /// [Context]: <https://developer.android.com/reference/android/content/Context>
41    fn context(&self) -> &Global<JObject<'static>>;
42    /// Returns a reference to the class returned by the current JVM's `getClassLoader` call.
43    fn class_loader(&self) -> &Global<JClassLoader<'static>>;
44}
45
46enum GlobalStorage {
47    Internal {
48        java_vm: JavaVM,
49        context: Global<JObject<'static>>,
50        loader: Global<JClassLoader<'static>>,
51    },
52    External(&'static dyn Runtime),
53}
54
55impl GlobalStorage {
56    fn vm(&self) -> &JavaVM {
57        match self {
58            GlobalStorage::Internal { java_vm, .. } => java_vm,
59            GlobalStorage::External(runtime) => runtime.java_vm(),
60        }
61    }
62
63    fn context(&self, env: &mut Env) -> Result<GlobalContext, Error> {
64        let context = match self {
65            Self::Internal { context, .. } => context,
66            Self::External(global) => global.context(),
67        };
68
69        let loader = match self {
70            Self::Internal { loader, .. } => loader,
71            Self::External(global) => global.class_loader(),
72        };
73
74        Ok(GlobalContext {
75            context: env.new_global_ref(context)?,
76            loader: env.new_global_ref(loader)?,
77        })
78    }
79}
80
81pub(super) struct GlobalContext {
82    /// The Android application [Context](https://developer.android.com/reference/android/app/Application).
83    pub(super) context: Global<JObject<'static>>,
84    loader: Global<JClassLoader<'static>>,
85}
86
87fn global() -> &'static GlobalStorage {
88    GLOBAL
89        .get()
90        .expect("Expect rustls-platform-verifier to be initialized")
91}
92
93/// Initialize given a typical Android NDK [`Env`] and [`JObject`] context.
94///
95/// This method will setup and store an environment locally. This is useful if nothing else in your
96/// application needs to access the Android runtime.
97pub fn init_with_env(env: &mut Env, context: JObject) -> Result<(), JNIError> {
98    GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
99        let loader = env
100            .call_method(
101                &context,
102                jni_str!("getClassLoader"),
103                jni_sig!(() -> JClassLoader),
104                &[],
105            )?
106            .l()?;
107        let loader = env.cast_local::<JClassLoader>(loader)?;
108
109        Ok(GlobalStorage::Internal {
110            java_vm: env.get_java_vm()?,
111            context: env.new_global_ref(context)?,
112            loader: env.new_global_ref(loader)?,
113        })
114    })?;
115    Ok(())
116}
117
118/// Initialize with a runtime that can dynamically serve references to
119/// the JVM, context, and class loader.
120///
121/// This is the most flexible option, and is useful for advanced use cases.
122///
123/// This function will never panic.
124pub fn init_with_runtime(runtime: &'static dyn Runtime) {
125    GLOBAL.get_or_init(|| GlobalStorage::External(runtime));
126}
127
128/// Initialize with references to the JVM, context, and class loader.
129///
130/// This is useful when you're already interacting with `jni-rs` wrapped objects and want to use
131/// global references to objects for efficiency.
132///
133/// This function will never panic.
134///
135/// # Examples
136///
137/// ```
138/// pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> {
139///     let mut env = unsafe { jni::EnvUnowned::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() };
140///     let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) };
141///     let loader = env.get_object_class(&context)?.get_class_loader(env)?;
142///
143///     env.with_env(|env| {
144///         rustls_platform_verifier::android::init_with_refs(
145///             env.get_java_vm(),
146///             env.new_global_ref(context)?,
147///             env.new_global_ref(loader)?,
148///         );
149///     });
150/// }
151/// ```
152pub fn init_with_refs(
153    java_vm: JavaVM,
154    context: Global<JObject<'static>>,
155    loader: Global<JClassLoader<'static>>,
156) {
157    GLOBAL.get_or_init(|| GlobalStorage::Internal {
158        java_vm,
159        context,
160        loader,
161    });
162}
163
164/// Wrapper for JNI errors that will log and clear exceptions
165/// It should generally be preferred to `jni::errors::Error`
166#[derive(Debug)]
167pub(super) struct Error;
168
169impl From<JNIError> for Error {
170    #[track_caller]
171    fn from(cause: JNIError) -> Self {
172        if let JNIError::JavaException = cause {
173            let _ = global()
174                .vm()
175                .with_top_local_frame(|env| -> Result<(), JNIError> {
176                    env.exception_describe();
177                    env.exception_clear();
178                    Ok(())
179                });
180        }
181
182        Self
183    }
184}
185
186pub(super) struct LocalContext<'a, 'env> {
187    pub(super) env: &'a mut Env<'env>,
188    pub(super) global: GlobalContext,
189}
190
191impl<'env> LocalContext<'_, 'env> {
192    /// Load a class from the application class loader
193    ///
194    /// This should be used instead of `JNIEnv::find_class` to ensure all classes
195    /// in the application can be found.
196    fn load_class(&mut self, name: &'static JNIStr) -> Result<JClass<'env>, Error> {
197        let name = self.env.new_string(name.to_str())?;
198        self.global
199            .loader
200            .load_class(self.env, name)
201            .map_err(Error::from)
202    }
203}
204
205/// Borrow the Android application context and execute the closure
206/// `with_context, ensuring locals are properly freed and exceptions
207/// are cleared.
208pub(super) fn with_context<F, T: 'static>(f: F) -> Result<T, Error>
209where
210    F: FnOnce(&mut LocalContext) -> Result<T, Error>,
211{
212    let global = global();
213    // Use `attach_current_thread_for_scope()` to avoid permanently attaching any threads.
214    // See https://github.com/rustls/rustls-platform-verifier/pull/185.
215    global.vm().attach_current_thread_for_scope(|env| {
216        let global_context = global.context(env)?;
217        let mut context = LocalContext {
218            env,
219            global: global_context,
220        };
221        f(&mut context)
222    })
223}
224
225/// Loads and caches a class on first use
226pub(super) struct CachedClass {
227    name: &'static JNIStr,
228    class: OnceCell<Global<JClass<'static>>>,
229}
230
231impl CachedClass {
232    /// Creates a lazily initialized class reference to the class with `name`.
233    pub(super) const fn new(name: &'static JNIStr) -> Self {
234        Self {
235            name,
236            class: OnceCell::new(),
237        }
238    }
239
240    /// Gets the cached class reference, loaded on first use
241    pub(super) fn get(&self, cx: &mut LocalContext) -> Result<&JClass<'static>, Error> {
242        let class = self.class.get_or_try_init(|| -> Result<_, Error> {
243            let class = cx.load_class(self.name)?;
244
245            Ok(cx.env.new_global_ref(class)?)
246        })?;
247
248        Ok(class)
249    }
250}