android_native_keyring_store/
credential.rs

1use 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    /// Initializes AndroidBuilder using the JNI context available
34    /// on the `ndk-context` crate.
35    #[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}