Skip to content

Commit 7bd92a8

Browse files
fix(android): setup JNI support for non-rust Android apps (#32)
Signed-off-by: Berend Sliedrecht <[email protected]>
1 parent 8f3ab48 commit 7bd92a8

File tree

7 files changed

+170
-62
lines changed

7 files changed

+170
-62
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "animo-secure-env"
3-
version = "0.3.3"
3+
version = "0.4.0"
44
edition = "2021"
55
rust-version = "1.67"
66
license = "Apache-2.0"

README.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44

55
## Supported targets
66

7-
- `aarch64-apple-ios`
8-
- `aarch64-apple-ios-sim`
9-
- `x86_64-apple-ios`
10-
- `aarch64-linux-android`
11-
- `armv7-linux-androideabi`
12-
- `i686-linux-android`
13-
- `x86_64-linux-android`
7+
- `aarch64-apple-ios`
8+
- `aarch64-apple-ios-sim`
9+
- `x86_64-apple-ios`
10+
- `aarch64-linux-android`
11+
- `armv7-linux-androideabi`
12+
- `i686-linux-android`
13+
- `x86_64-linux-android`
1414

1515
## iOS
1616

@@ -24,6 +24,26 @@ Beneath these bindings it fully relies on `KeyStore`. During key generation, bas
2424

2525
> NOTE: there still needs to be some additional research done into the exact garantuees that `setUserPresenceRequired` provides. If it means TEE, it is all good.
2626
27+
### Additional setup
28+
29+
Due to time constraints, currently some additional setup is required for Android to fully work. This has to do with accessing the JVM pointer from Rust. If something like [android_activity](https://github.com/rust-mobile/android-activity) is used, take a look at the [android example](./examples/android/src/lib.rs). If this library is used from a React Native context, or native Android app, include the following in your project:
30+
31+
```java
32+
package id.animo;
33+
34+
public class SecureEnvironment {
35+
static {
36+
System.loadLibrary("secure_env");
37+
}
38+
39+
40+
public static native void set_env();
41+
}
42+
43+
```
44+
45+
Afterwards, you can call `SecureEnvironment.set_env` before making any calls to the library. Afterwards everything should be set up properly.
46+
2747
## Features
2848

2949
| | ios | android |

examples/android/Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/android/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
use android_activity::AndroidApp;
22
use mobile_tests::run_tests;
33

4+
use jni::{JavaVM, JNIEnv, objects::JClass, sys::jobject};
5+
6+
extern "system" {
7+
fn Java_id_animo_SecureEnvironment_set_1env<'local>(env: JNIEnv<'local>, _class: JClass<'local>);
8+
}
9+
410
#[no_mangle]
5-
fn android_main(_app: AndroidApp) {
11+
fn android_main(app: AndroidApp) {
12+
// Since we cannot use the jvm pointer set by `android_activity` we manually call the exposed
13+
// method with a null pointer for a class (as it is not used anyways) and the jni env we
14+
// receive from the `app`.
15+
let jvm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as *mut _) }.unwrap();
16+
let env = unsafe { JNIEnv::from_raw(jvm.attach_current_thread().unwrap().get_raw()) }.unwrap();
17+
let clazz = unsafe { JClass::from_raw(std::ptr::null::<jobject>() as *mut _) };
18+
unsafe { Java_id_animo_SecureEnvironment_set_1env(env, clazz) };
19+
620
run_tests();
721
}

src/android.rs

Lines changed: 115 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -3,35 +3,36 @@ use crate::{
33
jni_tokens::*,
44
KeyOps, SecureEnvironmentOps,
55
};
6-
use jni::objects::{JByteArray, JObject, JString, JValue};
6+
use jni::{
7+
objects::{JByteArray, JClass, JObject, JString, JValue},
8+
sys::{jint, jobject, JNI_VERSION_1_6},
9+
JNIEnv, JavaVM,
10+
};
11+
use lazy_static::lazy_static;
12+
use libc::c_void;
713
use p256::{ecdsa::Signature, elliptic_curve::sec1::ToEncodedPoint};
814
use paste::paste;
15+
use std::sync::{Arc, Mutex};
916
use x509_parser::{prelude::FromDer, x509::SubjectPublicKeyInfo};
1017

11-
lazy_static::lazy_static! {
12-
pub static ref JAVA_VM: jni::JavaVM =
13-
unsafe { jni::JavaVM::from_raw(ndk_context::android_context().vm().cast()) }.unwrap();
18+
pub struct AndroidContext(*mut c_void);
19+
20+
unsafe impl Send for AndroidContext {}
21+
unsafe impl Sync for AndroidContext {}
22+
23+
lazy_static! {
24+
static ref JAVA_VM: Arc<Mutex<Option<jni::JavaVM>>> = Arc::new(Mutex::new(None));
1425
}
1526

16-
/// Android glue code that is called with the pointer to the current activity.
17-
/// With this pointer we can initialize the jvm which is required for JNI.
18-
///
19-
/// Code is a modified version of: [android-activity/src/native_activity/glue.rs](https://github.com/rust-mobile/android-activity/blob/0d299300f4120821ae1fcaaf0276129c512c2c96/android-activity/src/native_activity/glue.rs#L829)
20-
#[cfg(not(feature = "android_testing"))]
27+
// Entry point that can be used to set the pointer to the jvm. It has to be called manually from a
28+
// Java environment,
2129
#[no_mangle]
22-
extern "C" fn ANativeActivity_onCreate(
23-
activity: *mut ndk_sys::ANativeActivity,
24-
_saved_state: *const libc::c_void,
25-
_saved_state_size: libc::size_t,
30+
pub extern "system" fn Java_id_animo_SecureEnvironment_set_1env<'local>(
31+
env: JNIEnv<'local>,
32+
_class: JClass<'local>,
2633
) {
27-
let activity_ptr: libc::intptr_t = activity as _;
28-
let activity: *mut ndk_sys::ANativeActivity = activity_ptr as *mut _;
29-
30-
unsafe {
31-
let jvm: *mut jni::sys::JavaVM = (*activity).vm;
32-
let activity = (*activity).clazz;
33-
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
34-
}
34+
let vm = env.get_java_vm().unwrap();
35+
*JAVA_VM.lock().unwrap() = Some(vm);
3536
}
3637

3738
macro_rules! jni_handle_error {
@@ -138,7 +139,17 @@ pub struct SecureEnvironment;
138139

139140
impl SecureEnvironmentOps<Key> for SecureEnvironment {
140141
fn generate_keypair(id: impl Into<String>) -> SecureEnvResult<Key> {
141-
let mut env = JAVA_VM
142+
let jvm = JAVA_VM.lock().map_err(|_| {
143+
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
144+
})?;
145+
146+
let jvm = jvm
147+
.as_ref()
148+
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
149+
"JVM has not been set".to_owned(),
150+
))?;
151+
152+
let mut env = jvm
142153
.attach_current_thread_as_daemon()
143154
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
144155

@@ -197,15 +208,20 @@ impl SecureEnvironmentOps<Key> for SecureEnvironment {
197208
UnableToGenerateKey
198209
)?;
199210

200-
let ctx = ndk_context::android_context().context() as jni::sys::jobject;
201-
202-
if ctx.is_null() {
203-
return Err(SecureEnvError::UnableToGenerateKey(
204-
"Could not acquire context. Null, unaligned or invalid pointer was found"
205-
.to_owned(),
206-
));
207-
}
208-
let ctx = unsafe { JObject::from_raw(ctx) };
211+
let current_activity_thread = jni_call_static_method!(
212+
env,
213+
ACTIVITY_THREAD,
214+
ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD,
215+
l,
216+
UnableToGenerateKey
217+
)?;
218+
let ctx = jni_call_method!(
219+
env,
220+
current_activity_thread,
221+
ACTIVITY_THREAD_GET_APPLICATION,
222+
l,
223+
UnableToGenerateKey
224+
)?;
209225

210226
let package_manager = jni_call_method!(
211227
env,
@@ -317,11 +333,21 @@ impl SecureEnvironmentOps<Key> for SecureEnvironment {
317333
UnableToGenerateKey
318334
)?;
319335

320-
Ok(Key(key))
336+
Ok(Key(Arc::new(Mutex::new(*key))))
321337
}
322338

323339
fn get_keypair_by_id(id: impl Into<String>) -> SecureEnvResult<Key> {
324-
let mut env = JAVA_VM
340+
let jvm = JAVA_VM.lock().map_err(|_| {
341+
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
342+
})?;
343+
344+
let jvm = jvm
345+
.as_ref()
346+
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
347+
"JVM has not been set".to_owned(),
348+
))?;
349+
350+
let mut env = jvm
325351
.attach_current_thread_as_daemon()
326352
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
327353

@@ -392,25 +418,58 @@ impl SecureEnvironmentOps<Key> for SecureEnvironment {
392418
UnableToGetKeyPairById
393419
)?;
394420

395-
Ok(Key(key_pair))
421+
Ok(Key(Arc::new(Mutex::new(*key_pair))))
396422
}
397423
}
398424

