android_native_keyring_store/
credential.rs

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