Skip to content

Commit

Permalink
fix(android): setup JNI support for non-rust Android apps (#32)
Browse files Browse the repository at this point in the history
Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht committed Jun 4, 2024
1 parent 8f3ab48 commit 7bd92a8
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 62 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "animo-secure-env"
version = "0.3.3"
version = "0.4.0"
edition = "2021"
rust-version = "1.67"
license = "Apache-2.0"
Expand Down
34 changes: 27 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

## Supported targets

- `aarch64-apple-ios`
- `aarch64-apple-ios-sim`
- `x86_64-apple-ios`
- `aarch64-linux-android`
- `armv7-linux-androideabi`
- `i686-linux-android`
- `x86_64-linux-android`
- `aarch64-apple-ios`
- `aarch64-apple-ios-sim`
- `x86_64-apple-ios`
- `aarch64-linux-android`
- `armv7-linux-androideabi`
- `i686-linux-android`
- `x86_64-linux-android`

## iOS

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

> 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.
### Additional setup

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:

```java
package id.animo;

public class SecureEnvironment {
static {
System.loadLibrary("secure_env");
}


public static native void set_env();
}

```

Afterwards, you can call `SecureEnvironment.set_env` before making any calls to the library. Afterwards everything should be set up properly.

## Features

| | ios | android |
Expand Down
6 changes: 3 additions & 3 deletions examples/android/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion examples/android/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
use android_activity::AndroidApp;
use mobile_tests::run_tests;

use jni::{JavaVM, JNIEnv, objects::JClass, sys::jobject};

extern "system" {
fn Java_id_animo_SecureEnvironment_set_1env<'local>(env: JNIEnv<'local>, _class: JClass<'local>);
}

#[no_mangle]
fn android_main(_app: AndroidApp) {
fn android_main(app: AndroidApp) {
// Since we cannot use the jvm pointer set by `android_activity` we manually call the exposed
// method with a null pointer for a class (as it is not used anyways) and the jni env we
// receive from the `app`.
let jvm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as *mut _) }.unwrap();
let env = unsafe { JNIEnv::from_raw(jvm.attach_current_thread().unwrap().get_raw()) }.unwrap();
let clazz = unsafe { JClass::from_raw(std::ptr::null::<jobject>() as *mut _) };
unsafe { Java_id_animo_SecureEnvironment_set_1env(env, clazz) };

run_tests();
}
164 changes: 115 additions & 49 deletions src/android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,36 @@ use crate::{
jni_tokens::*,
KeyOps, SecureEnvironmentOps,
};
use jni::objects::{JByteArray, JObject, JString, JValue};
use jni::{
objects::{JByteArray, JClass, JObject, JString, JValue},
sys::{jint, jobject, JNI_VERSION_1_6},
JNIEnv, JavaVM,
};
use lazy_static::lazy_static;
use libc::c_void;
use p256::{ecdsa::Signature, elliptic_curve::sec1::ToEncodedPoint};
use paste::paste;
use std::sync::{Arc, Mutex};
use x509_parser::{prelude::FromDer, x509::SubjectPublicKeyInfo};

lazy_static::lazy_static! {
pub static ref JAVA_VM: jni::JavaVM =
unsafe { jni::JavaVM::from_raw(ndk_context::android_context().vm().cast()) }.unwrap();
pub struct AndroidContext(*mut c_void);

unsafe impl Send for AndroidContext {}
unsafe impl Sync for AndroidContext {}

lazy_static! {
static ref JAVA_VM: Arc<Mutex<Option<jni::JavaVM>>> = Arc::new(Mutex::new(None));
}

/// Android glue code that is called with the pointer to the current activity.
/// With this pointer we can initialize the jvm which is required for JNI.
///
/// 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)
#[cfg(not(feature = "android_testing"))]
// Entry point that can be used to set the pointer to the jvm. It has to be called manually from a
// Java environment,
#[no_mangle]
extern "C" fn ANativeActivity_onCreate(
activity: *mut ndk_sys::ANativeActivity,
_saved_state: *const libc::c_void,
_saved_state_size: libc::size_t,
pub extern "system" fn Java_id_animo_SecureEnvironment_set_1env<'local>(
env: JNIEnv<'local>,
_class: JClass<'local>,
) {
let activity_ptr: libc::intptr_t = activity as _;
let activity: *mut ndk_sys::ANativeActivity = activity_ptr as *mut _;

unsafe {
let jvm: *mut jni::sys::JavaVM = (*activity).vm;
let activity = (*activity).clazz;
ndk_context::initialize_android_context(jvm.cast(), activity.cast());
}
let vm = env.get_java_vm().unwrap();
*JAVA_VM.lock().unwrap() = Some(vm);
}

macro_rules! jni_handle_error {
Expand Down Expand Up @@ -138,7 +139,17 @@ pub struct SecureEnvironment;

impl SecureEnvironmentOps<Key> for SecureEnvironment {
fn generate_keypair(id: impl Into<String>) -> SecureEnvResult<Key> {
let mut env = JAVA_VM
let jvm = JAVA_VM.lock().map_err(|_| {
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
})?;

let jvm = jvm
.as_ref()
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
"JVM has not been set".to_owned(),
))?;

let mut env = jvm
.attach_current_thread_as_daemon()
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;

Expand Down Expand Up @@ -197,15 +208,20 @@ impl SecureEnvironmentOps<Key> for SecureEnvironment {
UnableToGenerateKey
)?;

let ctx = ndk_context::android_context().context() as jni::sys::jobject;

if ctx.is_null() {
return Err(SecureEnvError::UnableToGenerateKey(
"Could not acquire context. Null, unaligned or invalid pointer was found"
.to_owned(),
));
}
let ctx = unsafe { JObject::from_raw(ctx) };
let current_activity_thread = jni_call_static_method!(
env,
ACTIVITY_THREAD,
ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD,
l,
UnableToGenerateKey
)?;
let ctx = jni_call_method!(
env,
current_activity_thread,
ACTIVITY_THREAD_GET_APPLICATION,
l,
UnableToGenerateKey
)?;

let package_manager = jni_call_method!(
env,
Expand Down Expand Up @@ -317,11 +333,21 @@ impl SecureEnvironmentOps<Key> for SecureEnvironment {
UnableToGenerateKey
)?;

Ok(Key(key))
Ok(Key(Arc::new(Mutex::new(*key))))
}

fn get_keypair_by_id(id: impl Into<String>) -> SecureEnvResult<Key> {
let mut env = JAVA_VM
let jvm = JAVA_VM.lock().map_err(|_| {
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
})?;

let jvm = jvm
.as_ref()
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
"JVM has not been set".to_owned(),
))?;

let mut env = jvm
.attach_current_thread_as_daemon()
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;

Expand Down Expand Up @@ -392,25 +418,58 @@ impl SecureEnvironmentOps<Key> for SecureEnvironment {
UnableToGetKeyPairById
)?;

Ok(Key(key_pair))
Ok(Key(Arc::new(Mutex::new(*key_pair))))
}
}