399425
#[derive(Debug)]
400-
pub struct Key(JObject<'static>);
426+
pub struct Key(Arc<Mutex<jobject>>);
427+
428+
unsafe impl Send for Key {}
429+
unsafe impl Sync for Key {}
430+
431+
impl Key {
432+
unsafe fn get_object(&self) -> JObject {
433+
let raw = self.0.lock().unwrap();
434+
JObject::from_raw(*raw)
435+
}
436+
}
401437

402438
impl KeyOps for Key {
403439
fn get_public_key(&self) -> SecureEnvResult<Vec<u8>> {
404-
let mut env = JAVA_VM
440+
let jvm = JAVA_VM.lock().map_err(|_| {
441+
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
442+
})?;
443+
444+
let jvm = jvm
445+
.as_ref()
446+
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
447+
"JVM has not been set".to_owned(),
448+
))?;
449+
450+
let mut env = jvm
405451
.attach_current_thread_as_daemon()
406452
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
407453

408-
let p = jni_call_method!(env, &self.0, KEY_PAIR_GET_PUBLIC, l, UnableToGetPublicKey)?;
454+
let key = unsafe { self.get_object() };
409455

410-
let public_key =
411-
jni_call_method!(env, &p, PUBLIC_KEY_GET_ENCODED, l, UnableToGetPublicKey)?;
456+
let public_key = jni_call_method!(env, &key, KEY_PAIR_GET_PUBLIC, l, UnableToGetPublicKey)?;
412457

413-
let format = jni_call_method!(env, &p, PUBLIC_KEY_GET_FORMAT, l, UnableToGetPublicKey)?;
458+
let public_key_encoded = jni_call_method!(
459+
env,
460+
&public_key,
461+
PUBLIC_KEY_GET_ENCODED,
462+
l,
463+
UnableToGetPublicKey
464+
)?;
465+
466+
let format = jni_call_method!(
467+
env,
468+
&public_key,
469+
PUBLIC_KEY_GET_FORMAT,
470+
l,
471+
UnableToGetPublicKey
472+
)?;
414473

415474
let format = JString::from(format);
416475
let format = env
@@ -426,7 +485,7 @@ impl KeyOps for Key {
426485
)));
427486
}
428487

