secure_env/
android.rs

1use crate::{
2    error::{SecureEnvError, SecureEnvResult},
3    jni_tokens::*,
4    KeyOps, SecureEnvironmentOps,
5};
6use jni::{
7    objects::{JByteArray, JClass, JObject, JString, JValue},
8    sys::jobject,
9    JNIEnv,
10};
11use lazy_static::lazy_static;
12use libc::c_void;
13use p256::{ecdsa::Signature, elliptic_curve::sec1::ToEncodedPoint};
14use paste::paste;
15use std::sync::{Arc, Mutex};
16use x509_parser::{prelude::FromDer, x509::SubjectPublicKeyInfo};
17
18pub struct AndroidContext(*mut c_void);
19
20unsafe impl Send for AndroidContext {}
21unsafe impl Sync for AndroidContext {}
22
23lazy_static! {
24    static ref JAVA_VM: Arc<Mutex<Option<jni::JavaVM>>> = Arc::new(Mutex::new(None));
25}
26
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,
29#[no_mangle]
30pub extern "system" fn Java_id_animo_SecureEnvironment_set_1env<'local>(
31    env: JNIEnv<'local>,
32    _class: JClass<'local>,
33) {
34    let vm = env.get_java_vm().unwrap();
35    *JAVA_VM.lock().unwrap() = Some(vm);
36}
37
38macro_rules! jni_handle_error {
39    ($env:expr, $err:ident, $e:expr) => {
40        match (|| -> $crate::error::SecureEnvResult<()> {
41            if $env
42                .exception_check()
43                .map_err(|e| $crate::error::SecureEnvError::$err(e.to_string()))?
44            {
45                let throwable = $env
46                    .exception_occurred()
47                    .map_err(|e| $crate::error::SecureEnvError::$err(e.to_string()))?;
48                $env.exception_clear()
49                    .map_err(|e| $crate::error::SecureEnvError::$err(e.to_string()))?;
50
51                let message = $env
52                    .call_method(
53                        &throwable,
54                        EXCEPTION_TO_STRING,
55                        EXCEPTION_TO_STRING_SIG,
56                        &[],
57                    )
58                    .and_then(|v| v.l())
59                    .map_err(|e| {
60                        $crate::error::SecureEnvError::UnableToCreateJavaValue(e.to_string())
61                    })?;
62
63                let msg_rust: String = $env
64                    .get_string(&message.into())
65                    .map_err(|e| {
66                        $crate::error::SecureEnvError::UnableToCreateJavaValue(e.to_string())
67                    })?
68                    .into();
69
70                return Err($crate::error::SecureEnvError::$err(msg_rust));
71            } else {
72                return Err($crate::error::SecureEnvError::$err($e.to_string()));
73            }
74        })() {
75            Ok(_) => $crate::error::SecureEnvError::$err($e.to_string()),
76            Err(e) => e,
77        }
78    };
79}
80
81macro_rules! jni_call_method {
82    ($env:expr, $cls:expr, $method:ident, $args:expr, $ret_typ:ident, $err:ident) => {
83        paste! {
84        $env.call_method($cls, $method, [<$method _SIG>], $args)
85            .and_then(|v| v.$ret_typ())
86            .map_err(|e| jni_handle_error!($env, $err, e))
87        }
88    };
89
90    ($env:expr, $cls:expr, $method:ident, $ret_typ:ident, $err:ident) => {
91        jni_call_method!($env, $cls, $method, &[], $ret_typ, $err)
92    };
93}
94
95macro_rules! jni_call_static_method {
96    ($env:expr, $cls:ident, $method:ident, $args:expr, $ret_typ:ident, $err:ident) => {
97        paste! {{
98            $env.call_static_method([<$cls _CLS>], $method, [<$method _SIG>], $args)
99                .and_then(|v| v.$ret_typ())
100                .map_err(|e| jni_handle_error!($env, $err, e))
101        }}
102    };
103
104    ($env:expr, $cls:ident, $method:ident, $ret_typ:ident, $err:ident) => {
105        jni_call_static_method!($env, $cls, $method, &[], $ret_typ, $err)
106    };
107}
108
109macro_rules! jni_get_static_field {
110    ($env:expr, $cls:expr, $method:ident, $ret_typ:ident, $err:ident) => {
111        paste! {{
112            $env.get_static_field($cls, $method, [<$method _SIG>])
113                .and_then(|v| v.$ret_typ())
114                .map_err(|e| jni_handle_error!($env, $err, e))
115        }}
116    };
117}
118
119macro_rules! jni_new_object {
120    ($env:expr, $cls:ident, $args:expr, $err:ident) => {
121        paste! {{
122            $env.new_object([<$cls _CLS>], [<$cls _CTOR_SIG>], $args)
123                .map_err(|e| jni_handle_error!($env, $err, e))
124        }}
125    };
126}
127
128macro_rules! jni_find_class {
129    ($env:expr, $cls:ident, $err:ident) => {
130        paste! {{
131            $env.find_class([<$cls _CLS>])
132                .map_err(|e| jni_handle_error!($env, $err, e))
133        }}
134    };
135}
136
137#[derive(Debug)]
138pub struct SecureEnvironment;
139
140impl SecureEnvironmentOps<Key> for SecureEnvironment {
141    fn generate_keypair(id: impl Into<String>, backed_by_biometrics: bool) -> SecureEnvResult<Key> {
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
153            .attach_current_thread_as_daemon()
154            .map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
155
156        let id = id.into();
157
158        let id = env
159            .new_string(id)
160            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
161
162        let purpose_sign = jni_get_static_field!(
163            env,
164            KEY_PROPERTIES_CLS,
165            KEY_PROPERTIES_PURPOSE_SIGN,
166            i,
167            UnableToGenerateKey
168        )?;
169
170        let builder = jni_new_object!(
171            env,
172            KEY_GEN_PARAMETER_SPEC_BUILDER,
173            &[(&id).into(), JValue::from(purpose_sign)],
174            UnableToGenerateKey
175        )?;
176
177        let kp_cls = jni_find_class!(env, KEY_PROPERTIES, UnableToGenerateKey)?;
178
179        let digest_sha256 = jni_get_static_field!(
180            env,
181            &kp_cls,
182            KEY_PROPERTIES_DIGEST_SHA256,
183            l,
184            UnableToGenerateKey
185        )?;
186
187        let string_cls = jni_find_class!(env, STRING, UnableToGenerateKey)?;
188
189        let args = env
190            .new_object_array(1, string_cls, &digest_sha256)
191            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
192
193        let builder = jni_call_method!(
194            env,
195            builder,
196            KEY_GEN_PARAMETER_SPEC_BUILDER_SET_DIGESTS,
197            &[(&args).into()],
198            l,
199            UnableToGenerateKey
200        )?;
201
202        let builder = jni_call_method!(
203            env,
204            builder,
205            KEY_GEN_PARAMETER_SPEC_BUILDER_SET_KEY_SIZE,
206            &[JValue::from(256)],
207            l,
208            UnableToGenerateKey
209        )?;
210
211        let builder = if backed_by_biometrics {
212            let auth_biometric_strong = jni_get_static_field!(
213                env,
214                &kp_cls,
215                KEY_PROPERTIES_AUTH_BIOMETRIC_STRONG,
216                i,
217                UnableToGenerateKey
218            )?;
219
220            let builder = jni_call_method!(
221                env,
222                builder,
223                KEY_GEN_PARAMETER_SPEC_BUILDER_SET_USER_AUTHENTICATION_REQUIRED,
224                &[JValue::Bool(1)],
225                l,
226                UnableToGenerateKey
227            )?;
228
229            let builder = jni_call_method!(
230                env,
231                builder,
232                KEY_GEN_PARAMETER_SPEC_BUILDER_SET_INVALIDATED_BY_BIOMETRIC_ENROLLMENT,
233                &[JValue::Bool(1)],
234                l,
235                UnableToGenerateKey
236            )?;
237
238            jni_call_method!(
239                env,
240                builder,
241                KEY_GEN_PARAMETER_SPEC_BUILDER_SET_USER_AUTHENTICATION_PARAMETERS,
242                &[JValue::from(0), auth_biometric_strong.into()],
243                l,
244                UnableToGenerateKey
245            )?
246        } else {
247            builder
248        };
249
250        let current_activity_thread = jni_call_static_method!(
251            env,
252            ACTIVITY_THREAD,
253            ACTIVITY_THREAD_GET_CURRENT_ACTIVITY_THREAD,
254            l,
255            UnableToGenerateKey
256        )?;
257        let ctx = jni_call_method!(
258            env,
259            current_activity_thread,
260            ACTIVITY_THREAD_GET_APPLICATION,
261            l,
262            UnableToGenerateKey
263        )?;
264
265        let package_manager = jni_call_method!(
266            env,
267            ctx,
268            CONTEXT_GET_PACKAGE_MANAGER,
269            l,
270            UnableToGenerateKey
271        )?;
272
273        let hardware_keystore_token = env
274            .new_string("android.hardware.hardware_keystore")
275            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
276
277        // This has not been documented anywhere that I could find.
278        // After some debugging with emulators and multiple real device
279        // (some with a Secure Element (Pixel 6a) and some without (OnePlus Nord))
280        // 300 seems to be the correct cut-off.
281        let required_hardware_keystore_version = 300;
282
283        let has_strongbox_support = jni_call_method!(
284            env,
285            &package_manager,
286            PACKAGE_MANAGER_HAS_SYSTEM_FEATURE,
287            &[
288                (&hardware_keystore_token).into(),
289                required_hardware_keystore_version.into()
290            ],
291            z,
292            UnableToGenerateKey
293        )?;
294
295        let builder = if has_strongbox_support {
296            jni_call_method!(
297                env,
298                &builder,
299                KEY_GEN_PARAMETER_SPEC_BUILDER_SET_IS_STRONG_BOX_BACKED,
300                &[JValue::Bool(1)],
301                l,
302                UnableToGenerateKey
303            )?
304        } else {
305            // 41: Hardware enforcement of device-unlocked keys
306            // TODO: check the exact meaning behind this
307            //       Maybe there is number that corrolates to TEE?
308            //       This seems to work best with testing
309            let required_device_unlocked_keystore_version = 41;
310
311            let has_device_unlocked_keystore_support = jni_call_method!(
312                env,
313                &package_manager,
314                PACKAGE_MANAGER_HAS_SYSTEM_FEATURE,
315                &[
316                    (&hardware_keystore_token).into(),
317                    required_device_unlocked_keystore_version.into()
318                ],
319                z,
320                UnableToGenerateKey
321            )?;
322
323            if !has_device_unlocked_keystore_support {
324                return Err(SecureEnvError::UnableToGenerateKey(
325                    "Unable to generate keypair. Device has insufficient keystore support"
326                        .to_owned(),
327                ));
328            }
329
330            builder
331        };
332
333        let algorithm = env
334            .new_string(EC_ALGORITHM)
335            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
336
337        let provider = env
338            .new_string(ANDROID_KEY_STORE_PROVIDER)
339            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
340
341        let key_pair_generator = jni_call_static_method!(
342            env,
343            KEY_PAIR_GENERATOR,
344            KEY_PAIR_GENERATOR_GET_INSTANCE,
345            &[(&algorithm).into(), (&provider).into()],
346            l,
347            UnableToGenerateKey
348        )?;
349
350        let params = jni_call_method!(
351            env,
352            &builder,
353            KEY_GEN_PARAMETER_SPEC_BUILDER_BUILD,
354            l,
355            UnableToGenerateKey
356        )?;
357
358        jni_call_method!(
359            env,
360            &key_pair_generator,
361            KEY_PAIR_GENERATOR_INITIALIZE,
362            &[(&params).into()],
363            v,
364            UnableToGenerateKey
365        )?;
366
367        let key = jni_call_method!(
368            env,
369            &key_pair_generator,
370            KEY_PAIR_GENERATOR_GENERATE_KEY_PAIR,
371            l,
372            UnableToGenerateKey
373        )?;
374
375        Ok(Key(Arc::new(Mutex::new(*key))))
376    }
377
378    fn get_keypair_by_id(id: impl Into<String>) -> SecureEnvResult<Key> {
379        let jvm = JAVA_VM.lock().map_err(|_| {
380            SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
381        })?;
382
383        let jvm = jvm
384            .as_ref()
385            .ok_or(SecureEnvError::UnableToAttachJVMToThread(
386                "JVM has not been set".to_owned(),
387            ))?;
388
389        let mut env = jvm
390            .attach_current_thread_as_daemon()
391            .map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
392
393        let provider = env
394            .new_string(ANDROID_KEY_STORE_PROVIDER)
395            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
396
397        let id = id.into();
398        let id = env
399            .new_string(id)
400            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
401
402        let key_store = jni_call_static_method!(
403            env,
404            KEY_STORE,
405            KEY_STORE_GET_INSTANCE,
406            &[(&provider).into()],
407            l,
408            UnableToGetKeyPairById
409        )?;
410
411        jni_call_method!(
412            env,
413            &key_store,
414            KEY_STORE_LOAD,
415            &[(&JObject::null()).into()],
416            v,
417            UnableToGetKeyPairById
418        )?;
419
420        let entry = jni_call_method!(
421            env,
422            &key_store,
423            KEY_STORE_GET_ENTRY,
424            &[(&id).into(), (&JObject::null()).into()],
425            l,
426            UnableToGetKeyPairById
427        )?;
428
429        let private_key = jni_call_method!(
430            env,
431            &entry,
432            KEY_STORE_ENTRY_GET_PRIVATE_KEY,
433            l,
434            UnableToGetKeyPairById
435        )?;
436
437        let certificate = jni_call_method!(
438            env,
439            &entry,
440            KEY_STORE_ENTRY_GET_CERTIFICATE,
441            l,
442            UnableToGetKeyPairById
443        )?;
444
445        let public_key = jni_call_method!(
446            env,
447            &certificate,
448            CERTIFICATE_GET_PUBLIC_KEY,
449            l,
450            UnableToGetKeyPairById
451        )?;
452
453        let key_pair = jni_new_object!(
454            env,
455            KEY_PAIR,
456            &[(&public_key).into(), (&private_key).into()],
457            UnableToGetKeyPairById
458        )?;
459
460        Ok(Key(Arc::new(Mutex::new(*key_pair))))
461    }
462}
463
464#[derive(Debug)]
465pub struct Key(Arc<Mutex<jobject>>);
466
467unsafe impl Send for Key {}
468unsafe impl Sync for Key {}
469
470impl Key {
471    unsafe fn get_object(&self) -> JObject {
472        let raw = self.0.lock().unwrap();
473        JObject::from_raw(*raw)
474    }
475}
476
477impl KeyOps for Key {
478    fn get_public_key(&self) -> SecureEnvResult<Vec<u8>> {
479        let jvm = JAVA_VM.lock().map_err(|_| {
480            SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
481        })?;
482
483        let jvm = jvm
484            .as_ref()
485            .ok_or(SecureEnvError::UnableToAttachJVMToThread(
486                "JVM has not been set".to_owned(),
487            ))?;
488
489        let mut env = jvm
490            .attach_current_thread_as_daemon()
491            .map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
492
493        let key = unsafe { self.get_object() };
494
495        let public_key = jni_call_method!(env, &key, KEY_PAIR_GET_PUBLIC, l, UnableToGetPublicKey)?;
496
497        let public_key_encoded = jni_call_method!(
498            env,
499            &public_key,
500            PUBLIC_KEY_GET_ENCODED,
501            l,
502            UnableToGetPublicKey
503        )?;
504
505        let format = jni_call_method!(
506            env,
507            &public_key,
508            PUBLIC_KEY_GET_FORMAT,
509            l,
510            UnableToGetPublicKey
511        )?;
512
513        let format = JString::from(format);
514        let format = env
515            .get_string(&format)
516            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
517        let format = format
518            .to_str()
519            .map_err(|e| SecureEnvError::UnableToGetPublicKey(e.to_string()))?;
520
521        if format != "X.509" {
522            return Err(SecureEnvError::UnableToGetPublicKey(format!(
523                "Unexpected key format. Expected 'X.509', received: '{format}'"
524            )));
525        }
526
527        let public_key: JByteArray = public_key_encoded.into();
528
529        let public_key = env
530            .convert_byte_array(public_key)
531            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
532
533        let spki = SubjectPublicKeyInfo::from_der(&public_key)
534            .map_err(|e| SecureEnvError::UnableToGetPublicKey(e.to_string()))?;
535
536        let spki_data = spki.1.subject_public_key.data;
537
538        let public_key = p256::PublicKey::from_sec1_bytes(&spki_data)
539            .map_err(|e| SecureEnvError::UnableToGetPublicKey(e.to_string()))?;
540
541        let encoded_point = public_key.to_encoded_point(true);
542
543        let public_key = encoded_point.to_bytes().to_vec();
544
545        Ok(public_key)
546    }
547
548    /**
549     *
550     * Signing is an operation that requires authentication. Make sure to manually authenticate
551     * before calling this operation
552     *
553     */
554    fn sign(&self, msg: &[u8]) -> SecureEnvResult<Vec<u8>> {
555        let jvm = JAVA_VM.lock().map_err(|_| {
556            SecureEnvError::UnableToAttachJVMToThread("Could not acquire lock on JVM".to_owned())
557        })?;
558
559        let jvm = jvm
560            .as_ref()
561            .ok_or(SecureEnvError::UnableToAttachJVMToThread(
562                "JVM has not been set".to_owned(),
563            ))?;
564
565        let mut env = jvm
566            .attach_current_thread_as_daemon()
567            .map_err(|e| SecureEnvError::UnableToAttachJVMToThread(e.to_string()))?;
568
569        let key = unsafe { self.get_object() };
570
571        let algorithm = env
572            .new_string(SHA256_WITH_ECDSA_ALGO)
573            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
574
575        let private_key =
576            jni_call_method!(env, &key, KEY_PAIR_GET_PRIVATE, l, UnableToCreateSignature)?;
577
578        let signature_instance = jni_call_static_method!(
579            env,
580            SIGNATURE,
581            SIGNATURE_GET_INSTANCE,
582            &[(&algorithm).into()],
583            l,
584            UnableToCreateSignature
585        )?;
586
587        jni_call_method!(
588            env,
589            &signature_instance,
590            SIGNATURE_INIT_SIGN,
591            &[(&private_key).into()],
592            v,
593            UnableToCreateSignature
594        )?;
595
596        let b_arr = env
597            .byte_array_from_slice(msg)
598            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
599
600        jni_call_method!(
601            env,
602            &signature_instance,
603            SIGNATURE_UPDATE,
604            &[(&b_arr).into()],
605            v,
606            UnableToCreateSignature
607        )?;
608
609        let signature = jni_call_method!(
610            env,
611            &signature_instance,
612            SIGNATURE_SIGN,
613            l,
614            UnableToCreateSignature
615        )?;
616
617        let signature: JByteArray = signature.into();
618
619        let signature = env
620            .convert_byte_array(signature)
621            .map_err(|e| SecureEnvError::UnableToCreateJavaValue(e.to_string()))?;
622
623        let signature = Signature::from_der(&signature)
624            .map_err(|e| SecureEnvError::UnableToCreateSignature(e.to_string()))?;
625
626        let r = signature.r();
627        let s = signature.s();
628        let compact_signature = [r.to_bytes(), s.to_bytes()].concat();
629
630        Ok(compact_signature)
631    }
632}