1use crate::{
2 cipher::{Cipher, GCMParameterSpec},
3 keystore::{Key, KeyGenParameterSpecBuilder, KeyGenerator, KeyStore},
4 shared_preferences::{Context, SharedPreferences},
5};
6use jni::{JNIEnv, JavaVM};
7use keyring::{
8 Credential,
9 credential::{CredentialApi, CredentialBuilderApi},
10};
11use std::sync::Mutex;
12
13pub const KEY_ALGORITHM_AES: &str = "AES";
14pub const PROVIDER: &str = "AndroidKeyStore";
15pub const PURPOSE_ENCRYPT: i32 = 1;
16pub const PURPOSE_DECRYPT: i32 = 2;
17pub const BLOCK_MODE_GCM: &str = "GCM";
18pub const ENCRYPTION_PADDING_NONE: &str = "NoPadding";
19pub const MODE_PRIVATE: i32 = 0;
20pub const ENCRYPT_MODE: i32 = 1;
21pub const DECRYPT_MODE: i32 = 2;
22pub const CIPHER_TRANSFORMATION: &str = "AES/GCM/NoPadding";
23
24pub struct AndroidBuilder {
25 java_vm: JavaVM,
26 context: Context,
27}
28impl AndroidBuilder {
29 #[cfg(feature = "ndk-context")]
32 pub fn from_ndk_context() -> AndroidKeyringResult<Self> {
33 let ctx = ndk_context::android_context();
34 let vm = ctx.vm().cast();
35 let activity = ctx.context();
36
37 let java_vm = unsafe { jni::JavaVM::from_raw(vm)? };
38 let env = java_vm.attach_current_thread()?;
39
40 let context = unsafe { jni::objects::JObject::from_raw(activity as jni::sys::jobject) };
41 let context = Context::new(&env, context)?;
42
43 Self::new(&env, context)
44 }
45
46 pub fn new(env: &JNIEnv, context: Context) -> AndroidKeyringResult<Self> {
47 let java_vm = env.get_java_vm()?;
48 Ok(Self { java_vm, context })
49 }
50}
51impl CredentialBuilderApi for AndroidBuilder {
52 fn build(
53 &self,
54 _target: Option<&str>,
55 service: &str,
56 user: &str,
57 ) -> keyring::Result<Box<Credential>> {
58 let credential = self
59 .check_for_exception(|env| AndroidCredential::new(env, &self.context, service, user))?;
60
61 Ok(Box::new(credential))
62 }
63
64 fn as_any(&self) -> &dyn std::any::Any {
65 self
66 }
67}
68
69pub struct AndroidCredential {
70 java_vm: JavaVM,
71 key: Key,
72 file: SharedPreferences,
73 user: String,
74}
75impl AndroidCredential {
76 pub fn new(
77 env: &mut JNIEnv,
78 context: &Context,
79 service: &str,
80 user: &str,
81 ) -> AndroidKeyringResult<Self> {
82 let java_vm = env.get_java_vm()?;
83 let key = {
84 static SERVICE_LOCK: Mutex<()> = Mutex::new(());
85 let _lock = SERVICE_LOCK.lock().unwrap();
86 Self::get_key(env, service)?
87 };
88 let file = Self::get_file(env, context, service)?;
89
90 Ok(Self {
91 java_vm,
92 key,
93 file,
94 user: user.to_owned(),
95 })
96 }
97
98 fn get_key(env: &mut JNIEnv, service: &str) -> AndroidKeyringResult<Key> {
99 let keystore = KeyStore::get_instance(env, PROVIDER)?;
100 keystore.load(env)?;
101
102 Ok(match keystore.get_key(env, service)? {
103 Some(key) => key,
104 None => {
105 let key_generator_spec = KeyGenParameterSpecBuilder::new(
106 env,
107 service,
108 PURPOSE_DECRYPT | PURPOSE_ENCRYPT,
109 )?
110 .set_block_modes(env, &[BLOCK_MODE_GCM])?
111 .set_encryption_paddings(env, &[ENCRYPTION_PADDING_NONE])?
112 .set_user_authentication_required(env, false)?
113 .build(env)?;
114 let key_generator = KeyGenerator::get_instance(env, KEY_ALGORITHM_AES, PROVIDER)?;
115 key_generator.init(env, key_generator_spec.into())?;
116 let key = key_generator.generate_key(env)?;
117 key.into()
118 }
119 })
120 }
121
122 fn get_file(
123 env: &mut JNIEnv,
124 context: &Context,
125 service: &str,
126 ) -> AndroidKeyringResult<SharedPreferences> {
127 Ok(context.get_shared_preferences(env, service, MODE_PRIVATE)?)
128 }
129}
130impl CredentialApi for AndroidCredential {
131 fn set_password(&self, password: &str) -> keyring::Result<()> {
132 self.set_secret(password.as_bytes())
133 }
134
135 fn set_secret(&self, password: &[u8]) -> keyring::Result<()> {
136 self.check_for_exception(|env| {
137 let cipher = Cipher::get_instance(env, CIPHER_TRANSFORMATION)?;
138 cipher.init(env, ENCRYPT_MODE, &self.key)?;
139 let iv = cipher.get_iv(env)?;
140 let ciphertext = cipher.do_final(env, password)?;
141
142 let iv_len = iv.len() as u8;
143
144 let edit = self.file.edit(env)?;
145 let mut value = vec![iv_len];
146 value.extend_from_slice(&iv);
147 value.extend_from_slice(&ciphertext);
148 edit.put_binary(env, &self.user, &value)?;
149 edit.commit(env)?;
150
151 Ok(())
152 })?;
153
154 Ok(())
155 }
156
157 fn get_password(&self) -> keyring::Result<String> {
158 let secret = self.get_secret()?;
159 match String::from_utf8(secret) {
160 Ok(str) => Ok(str),
161 Err(e) => Err(keyring::Error::BadEncoding(e.into_bytes())),
162 }
163 }
164
165 fn get_secret(&self) -> keyring::Result<Vec<u8>> {
166 let r = self.check_for_exception(|env| {
167 let ciphertext = self.file.get_binary(env, &self.user)?;
168 Ok(match ciphertext {
169 Some(ciphertext) => {
170 if ciphertext.is_empty() {
171 return Err(AndroidKeyringError::CorruptedData);
172 }
173
174 let iv_len = ciphertext[0] as usize;
175 let ciphertext = &ciphertext[1..];
176 if ciphertext.len() < iv_len {
177 return Err(AndroidKeyringError::CorruptedData);
178 }
179
180 let iv = &ciphertext[..iv_len];
181 let ciphertext = &ciphertext[iv_len..];
182
183 let spec = GCMParameterSpec::new(env, 128, iv)?;
184 let cipher = Cipher::get_instance(env, CIPHER_TRANSFORMATION)?;
185 cipher.init2(env, DECRYPT_MODE, &self.key, spec.into())?;
186 let plaintext = cipher.do_final(env, ciphertext)?;
187
188 Some(plaintext)
189 }
190 None => None,
191 })
192 })?;
193
194 match r {
195 Some(r) => Ok(r),
196 None => Err(keyring::Error::NoEntry),
197 }
198 }
199
200 fn delete_credential(&self) -> keyring::Result<()> {
201 self.check_for_exception(|env| {
202 let edit = self.file.edit(env)?;
203 edit.remove(env, &self.user)?.commit(env)?;
204 Ok(())
205 })?;
206
207 Ok(())
208 }
209
210 fn as_any(&self) -> &dyn std::any::Any {
211 self
212 }
213}
214
215pub trait HasJavaVm {
216 fn java_vm(&self) -> &JavaVM;
217 fn check_for_exception<T, F>(&self, f: F) -> AndroidKeyringResult<T>
218 where
219 F: FnOnce(&mut JNIEnv) -> AndroidKeyringResult<T>,
220 {
221 let vm = self.java_vm();
222 let mut env = vm.attach_current_thread()?;
223 let t_result = f(&mut env);
224 if env.exception_check()? {
225 env.exception_describe()?;
226 env.exception_clear()?;
227 if let Err(e) = t_result {
228 tracing::warn!(%e, "Result::Err being converted into JavaExceptionThrown");
229 tracing::debug!(?e);
230 }
231 return Err(AndroidKeyringError::JavaExceptionThrow);
232 }
233
234 t_result
235 }
236}
237impl HasJavaVm for AndroidBuilder {
238 fn java_vm(&self) -> &JavaVM {
239 &self.java_vm
240 }
241}
242impl HasJavaVm for AndroidCredential {
243 fn java_vm(&self) -> &JavaVM {
244 &self.java_vm
245 }
246}
247
248#[derive(thiserror::Error, Debug)]
249pub enum AndroidKeyringError {
250 #[error(transparent)]
251 JniError(#[from] jni::errors::Error),
252 #[error("Java exception was thrown")]
253 JavaExceptionThrow,
254 #[error("Corrupted data in SharedPreferences")]
255 CorruptedData,
256}
257impl From<AndroidKeyringError> for keyring::Error {
258 fn from(value: AndroidKeyringError) -> Self {
259 Self::PlatformFailure(Box::new(value))
260 }
261}
262type AndroidKeyringResult<T> = Result<T, AndroidKeyringError>;