429-
let public_key: JByteArray = public_key.into();
488+
let public_key: JByteArray = public_key_encoded.into();
430489

431490
let public_key = env
432491
.convert_byte_array(public_key)
@@ -448,21 +507,28 @@ impl KeyOps for Key {
448507
}
449508

450509
fn sign(&self, msg: &[u8]) -> SecureEnvResult<Vec<u8>> {
451-
let mut env = JAVA_VM
510+
let jvm = JAVA_VM.lock().map_err(|_| {
511+
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
512+
})?;
513+
514+
let jvm = jvm
515+
.as_ref()
516+
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
517+
"JVM has not been set".to_owned(),
518+
))?;
519+
520+
let mut env = jvm
452521
.attach_current_thread_as_daemon()
453522
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
454523

524+
let key = unsafe { self.get_object() };
525+
455526
let algorithm = env
456527
.new_string(SHA256_WITH_ECDSA_ALGO)
457528
.map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
458529

459-
let private_key = jni_call_method!(
460-
env,
461-
&self.0,
462-
KEY_PAIR_GET_PRIVATE,
463-
l,
464-
UnableToCreateSignature
465-
)?;
530+
let private_key =
531+
jni_call_method!(env, &key, KEY_PAIR_GET_PRIVATE, l, UnableToCreateSignature)?;
466532

467533
let signature_instance = jni_call_static_method!(
468534
env,

src/jni_tokens.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,11 @@ pub static SIGNATURE_UPDATE_SIG: &str = "([B)V";
125125

126126
pub static SIGNATURE_SIGN: &str = "sign";
127127
pub static SIGNATURE_SIGN_SIG: &str = "()[B";
128+
129+
pub static ACTIVITY_THREAD_CLS: &str = "android/app/ActivityThread";
130+
131+
pub static ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD: &str = "currentActivityThread";
132+
pub static ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD_SIG: &str = "()Landroid/app/ActivityThread;";
133+
134+
pub static ACTIVITY_THREAD_GET_APPLICATION: &str = "getApplication";
135+
pub static ACTIVITY_THREAD_GET_APPLICATION_SIG: &str = "()Landroid/app/Application;";

0 commit comments

Comments
 (0)