#[derive(Debug)]
pub struct Key(JObject<'static>);
pub struct Key(Arc<Mutex<jobject>>);

unsafe impl Send for Key {}
unsafe impl Sync for Key {}

impl Key {
unsafe fn get_object(&self) -> JObject {
let raw = self.0.lock().unwrap();
JObject::from_raw(*raw)
}
}

impl KeyOps for Key {
fn get_public_key(&self) -> SecureEnvResult<Vec<u8>> {
let mut env = JAVA_VM
let jvm = JAVA_VM.lock().map_err(|_| {
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
})?;

let jvm = jvm
.as_ref()
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
"JVM has not been set".to_owned(),
))?;

let mut env = jvm
.attach_current_thread_as_daemon()
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;

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

let public_key =
jni_call_method!(env, &p, PUBLIC_KEY_GET_ENCODED, l, UnableToGetPublicKey)?;
let public_key = jni_call_method!(env, &key, KEY_PAIR_GET_PUBLIC, l, UnableToGetPublicKey)?;

let format = jni_call_method!(env, &p, PUBLIC_KEY_GET_FORMAT, l, UnableToGetPublicKey)?;
let public_key_encoded = jni_call_method!(
env,
&public_key,
PUBLIC_KEY_GET_ENCODED,
l,
UnableToGetPublicKey
)?;

let format = jni_call_method!(
env,
&public_key,
PUBLIC_KEY_GET_FORMAT,
l,
UnableToGetPublicKey
)?;

let format = JString::from(format);
let format = env
Expand All @@ -426,7 +485,7 @@ impl KeyOps for Key {
)));
}

let public_key: JByteArray = public_key.into();
let public_key: JByteArray = public_key_encoded.into();

let public_key = env
.convert_byte_array(public_key)
Expand All @@ -448,21 +507,28 @@ impl KeyOps for Key {
}

fn sign(&self, msg: &[u8]) -> SecureEnvResult<Vec<u8>> {
let mut env = JAVA_VM
let jvm = JAVA_VM.lock().map_err(|_| {
SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
})?;

let jvm = jvm
.as_ref()
.ok_or(SecureEnvError::UnableToAttachJVMToThread(
"JVM has not been set".to_owned(),
))?;

let mut env = jvm
.attach_current_thread_as_daemon()
.map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;

let key = unsafe { self.get_object() };

let algorithm = env
.new_string(SHA256_WITH_ECDSA_ALGO)
.map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;

let private_key = jni_call_method!(
env,
&self.0,
KEY_PAIR_GET_PRIVATE,
l,
UnableToCreateSignature
)?;
let private_key =
jni_call_method!(env, &key, KEY_PAIR_GET_PRIVATE, l, UnableToCreateSignature)?;

let signature_instance = jni_call_static_method!(
env,
Expand Down
8 changes: 8 additions & 0 deletions src/jni_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,11 @@ pub static SIGNATURE_UPDATE_SIG: &str = "([B)V";

pub static SIGNATURE_SIGN: &str = "sign";
pub static SIGNATURE_SIGN_SIG: &str = "()[B";

pub static ACTIVITY_THREAD_CLS: &str = "android/app/ActivityThread";

pub static ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD: &str = "currentActivityThread";
pub static ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD_SIG: &str = "()Landroid/app/ActivityThread;";

pub static ACTIVITY_THREAD_GET_APPLICATION: &str = "getApplication";
pub static ACTIVITY_THREAD_GET_APPLICATION_SIG: &str = "()Landroid/app/Application;";

0 comments on commit 7bd92a8

Please sign in to comment.