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::{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<JNIEnv, 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_permanently()?)
61 }
62
63 fn context(&self) -> Result<Context, 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(Context {
77 context: env.new_global_ref(context)?,
78 loader: env.new_global_ref(loader)?,
79 env,
80 })
81 }
82}
83
84fn global() -> &'static Global {
85 GLOBAL
86 .get()
87 .expect("Expect rustls-platform-verifier to be initialized")
88}
89
90/// Initialize given a typical Android NDK [`JNIEnv`] and [`JObject`] context.
91///
92/// This method will setup and store an environment locally. This is useful if nothing else in your
93/// application needs to access the Android runtime.
94pub fn init_with_env(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
95 GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
96 let loader =
97 env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
98
99 Ok(Global::Internal {
100 java_vm: env.get_java_vm()?,
101 context: env.new_global_ref(context)?,
102 loader: env.new_global_ref(JObject::try_from(loader)?)?,
103 })
104 })?;
105 Ok(())
106}
107
108/// *Deprecated*: This is the original method name for [`init_with_env`] and is functionally
109/// identical.
110pub fn init_hosted(env: &mut JNIEnv, context: JObject) -> Result<(), JNIError> {
111 init_with_env(env, context)
112}
113
114/// Initialize with a runtime that can dynamically serve references to
115/// the JVM, context, and class loader.
116///
117/// This is the most flexible option, and is useful for advanced use cases.
118///
119/// This function will never panic.
120pub fn init_with_runtime(runtime: &'static dyn Runtime) {
121 GLOBAL.get_or_init(|| Global::External(runtime));
122}
123
124/// *Deprecated*: This is the original method name for [`init_with_runtime`] and is functionally
125/// identical.
126pub fn init_external(runtime: &'static dyn Runtime) {
127 init_with_runtime(runtime);
128}
129
130/// Initialize with references to the JVM, context, and class loader.
131///
132/// This is useful when you're already interacting with `jni-rs` wrapped objects and want to use
133/// global references to objects for efficiency.
134///
135/// This function will never panic.
136///
137/// # Examples
138///
139/// ```
140/// pub fn android_init(raw_env: *mut c_void, raw_context: *mut c_void) -> Result<(), jni::errors::Error> {
141/// let mut env = unsafe { jni::JNIEnv::from_raw(raw_env as *mut jni::sys::JNIEnv).unwrap() };
142/// let context = unsafe { JObject::from_raw(raw_context as jni::sys::jobject) };
143/// let loader = env.call_method(&context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
144///
145/// rustls_platform_verifier::android::init_with_refs(
146/// env.get_java_vm()?,
147/// env.new_global_ref(context)?,
148/// env.new_global_ref(JObject::try_from(loader)?)?,
149/// );
150/// }
151/// ```
152pub fn init_with_refs(java_vm: JavaVM, context: GlobalRef, loader: GlobalRef) {
153 GLOBAL.get_or_init(|| Global::Internal {
154 java_vm,
155 context,
156 loader,
157 });
158}
159
160/// Wrapper for JNI errors that will log and clear exceptions
161/// It should generally be preferred to `jni::errors::Error`
162#[derive(Debug)]
163pub(super) struct Error;
164
165impl From<JNIError> for Error {
166 #[track_caller]
167 fn from(cause: JNIError) -> Self {
168 if let JNIError::JavaException = cause {
169 if let Ok(env) = global().env() {
170 // These should not fail if we are already in a throwing state unless
171 // things are very broken. In that case, there isn't much we can do.
172 let _ = env.exception_describe();
173 let _ = env.exception_clear();
174 }
175 }
176
177 Self
178 }
179}
180
181pub(super) struct Context<'a> {
182 env: JNIEnv<'a>,
183 context: GlobalRef,
184 loader: GlobalRef,
185}
186
187impl<'a> Context<'a> {
188 /// Borrow a reference to the JNI Environment executing the Android application
189 pub(super) fn env(&mut self) -> &mut JNIEnv<'a> {
190 &mut self.env
191 }
192
193 /// Borrow the `applicationContext` from the Android application
194 /// <https://developer.android.com/reference/android/app/Application>
195 pub(super) fn application_context(&self) -> &JObject<'a> {
196 &self.context
197 }
198
199 /// Load a class from the application class loader
200 ///
201 /// This should be used instead of `JNIEnv::find_class` to ensure all classes
202 /// in the application can be found.
203 pub(super) fn load_class(&mut self, name: &str) -> Result<JClass<'a>, Error> {
204 let name = self.env.new_string(name)?;
205 let class = self.env.call_method(
206 &self.loader,
207 "loadClass",
208 "(Ljava/lang/String;)Ljava/lang/Class;",
209 &[JValue::from(&name)],
210 )?;
211
212 Ok(JObject::try_from(class)?.into())
213 }
214}
215
216/// Borrow the Android application context and execute the closure
217/// `with_context, ensuring locals are properly freed and exceptions
218/// are cleared.
219pub(super) fn with_context<F, T>(f: F) -> Result<T, Error>
220where
221 F: FnOnce(&mut Context, &mut JNIEnv) -> Result<T, Error>,
222{
223 let mut context = global().context()?;
224 let mut binding = global().context()?;
225 let env = binding.env();
226
227 // 16 is the default capacity in the JVM, we can make this configurable if necessary
228 env.with_local_frame(16, |env| f(&mut context, env))
229}
230
231/// Loads and caches a class on first use
232pub(super) struct CachedClass {
233 name: &'static str,
234 class: OnceCell<GlobalRef>,
235}
236
237impl CachedClass {
238 /// Creates a lazily initialized class reference to the class with `name`.
239 pub(super) const fn new(name: &'static str) -> Self {
240 Self {
241 name,
242 class: OnceCell::new(),
243 }
244 }
245
246 /// Gets the cached class reference, loaded on first use
247 pub(super) fn get<'a: 'b, 'b>(&'a self, cx: &mut Context<'b>) -> Result<&'a JClass<'b>, Error> {
248 let class = self.class.get_or_try_init(|| -> Result<_, Error> {
249 let class = cx.load_class(self.name)?;
250
251 Ok(cx.env().new_global_ref(class)?)
252 })?;
253
254 Ok(class.as_obj().into())
255 }
256}