rustls_platform_verifier/
android.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
//! A small wrapper over interacting with the JNI in a type-safe way.

use jni::errors::Error as JNIError;
use jni::objects::{GlobalRef, JClass, JObject, JValue};
use jni::{JNIEnv, JavaVM};
use once_cell::sync::OnceCell;

static GLOBAL: OnceCell<Global> = OnceCell::new();

/// A layer to access the Android runtime which is hosting the current
/// application process.
///
/// Generally this trait should be implemented in your Rust app component's FFI
/// initialization layer.
pub trait Runtime: Send + Sync {
    /// Returns a handle to the current process' JVM.
    fn java_vm(&self) -> &JavaVM;
    /// Returns a reference to the current app's [Context].
    ///
    /// [Context]: <https://developer.android.com/reference/android/content/Context>
    fn context(&self) -> &GlobalRef;
    /// Returns a reference to the class returned by the current JVM's `getClassLoader` call.
    fn class_loader(&self) -> &GlobalRef;
}

enum Global {
    Internal {
        java_vm: JavaVM,
        context: GlobalRef,
        loader: GlobalRef,
    },
    External(&'static dyn Runtime),
}

impl Global {
    fn env(&self) -> Result<JNIEnv, Error> {
        let vm = match self {
            Global::Internal { java_vm, .. } => java_vm,
            Global::External(global) => global.java_vm(),
        };
        Ok(vm.attach_current_thread_permanently()?)
    }

    fn context(&self) -> Result<Context, Error> {
        let env = self.env()?;

        let context = match self {
            Global::Internal { context, .. } => context,
            Global::External(global) => global.context(),
        };

        let loader = match self {
            Global::Internal { loader, .. } => loader,
            Global::External(global) => global.class_loader(),
        };

        Ok(Context {
            env,
            context: JObject::from(context),
            loader: JObject::from(loader),
        })
    }
}

fn global() -> &'static Global {
    GLOBAL
        .get()
        .expect("Expect rustls-platform-verifier to be initialized")
}

/// Initializes and stores the required context for the Android platform.
///
/// This method will setup and store an envrionment locally. This is useful if
/// nothing else in your application needs access the Android runtime.
///
/// Initialization must be done before any verification is attempted.
pub fn init_hosted(env: &JNIEnv, context: JObject) -> Result<(), JNIError> {
    GLOBAL.get_or_try_init(|| -> Result<_, JNIError> {
        let loader =
            env.call_method(context, "getClassLoader", "()Ljava/lang/ClassLoader;", &[])?;
        let global = Global::Internal {
            java_vm: env.get_java_vm()?,
            context: env.new_global_ref(context)?,
            loader: env.new_global_ref(JObject::try_from(loader)?)?,
        };

        Ok(global)
    })?;

    Ok(())
}

/// Initializes and stores the required context for the Android platform.
///
/// This method utilizes an existing Android runtime envrionment and set anything
/// else up on its own. This is useful if your application already interacts with
/// the runtime and has pre-existing handles.
///
/// This function will never panic, and is therefore safe to use at FFI boundaries.
///
/// Initialization must be done before any verification is attempted.
pub fn init_external(runtime: &'static dyn Runtime) {
    GLOBAL.get_or_init(|| Global::External(runtime));
}

/// Wrapper for JNI errors that will log and clear exceptions
/// It should generally be preferred to `jni::errors::Error`
#[derive(Debug)]
pub(super) struct Error;

impl From<JNIError> 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();
            }
        }

        Self
    }
}

pub(super) struct Context<'a> {
    env: JNIEnv<'a>,
    context: JObject<'a>,
    loader: JObject<'a>,
}

impl<'a> Context<'a> {
    /// Borrow a reference to the JNI Environment executing the Android application
    pub(super) fn env(&self) -> &JNIEnv<'a> {
        &self.env
    }

    /// Borrow the `applicationContext` from the Android application
    /// <https://developer.android.com/reference/android/app/Application>
    pub(super) fn application_context(&self) -> &JObject<'a> {
        &self.context
    }

    /// Load a class from the application class loader
    ///
    /// This should be used instead of `JNIEnv::find_class` to ensure all classes
    /// in the application can be found.
    pub(super) fn load_class(&self, name: &str) -> Result<JClass<'a>, Error> {
        let env = self.env();
        let name = env.new_string(name)?;
        let class = env.call_method(
            self.loader,
            "loadClass",
            "(Ljava/lang/String;)Ljava/lang/Class;",
            &[JValue::from(name)],
        )?;

        Ok(JObject::try_from(class)?.into())
    }
}

/// Borrow the Android application context and execute the closure
/// `with_context, ensuring locals are properly freed and exceptions
/// are cleared.
pub(super) fn with_context<F, T>(f: F) -> Result<T, Error>
where
    F: FnOnce(&Context) -> Result<T, Error>,
{
    let context = global().context()?;
    let env = context.env();

    // 16 is the default capacity in the JVM, we can make this configurable if necessary
    env.push_local_frame(16)?;

    let res = f(&context);

    env.pop_local_frame(JObject::null())?;

    res
}

/// Loads and caches a class on first use
pub(super) struct CachedClass {
    name: &'static str,
    class: OnceCell<GlobalRef>,
}

impl CachedClass {
    /// Creates a lazily initialized class reference to the class with `name`.
    pub(super) const fn new(name: &'static str) -> Self {
        Self {
            name,
            class: OnceCell::new(),
        }
    }

    /// Gets the cached class reference, loaded on first use
    pub(super) fn get<'a: 'b, 'b>(&'a self, cx: &Context<'b>) -> Result<JClass<'b>, 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(JClass::from(class.as_obj()))
    }
}