use crate::error::{KeyringError, Result};
use crate::*;
use jni::errors::Error;
use jni::errors::Result as JniResult;
use jni::objects::{Global, JClass, JObject, JString, JValue, JValueOwned};
use jni::{jni_sig, jni_str, Env, JavaVM};
macro_rules! check_exception {
($env:expr, $err:expr, $message:expr) => {
if $env.exception_check() {
let _ = $env.exception_clear();
$err = Some(KeyringError::AndroidKeystoreError($message.to_owned()));
return Err(Error::JavaException);
}
};
}
pub type AndroidKeyringContext = (JavaVM, Global<JObject<'static>>);
pub struct AndroidKeyringManager {
vm: JavaVM,
encrypted_shared_preferences: Global<JObject<'static>>,
_class_loader: Global<JObject<'static>>,
}
impl Drop for AndroidKeyringManager {
fn drop(&mut self) {
self.vm
.attach_current_thread(|_| JniResult::Ok(()))
.unwrap();
}
}
impl AndroidKeyringManager {
pub fn new(application: &str, ctx: AndroidKeyringContext) -> Result<AndroidKeyringManager> {
let vm = ctx.0;
let context = ctx.1.as_obj();
let application = application.to_owned();
let (encrypted_shared_preferences, class_loader) = vm
.attach_current_thread(|env| {
env.with_local_frame(256, |jnienv| {
let class_loader = get_class_loader(jnienv, context)?;
let master_key = get_master_key(jnienv, context, &class_loader)?;
let encrypted_shared_preferences = get_encrypted_shared_preferences(
jnienv,
application,
context,
&class_loader,
&master_key,
)?;
JniResult::Ok((
jnienv.new_global_ref(encrypted_shared_preferences)?,
jnienv.new_global_ref(class_loader)?,
))
})
})
.map_err(|e| KeyringError::Generic(format!("{}", e)))?;
Ok(AndroidKeyringManager {
vm,
encrypted_shared_preferences,
_class_loader: class_loader,
})
}
pub fn with_keyring<F, T>(&self, service: &str, key: &str, func: F) -> Result<T>
where
F: FnOnce(&mut dyn Keyring) -> Result<T>,
{
self.vm
.attach_current_thread(|env| {
let mut kr = AndroidKeyring::new(self, env, service, key);
JniResult::Ok(func(&mut kr))
})
.map_err(|e| KeyringError::Generic(format!("{}", e)))?
}
}
pub struct AndroidKeyring<'a, 'b> {
manager: &'a AndroidKeyringManager,
jnienv: &'a mut Env<'b>,
service: &'a str,
key: &'a str,
}
impl<'a, 'b> AndroidKeyring<'a, 'b> {
fn new(
manager: &'a AndroidKeyringManager,
jnienv: &'a mut Env<'b>,
service: &'a str,
key: &'a str,
) -> AndroidKeyring<'a, 'b> {
AndroidKeyring {
manager,
jnienv,
service,
key,
}
}
fn make_key_string(&self) -> String {
format!(
"{}\t{}",
crate::escape(self.service),
crate::escape(self.key)
)
}
}
impl<'a, 'b> Keyring for AndroidKeyring<'a, 'b> {
fn set_value(&mut self, value: &str) -> Result<()> {
let mut err: Option<KeyringError> = None;
let key_string = self.make_key_string();
let encrypted_shared_preferences = self.manager.encrypted_shared_preferences.as_obj();
let out = self.jnienv.with_local_frame(256, |jnienv| {
let editor = jnienv
.call_method(
encrypted_shared_preferences,
jni_str!("edit"),
jni_sig!("()Landroid/content/SharedPreferences$Editor;"),
&[],
)?
.l()?;
check_exception!(jnienv, err, "Couldn't edit shared preferences");
let k = get_string_jvalue(jnienv, &key_string)?;
let v = get_string_jvalue(jnienv, &crate::escape(value))?;
let editor = jnienv
.call_method(
editor,
jni_str!("putString"),
jni_sig!("(Ljava/lang/String;Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"),
&[ k.borrow(), v.borrow() ],
)
?
.l()
?;
check_exception!(jnienv, err, "Couldn't put string to shared preferences");
let _ = jnienv.call_method(editor, jni_str!("apply"), jni_sig!("()V"), &[])?;
check_exception!(jnienv, err, "Couldn't put apply changes to shared preferences");
Ok(())
});
if let Some(err) = err {
return Err(err);
}
out.map_err(|e| KeyringError::AndroidKeystoreError(format!("{}", e)))
}
fn get_value(&self) -> Result<String> {
let mut err: Option<KeyringError> = None;
let key_string = self.make_key_string();
let encrypted_shared_preferences = self.manager.encrypted_shared_preferences.as_obj();
let out = self.jnienv.with_local_frame(256, |jnienv| {
let k = get_string_jvalue(jnienv, &key_string)?;
let exists = jnienv
.call_method(
self.manager.encrypted_shared_preferences.as_obj(),
jni_str!("contains"),
jni_sig!("(Ljava/lang/String;)Z"),
&[k.borrow()],
)?
.z()?;
check_exception!(
jnienv,
err,
"Couldn't check existence of string from shared preferences"
);
if !exists {
err = Some(KeyringError::NoPasswordFound);
return Ok(String::new());
}
let value = jnienv
.call_method(
encrypted_shared_preferences,
jni_str!("getString"),
jni_sig!("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
&[k.borrow(), JValue::Object(&JObject::null())],
)?
.l()?;
check_exception!(jnienv, err, "Couldn't get string from shared preferences");
let jstr = JString::cast_local(jnienv, value)?;
Ok(jstr.to_string())
});
if let Some(err) = err {
return Err(err);
}
let out = out.map_err(|e| KeyringError::AndroidKeystoreError(format!("{}", e)))?;
let out = crate::unescape(&out).map_err(map_to_generic)?;
Ok(out)
}
fn delete_value(&mut self) -> Result<()> {
let mut err: Option<KeyringError> = None;
let key_string = self.make_key_string();
let encrypted_shared_preferences = self.manager.encrypted_shared_preferences.as_obj();
let out = self.jnienv.with_local_frame(256, |jnienv| {
let k = get_string_jvalue(jnienv, &key_string)?;
let exists = jnienv
.call_method(
encrypted_shared_preferences,
jni_str!("contains"),
jni_sig!("(Ljava/lang/String;)Z"),
&[k.borrow()],
)?
.z()?;
check_exception!(
jnienv,
err,
"Couldn't check existence of string from shared preferences"
);
if !exists {
err = Some(KeyringError::NoPasswordFound);
return Ok(());
}
let editor = jnienv
.call_method(
encrypted_shared_preferences,
jni_str!("edit"),
jni_sig!("()Landroid/content/SharedPreferences$Editor;"),
&[],
)?
.l()?;
check_exception!(jnienv, err, "Couldn't edit shared preferences");
let editor = jnienv
.call_method(
editor,
jni_str!("remove"),
jni_sig!("(Ljava/lang/String;)Landroid/content/SharedPreferences$Editor;"),
&[k.borrow()],
)?
.l()?;
check_exception!(
jnienv,
err,
"Couldn't remove string from shared preferences"
);
let _ = jnienv.call_method(editor, jni_str!("apply"), jni_sig!("()V"), &[])?;
check_exception!(
jnienv,
err,
"Couldn't put apply changes to shared preferences"
);
Ok(())
});
if let Some(err) = err {
return Err(err);
}
out.map_err(|e| KeyringError::AndroidKeystoreError(format!("{}", e)))
}
}
pub fn get_string_jvalue<'a>(jnienv: &mut Env<'a>, s: &str) -> JniResult<JValueOwned<'a>> {
Ok(JValueOwned::from(JObject::from(jnienv.new_string(s)?)))
}
pub fn get_class_loader<'a>(jnienv: &mut Env<'a>, context: &JObject<'a>) -> JniResult<JObject<'a>> {
jnienv
.call_method(
context,
jni_str!("getClassLoader"),
jni_sig!("()Ljava/lang/ClassLoader;"),
&[],
)?
.l()
}
pub fn load_class<'a, 'b>(
jnienv: &mut Env<'a>,
class_loader: &JObject<'b>,
name: &str,
) -> JniResult<JClass<'a>> {
let name = get_string_jvalue(jnienv, name)?;
let class = jnienv
.call_method(
class_loader,
jni_str!("loadClass"),
jni_sig!("(Ljava/lang/String;)Ljava/lang/Class;"),
&[name.borrow()],
)?
.l()?;
JClass::cast_local(jnienv, class)
}
pub fn get_master_key<'b>(
jnienv: &mut Env<'b>,
context: &JObject<'b>,
class_loader: &JObject<'b>,
) -> JniResult<JObject<'b>> {
jnienv.with_local_frame_returning_local::<_, JObject, _>(64, |jnienv| {
let c_kgps_builder = load_class(jnienv, class_loader, "android/security/keystore/KeyGenParameterSpec$Builder")?;
let c_master_key = load_class(jnienv, class_loader, "androidx/security/crypto/MasterKey")?;
let c_key_properties = load_class(jnienv, class_loader,"android/security/keystore/KeyProperties")?;
let c_string = load_class(jnienv, class_loader, "java/lang/String")?;
let c_master_key_builder = load_class(jnienv, class_loader, "androidx/security/crypto/MasterKey$Builder")?;
let sf_dmka = jnienv.get_static_field(
&c_master_key,
jni_str!("DEFAULT_MASTER_KEY_ALIAS"),
jni_sig!("Ljava/lang/String;"),
)?;
let sf_pe =jnienv.get_static_field(
&c_key_properties,
jni_str!("PURPOSE_ENCRYPT"),
jni_sig!("I"),
)?;
let sf_pd = jnienv
.get_static_field(
&c_key_properties,
jni_str!("PURPOSE_DECRYPT"),
jni_sig!("I"),
)?;
let builder = jnienv
.new_object(
c_kgps_builder,
jni_sig!("(Ljava/lang/String;I)V"),
&[
sf_dmka.borrow(),
JValue::Int(sf_pe.i()? | sf_pd.i()?),
],
)
?;
let sf_bm_gcm = jnienv.get_static_field(
&c_key_properties,
jni_str!("BLOCK_MODE_GCM"),
jni_sig!("Ljava/lang/String;"),
)?.l()?;
let sbm_params = jnienv.new_object_array(
1,
&c_string,
sf_bm_gcm,
)?;
let builder = jnienv
.call_method(
builder,
jni_str!("setBlockModes"),
jni_sig!("([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;"),
&[JValue::from(&*sbm_params)],
)?.l()?;
let sf_epn = jnienv.get_static_field(
&c_key_properties,
jni_str!("ENCRYPTION_PADDING_NONE"),
jni_sig!("Ljava/lang/String;"),
)?;
let sep_params = jnienv.new_object_array(
1,
&c_string,
sf_epn.l()?,
)?;
let builder = jnienv
.call_method(
builder,
jni_str!("setEncryptionPaddings"),
jni_sig!("([Ljava/lang/String;)Landroid/security/keystore/KeyGenParameterSpec$Builder;"),
&[JValue::from(&*sep_params)],
)?.l()?;
let sf_dagmks = jnienv
.get_static_field(
&c_master_key,
jni_str!("DEFAULT_AES_GCM_MASTER_KEY_SIZE"),
jni_sig!("I"),
)?;
let builder = jnienv
.call_method(
builder,
jni_str!("setKeySize"),
jni_sig!("(I)Landroid/security/keystore/KeyGenParameterSpec$Builder;"),
&[sf_dagmks.borrow()],
)?.l()?;
let spec = jnienv
.call_method(
builder,
jni_str!("build"),
jni_sig!("()Landroid/security/keystore/KeyGenParameterSpec;"),
&[],
)?;
let mkbuilder = jnienv
.new_object(
&c_master_key_builder,
jni_sig!("(Landroid/content/Context;)V"),
&[JValue::from(context)],
)?;
let mkbuilder = jnienv.call_method(mkbuilder,
jni_str!("setKeyGenParameterSpec"),
jni_sig!("(Landroid/security/keystore/KeyGenParameterSpec;)Landroidx/security/crypto/MasterKey$Builder;"),
&[ spec.borrow() ])?.l()?;
let master_key = jnienv
.call_method(
mkbuilder,
jni_str!("build"),
jni_sig!("()Landroidx/security/crypto/MasterKey;"),
&[],
)?.l()?;
Ok(master_key)
})
}
pub fn get_encrypted_shared_preferences<'b>(
jnienv: &mut Env<'b>,
application: String,
context: &JObject<'b>,
class_loader: &JObject<'b>,
master_key: &JObject<'b>,
) -> JniResult<JObject<'b>> {
jnienv.with_local_frame_returning_local::<_, JObject, _>(64, |jnienv| {
let c_esp = load_class(jnienv, class_loader, "androidx/security/crypto/EncryptedSharedPreferences")?;
let c_esp_pkes = load_class(
jnienv,
class_loader,
"androidx/security/crypto/EncryptedSharedPreferences$PrefKeyEncryptionScheme")?;
let c_esp_pves =load_class(
jnienv,
class_loader,
"androidx/security/crypto/EncryptedSharedPreferences$PrefValueEncryptionScheme")?;
let application = get_string_jvalue(jnienv, &application)?;
let sf_pkes = jnienv.get_static_field(
c_esp_pkes,
jni_str!("AES256_SIV"),
jni_sig!("Landroidx/security/crypto/EncryptedSharedPreferences$PrefKeyEncryptionScheme;"))?;
let sf_pves = jnienv.get_static_field(
c_esp_pves,
jni_str!("AES256_GCM"),
jni_sig!("Landroidx/security/crypto/EncryptedSharedPreferences$PrefValueEncryptionScheme;"))?;
let esp = jnienv.call_static_method(
&c_esp,
jni_str!("create"),
jni_sig!("(Landroid/content/Context;Ljava/lang/String;Landroidx/security/crypto/MasterKey;Landroidx/security/crypto/EncryptedSharedPreferences$PrefKeyEncryptionScheme;Landroidx/security/crypto/EncryptedSharedPreferences$PrefValueEncryptionScheme;)Landroid/content/SharedPreferences;"),
&[
JValue::from(context),
application.borrow(),
JValue::Object(master_key),
sf_pkes.borrow(),
sf_pves.borrow()
])?.l()?;
Ok(esp)
})
}