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