android_native_keyring_store/
credential.rs1use std::{
2 collections::HashMap,
3 sync::{Arc, Mutex},
4};
5
6use jni::{JNIEnv, JavaVM};
7use keyring_core::{
8 Credential, Entry,
9 api::{CredentialApi, CredentialStoreApi},
10};
11
12use crate::{
13 cipher::{Cipher, GCMParameterSpec},
14 keystore::{Key, KeyGenParameterSpecBuilder, KeyGenerator, KeyStore},
15 shared_preferences::{Context, SharedPreferences},
16};
17
18pub const KEY_ALGORITHM_AES: &str = "AES";
19pub const PROVIDER: &str = "AndroidKeyStore";
20pub const PURPOSE_ENCRYPT: i32 = 1;
21pub const PURPOSE_DECRYPT: i32 = 2;
22pub const BLOCK_MODE_GCM: &str = "GCM";
23pub const ENCRYPTION_PADDING_NONE: &str = "NoPadding";
24pub const MODE_PRIVATE: i32 = 0;
25pub const ENCRYPT_MODE: i32 = 1;
26pub const DECRYPT_MODE: i32 = 2;
27pub const CIPHER_TRANSFORMATION: &str = "AES/GCM/NoPadding";
28pub const IV_LEN: usize = 12;
29
30pub struct AndroidStore {
31 java_vm: Arc<JavaVM>,
32 context: Context,
33 instance_id: String,
34}
35
36impl std::fmt::Debug for AndroidStore {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_struct("Store")
39 .field("vendor", &self.vendor())
40 .field("id", &self.id())
41 .field("context", &self.context.id())
42 .finish()
43 }
44}
45
46impl AndroidStore {
47 pub fn from_ndk_context() -> AndroidKeyringResult<Arc<Self>> {
50 let ctx = ndk_context::android_context();
51 let vm = ctx.vm().cast();
52 let activity = ctx.context();
53
54 let java_vm = unsafe { JavaVM::from_raw(vm)? };
55 let env = java_vm.attach_current_thread()?;
56
57 let j_context = unsafe { jni::objects::JObject::from_raw(activity as jni::sys::jobject) };
58 let context = Context::new(&env, j_context)?;
59 let java_vm = Arc::new(env.get_java_vm()?);
60 let instance_id = generate_instance_id();
61 Ok(Arc::new(Self { java_vm, context, instance_id }))
62 }
63}
64
65impl CredentialStoreApi for AndroidStore {
66 fn vendor(&self) -> String {
67 "Android SharedPreferences/KeyStore, https://github.com/open-source-cooperative/android-native-keyring-store".to_string()
68 }
69
70 fn id(&self) -> String {
71 self.instance_id.clone()
72 }
73
74 fn build(
75 &self,
76 service: &str,
77 user: &str,
78 _modifiers: Option<&HashMap<&str, &str>>,
79 ) -> keyring_core::Result<Entry> {
80 let credential =
81 AndroidCredential::new(self.java_vm.clone(), self.context.clone(), service, user);
82
83 Ok(Entry::new_with_credential(Arc::new(credential)))
84 }
85
86 fn as_any(&self) -> &dyn std::any::Any {
87 self
88 }
89
90 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 std::fmt::Debug::fmt(self, f)
92 }
93}
94
95pub struct AndroidCredential {
96 java_vm: Arc<JavaVM>,
97 context: Context,
98 service: String,
99 user: String,
100}
101
102impl std::fmt::Debug for AndroidCredential {
103 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("AndroidCredential")
105 .field("service", &self.service)
106 .field("user", &self.user)
107 .finish()
108 }
109}
110
111impl AndroidCredential {
112 pub fn new(java_vm: Arc<JavaVM>, context: Context, service: &str, user: &str) -> Self {
113 Self {
114 java_vm,
115 context,
116 service: service.to_owned(),
117 user: user.to_owned(),
118 }
119 }
120
121 fn get_key(env: &mut JNIEnv, service: &str) -> AndroidKeyringResult<Key> {
122 static SERVICE_LOCK: Mutex<()> = Mutex::new(());
123 let _lock = SERVICE_LOCK.lock().unwrap();
124
125 let keystore = KeyStore::get_instance(env, PROVIDER)?;
126 keystore.load(env)?;
127
128 Ok(match keystore.get_key(env, service)? {
129 Some(key) => key,
130 None => {
131 let key_generator_spec = KeyGenParameterSpecBuilder::new(
132 env,
133 service,
134 PURPOSE_DECRYPT | PURPOSE_ENCRYPT,
135 )?
136 .set_block_modes(env, &[BLOCK_MODE_GCM])?
137 .set_encryption_paddings(env, &[ENCRYPTION_PADDING_NONE])?
138 .set_user_authentication_required(env, false)?
139 .build(env)?;
140 let key_generator = KeyGenerator::get_instance(env, KEY_ALGORITHM_AES, PROVIDER)?;
141 key_generator.init(env, key_generator_spec.into())?;
142 let key = key_generator.generate_key(env)?;
143 key.into()
144 }
145 })
146 }
147
148 fn get_file(
149 env: &mut JNIEnv,
150 context: &Context,
151 service: &str,
152 ) -> AndroidKeyringResult<SharedPreferences> {
153 Ok(context.get_shared_preferences(env, service, MODE_PRIVATE)?)
154 }
155}
156
157impl CredentialApi for AndroidCredential {
158 fn set_secret(&self, secret: &[u8]) -> keyring_core::Result<()> {
159 self.check_for_exception(|env| {
160 let file = Self::get_file(env, &self.context, &self.service)?;
161 let key = Self::get_key(env, &self.service)?;
162
163 let cipher = Cipher::get_instance(env, CIPHER_TRANSFORMATION)?;
164 cipher.init(env, ENCRYPT_MODE, &key)?;
165 let iv = cipher.get_iv(env)?;
166 assert_eq!(
167 iv.len(),
168 IV_LEN,
169 "IV should always be 12 bytes, please file a bug report"
170 );
171 let ciphertext = cipher.do_final(env, secret)?;
172
173 let iv_len = iv.len() as u8;
174
175 let edit = file.edit(env)?;
176 let mut value = vec![iv_len];
177 value.extend_from_slice(&iv);
178 value.extend_from_slice(&ciphertext);
179 edit.put_binary(env, &self.user, &value)?;
180 edit.commit(env)?;
181
182 Ok(())
183 })?;
184
185 Ok(())
186 }
187
188 fn get_secret(&self) -> keyring_core::Result<Vec<u8>> {
189 let r = self.check_for_exception(|env| {
190 let file = Self::get_file(env, &self.context, &self.service)?;
191 let key = Self::get_key(env, &self.service)?;
192 let ciphertext = file.get_binary(env, &self.user)?;
193
194 Ok(match ciphertext {
195 Some(data) => {
196 if data.is_empty() {
197 return Err(AndroidKeyringError::CorruptedData(
198 data,
199 CorruptedData::MissingIvLen,
200 ));
201 }
202
203 let iv_len = data[0] as usize;
204
205 if iv_len != IV_LEN {
206 return Err(AndroidKeyringError::CorruptedData(
207 data,
208 CorruptedData::InvalidIvLen(iv_len),
209 ));
210 }
211
212 let ciphertext = &data[1..];
213 let ciphertext_len = ciphertext.len();
214 if ciphertext_len <= iv_len {
215 return Err(AndroidKeyringError::CorruptedData(
216 data,
217 CorruptedData::DataTooSmall(ciphertext_len),
218 ));
219 }
220
221 let iv = &ciphertext[..iv_len];
222 let iv = &iv[..iv_len];
223 let ciphertext = &ciphertext[iv_len..];
224
225 let spec = GCMParameterSpec::new(env, 128, iv)?;
226 let cipher = Cipher::get_instance(env, CIPHER_TRANSFORMATION)?;
227 cipher.init2(env, DECRYPT_MODE, &key, spec.into())?;
228 let plaintext = cipher.do_final(env, ciphertext).map_err(move |_| {
229 AndroidKeyringError::CorruptedData(data, CorruptedData::DecryptionFailure)
230 })?;
231
232 Some(plaintext)
233 }
234 None => None,
235 })
236 })?;
237
238 match r {
239 Some(r) => Ok(r),
240 None => Err(keyring_core::Error::NoEntry),
241 }
242 }
243
244 fn delete_credential(&self) -> keyring_core::Result<()> {
245 self.check_for_exception(|env| {
246 let file = Self::get_file(env, &self.context, &self.service)?;
247 let edit = file.edit(env)?;
248 edit.remove(env, &self.user)?.commit(env)?;
249 edit.commit(env)?;
250 Ok(())
251 })?;
252
253 Ok(())
254 }
255
256 fn get_credential(&self) -> keyring_core::Result<Option<Arc<Credential>>> {
257 self.get_secret()?;
258 Ok(None)
259 }
260
261 fn get_specifiers(&self) -> Option<(String, String)> {
262 Some((self.service.clone(), self.user.clone()))
263 }
264
265 fn as_any(&self) -> &dyn std::any::Any {
266 self
267 }
268
269 fn debug_fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 std::fmt::Debug::fmt(self, f)
271 }
272}
273
274pub trait HasJavaVm {
275 fn java_vm(&self) -> &JavaVM;
276 fn check_for_exception<T, F>(&self, f: F) -> AndroidKeyringResult<T>
277 where
278 F: FnOnce(&mut JNIEnv) -> AndroidKeyringResult<T>,
279 {
280 let vm = self.java_vm();
281 let mut env = vm.attach_current_thread()?;
282 let t_result = f(&mut env);
283 if env.exception_check()? {
284 env.exception_describe()?;
285 env.exception_clear()?;
286
287 if t_result.is_ok() {
288 return Err(AndroidKeyringError::JavaExceptionThrow);
289 }
290 }
291
292 t_result
293 }
294}
295impl HasJavaVm for AndroidStore {
296 fn java_vm(&self) -> &JavaVM {
297 &self.java_vm
298 }
299}
300impl HasJavaVm for AndroidCredential {
301 fn java_vm(&self) -> &JavaVM {
302 &self.java_vm
303 }
304}
305
306#[derive(thiserror::Error, Debug)]
307pub enum AndroidKeyringError {
308 #[error(transparent)]
309 JniError(#[from] jni::errors::Error),
310 #[error("Java exception was thrown")]
311 JavaExceptionThrow,
312 #[error("{1}")]
313 CorruptedData(Vec<u8>, CorruptedData),
314}
315impl From<AndroidKeyringError> for keyring_core::Error {
316 fn from(value: AndroidKeyringError) -> Self {
317 match value {
318 AndroidKeyringError::JniError(error) => {
319 keyring_core::Error::PlatformFailure(Box::new(error))
320 }
321 e @ AndroidKeyringError::JavaExceptionThrow => {
322 keyring_core::Error::PlatformFailure(Box::new(e))
323 }
324 AndroidKeyringError::CorruptedData(data, error) => {
325 keyring_core::Error::BadDataFormat(data, Box::new(error))
326 }
327 }
328 }
329}
330type AndroidKeyringResult<T> = Result<T, AndroidKeyringError>;
331
332#[derive(thiserror::Error, Debug)]
333pub enum CorruptedData {
334 #[error("IV length not specified on entry")]
335 MissingIvLen,
336 #[error("IV length in data is {0}, but should be {expected}", expected=IV_LEN)]
337 InvalidIvLen(usize),
338 #[error("Data is too small to contain IV and ciphertext, length = {0}")]
339 DataTooSmall(usize),
340 #[error("Verification of data signature/MAC failed")]
341 DecryptionFailure,
342}
343
344
345fn generate_instance_id() -> String {
346 use std::time::{SystemTime, UNIX_EPOCH};
347
348 let now = SystemTime::now();
349 let elapsed = if now.lt(&UNIX_EPOCH) {
350 UNIX_EPOCH.duration_since(now).unwrap()
351 } else {
352 now.duration_since(UNIX_EPOCH).unwrap()
353 };
354
355 format!(
356 "Crate version {}, Instantiated at {}",
357 env!("CARGO_PKG_VERSION"),
358 elapsed.as_secs_f64()
359 )
360}