diff --git a/Cargo.lock b/Cargo.lock index 24e3d6e..4ccd84c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 3 [[package]] name = "animo-secure-env" -version = "0.3.3" +version = "0.4.0" dependencies = [ "jni", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 983ecf7..7b2b17e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/README.md b/README.md index 4936276..944ebaf 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 | diff --git a/examples/android/Cargo.lock b/examples/android/Cargo.lock index f204e99..78680da 100644 --- a/examples/android/Cargo.lock +++ b/examples/android/Cargo.lock @@ -106,7 +106,7 @@ dependencies = [ [[package]] name = "animo-secure-env" -version = "0.3.2" +version = "0.4.0" dependencies = [ "jni", "lazy_static", @@ -800,9 +800,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" diff --git a/examples/android/src/lib.rs b/examples/android/src/lib.rs index c169cc5..a68af83 100644 --- a/examples/android/src/lib.rs +++ b/examples/android/src/lib.rs @@ -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::() as *mut _) }; + unsafe { Java_id_animo_SecureEnvironment_set_1env(env, clazz) }; + run_tests(); } diff --git a/src/android.rs b/src/android.rs index 42f0305..7577ad2 100644 --- a/src/android.rs +++ b/src/android.rs @@ -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>> = 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 { @@ -138,7 +139,17 @@ pub struct SecureEnvironment; impl SecureEnvironmentOps for SecureEnvironment { fn generate_keypair(id: impl Into) -> SecureEnvResult { - 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()))?; @@ -197,15 +208,20 @@ impl SecureEnvironmentOps 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, @@ -317,11 +333,21 @@ impl SecureEnvironmentOps for SecureEnvironment { UnableToGenerateKey )?; - Ok(Key(key)) + Ok(Key(Arc::new(Mutex::new(*key)))) } fn get_keypair_by_id(id: impl Into) -> SecureEnvResult { - 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()))?; @@ -392,25 +418,58 @@ impl SecureEnvironmentOps 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>); + +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> { - 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 @@ -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) @@ -448,21 +507,28 @@ impl KeyOps for Key { } fn sign(&self, msg: &[u8]) -> SecureEnvResult> { - 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, diff --git a/src/jni_tokens.rs b/src/jni_tokens.rs index 11e0e34..a4d0165 100644 --- a/src/jni_tokens.rs +++ b/src/jni_tokens.rs @@ -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;";