logins/
encryption.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 */
5
6// This is the *local* encryption support - it has nothing to do with the
7// encryption used by sync.
8
9// For context, what "local encryption" means in this context is:
10// * We use regular sqlite, but ensure that sensitive data is encrypted in the DB in the
11//   `secure_fields` column.  The encryption key is managed by the app.
12// * The `decrypt_struct` and `encrypt_struct` functions are used to convert between an encrypted
13//   `secure_fields` string and a decrypted `SecureFields` struct
14// * Most API functions return `EncryptedLogin` which has its data encrypted.
15//
16// This makes life tricky for Sync - sync has its own encryption and its own
17// management of sync keys. The entire records are encrypted on the server -
18// so the record on the server has the plain-text data (which is then
19// encrypted as part of the entire record), so:
20// * When transforming a record from the DB into a Sync record, we need to
21//   *decrypt* the data.
22// * When transforming a record from Sync into a DB record, we need to *encrypt*
23//   the data.
24//
25// So Sync needs to know the key etc, and that needs to get passed down
26// multiple layers, from the app saying "sync now" all the way down to the
27// low level sync code.
28// To make life a little easier, we do that via a struct.
29//
30// Consumers of the Login component have 3 options for setting up encryption:
31//    1. Implement EncryptorDecryptor directly
32//       eg `LoginStore::new(MyEncryptorDecryptor)`
33//    2. Implement KeyManager and use ManagedEncryptorDecryptor
34//       eg `LoginStore::new(ManagedEncryptorDecryptor::new(MyKeyManager))`
35//    3. Generate a single key and create a StaticKeyManager and use it together with
36//       ManagedEncryptorDecryptor
37//       eg `LoginStore::new(ManagedEncryptorDecryptor::new(StaticKeyManager { key: myKey }))`
38//
39//  You can implement EncryptorDecryptor directly to keep full control over the encryption
40//  algorithm. For example, on the desktop, this could make use of NSS's SecretDecoderRing to
41//  achieve transparent key management.
42//
43//  If the application wants to keep the current encryption, like Android and iOS, for example, but
44//  control the key management itself, the KeyManager can be implemented and the encryption can be
45//  done on the Rust side with the ManagedEncryptorDecryptor.
46//
47//  In tests or some command line tools, it can be practical to use a static key that does not
48//  change at runtime and is already present when the LoginsStore is initialized. In this case, it
49//  makes sense to use the provided StaticKeyManager.
50
51use crate::error::*;
52use std::sync::Arc;
53
54#[cfg(feature = "keydb")]
55use futures::executor::block_on;
56
57#[cfg(feature = "keydb")]
58use async_trait::async_trait;
59
60#[cfg(feature = "keydb")]
61use nss::assert_initialized as assert_nss_initialized;
62#[cfg(feature = "keydb")]
63use nss::pk11::sym_key::{
64    authenticate_with_primary_password, authentication_with_primary_password_is_needed,
65    get_or_create_aes256_key,
66};
67
68/// This is the generic EncryptorDecryptor trait, as handed over to the Store during initialization.
69/// Consumers can implement either this generic trait and bring in their own crypto, or leverage the
70/// ManagedEncryptorDecryptor below, which provides encryption algorithms out of the box.
71///
72/// Note that EncryptorDecryptor must not call any LoginStore methods. The login store can call out
73/// to the EncryptorDecryptor when it's internal mutex is held so calling back in to the LoginStore
74/// may deadlock.
75pub trait EncryptorDecryptor: Send + Sync {
76    fn encrypt(&self, cleartext: Vec<u8>) -> ApiResult<Vec<u8>>;
77    fn decrypt(&self, ciphertext: Vec<u8>) -> ApiResult<Vec<u8>>;
78}
79
80impl<T: EncryptorDecryptor> EncryptorDecryptor for Arc<T> {
81    fn encrypt(&self, clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
82        (**self).encrypt(clearbytes)
83    }
84
85    fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
86        (**self).decrypt(cipherbytes)
87    }
88}
89
90/// The ManagedEncryptorDecryptor makes use of the NSS provided cryptographic algorithms. The
91/// ManagedEncryptorDecryptor uses a KeyManager for encryption key retrieval.
92pub struct ManagedEncryptorDecryptor {
93    key_manager: Arc<dyn KeyManager>,
94}
95
96impl ManagedEncryptorDecryptor {
97    #[uniffi::constructor()]
98    pub fn new(key_manager: Arc<dyn KeyManager>) -> Self {
99        Self { key_manager }
100    }
101}
102
103impl EncryptorDecryptor for ManagedEncryptorDecryptor {
104    fn encrypt(&self, clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
105        let keybytes = self
106            .key_manager
107            .get_key()
108            .map_err(|_| LoginsApiError::MissingKey)?;
109        let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?;
110
111        let encdec = jwcrypto::EncryptorDecryptor::new(key)
112            .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
113
114        let cleartext =
115            std::str::from_utf8(&clearbytes).map_err(|e| LoginsApiError::EncryptionFailed {
116                reason: e.to_string(),
117            })?;
118        encdec
119            .encrypt(cleartext)
120            .map_err(
121                |e: jwcrypto::JwCryptoError| LoginsApiError::EncryptionFailed {
122                    reason: e.to_string(),
123                },
124            )
125            .map(|text| text.into())
126    }
127
128    fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
129        let keybytes = self
130            .key_manager
131            .get_key()
132            .map_err(|_| LoginsApiError::MissingKey)?;
133        let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?;
134
135        let encdec = jwcrypto::EncryptorDecryptor::new(key)
136            .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
137
138        let ciphertext =
139            std::str::from_utf8(&cipherbytes).map_err(|e| LoginsApiError::DecryptionFailed {
140                reason: e.to_string(),
141            })?;
142        encdec
143            .decrypt(ciphertext)
144            .map_err(
145                |e: jwcrypto::JwCryptoError| LoginsApiError::DecryptionFailed {
146                    reason: e.to_string(),
147                },
148            )
149            .map(|text| text.into())
150    }
151}
152
153/// Consumers can implement the KeyManager in combination with the ManagedEncryptorDecryptor to hand
154/// over the encryption key whenever encryption or decryption happens.
155pub trait KeyManager: Send + Sync {
156    fn get_key(&self) -> ApiResult<Vec<u8>>;
157}
158
159/// Last but not least we provide a StaticKeyManager, which can be
160/// used in cases where there is a single key during runtime, for example in tests.
161pub struct StaticKeyManager {
162    key: String,
163}
164
165impl StaticKeyManager {
166    pub fn new(key: String) -> Self {
167        Self { key }
168    }
169}
170
171impl KeyManager for StaticKeyManager {
172    #[handle_error(Error)]
173    fn get_key(&self) -> ApiResult<Vec<u8>> {
174        Ok(self.key.as_bytes().into())
175    }
176}
177
178/// `PrimaryPasswordAuthenticator` is used in conjunction with `NSSKeyManager` to provide the
179/// primary password and the success or failure actions of the authentication process.
180#[cfg(feature = "keydb")]
181#[uniffi::export(with_foreign)]
182#[async_trait]
183pub trait PrimaryPasswordAuthenticator: Send + Sync {
184    /// Get a primary password for authentication, otherwise return the
185    /// AuthenticationCancelled error to cancel the authentication process.
186    async fn get_primary_password(&self) -> ApiResult<String>;
187    async fn on_authentication_success(&self) -> ApiResult<()>;
188    async fn on_authentication_failure(&self) -> ApiResult<()>;
189}
190
191/// Use the `NSSKeyManager` to use NSS for key management.
192///
193/// NSS stores keys in `key4.db` within the profile and wraps the key with a key derived from the
194/// primary password, if set. It defers to the provided `PrimaryPasswordAuthenticator`
195/// implementation to handle user authentication.  Note that if no primary password is set, the
196/// wrapping key is deterministically derived from an empty string.
197///
198/// Make sure to initialize NSS using `ensure_initialized_with_profile_dir` before creating a
199/// NSSKeyManager.
200///
201/// # Examples
202/// ```no_run
203/// use async_trait::async_trait;
204/// use logins::encryption::KeyManager;
205/// use logins::{PrimaryPasswordAuthenticator, LoginsApiError, NSSKeyManager};
206/// use std::sync::Arc;
207///
208/// struct MyPrimaryPasswordAuthenticator {}
209///
210/// #[async_trait]
211/// impl PrimaryPasswordAuthenticator for MyPrimaryPasswordAuthenticator {
212///     async fn get_primary_password(&self) -> Result<String, LoginsApiError> {
213///         // Most likely, you would want to prompt for a password.
214///         // let password = prompt_string("primary password").unwrap_or_default();
215///         Ok("secret".to_string())
216///     }
217///
218///     async fn on_authentication_success(&self) -> Result<(), LoginsApiError> {
219///         println!("success");
220///         Ok(())
221///     }
222///
223///     async fn on_authentication_failure(&self) -> Result<(), LoginsApiError> {
224///         println!("this did not work, please try again:");
225///         Ok(())
226///     }
227/// }
228/// let key_manager = NSSKeyManager::new(Arc::new(MyPrimaryPasswordAuthenticator {}));
229/// assert_eq!(key_manager.get_key().unwrap().len(), 63);
230/// ```
231#[cfg(feature = "keydb")]
232#[derive(uniffi::Object)]
233pub struct NSSKeyManager {
234    primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>,
235}
236
237#[cfg(feature = "keydb")]
238#[uniffi::export]
239impl NSSKeyManager {
240    /// Initialize new `NSSKeyManager` with a given `PrimaryPasswordAuthenticator`.
241    /// There must be a previous initializiation of NSS before initializing
242    /// `NSSKeyManager`, otherwise this panics.
243    #[uniffi::constructor()]
244    pub fn new(primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>) -> Self {
245        assert_nss_initialized();
246        Self {
247            primary_password_authenticator,
248        }
249    }
250
251    pub fn into_dyn_key_manager(self: Arc<Self>) -> Arc<dyn KeyManager> {
252        self
253    }
254}
255
256/// Identifier for the logins key, under which the key is stored in NSS.
257#[cfg(feature = "keydb")]
258static KEY_NAME: &str = "as-logins-key";
259
260// wrapp `authentication_with_primary_password_is_needed` into an ApiResult
261#[cfg(feature = "keydb")]
262fn api_authentication_with_primary_password_is_needed() -> ApiResult<bool> {
263    authentication_with_primary_password_is_needed().map_err(|e: nss::Error| {
264        LoginsApiError::NSSAuthenticationError {
265            reason: e.to_string(),
266        }
267    })
268}
269
270// wrapp `authenticate_with_primary_password` into an ApiResult
271#[cfg(feature = "keydb")]
272fn api_authenticate_with_primary_password(primary_password: &str) -> ApiResult<bool> {
273    authenticate_with_primary_password(primary_password).map_err(|e: nss::Error| {
274        LoginsApiError::NSSAuthenticationError {
275            reason: e.to_string(),
276        }
277    })
278}
279
280#[cfg(feature = "keydb")]
281impl KeyManager for NSSKeyManager {
282    fn get_key(&self) -> ApiResult<Vec<u8>> {
283        if api_authentication_with_primary_password_is_needed()? {
284            let primary_password =
285                block_on(self.primary_password_authenticator.get_primary_password())?;
286            let mut result = api_authenticate_with_primary_password(&primary_password)?;
287
288            if result {
289                block_on(
290                    self.primary_password_authenticator
291                        .on_authentication_success(),
292                )?;
293            } else {
294                while !result {
295                    block_on(
296                        self.primary_password_authenticator
297                            .on_authentication_failure(),
298                    )?;
299
300                    let primary_password =
301                        block_on(self.primary_password_authenticator.get_primary_password())?;
302                    result = api_authenticate_with_primary_password(&primary_password)?;
303                }
304                block_on(
305                    self.primary_password_authenticator
306                        .on_authentication_success(),
307                )?;
308            }
309        }
310
311        let key = get_or_create_aes256_key(KEY_NAME).expect("Could not get or create key via NSS");
312        let mut bytes: Vec<u8> = Vec::new();
313        serde_json::to_writer(
314            &mut bytes,
315            &jwcrypto::Jwk::new_direct_from_bytes(None, &key),
316        )
317        .unwrap();
318        Ok(bytes)
319    }
320}
321
322#[handle_error(Error)]
323pub fn create_canary(text: &str, key: &str) -> ApiResult<String> {
324    Ok(jwcrypto::EncryptorDecryptor::new(key)?.create_canary(text)?)
325}
326
327pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool> {
328    let encdec = jwcrypto::EncryptorDecryptor::new(key)
329        .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
330    Ok(encdec.check_canary(canary, text).unwrap_or(false))
331}
332
333#[handle_error(Error)]
334pub fn create_key() -> ApiResult<String> {
335    Ok(jwcrypto::EncryptorDecryptor::create_key()?)
336}
337
338#[cfg(test)]
339pub mod test_utils {
340    use super::*;
341    use serde::{de::DeserializeOwned, Serialize};
342
343    lazy_static::lazy_static! {
344        pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap();
345        pub static ref TEST_ENCDEC: Arc<ManagedEncryptorDecryptor> = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager { key: TEST_ENCRYPTION_KEY.clone() })));
346    }
347
348    pub fn encrypt_struct<T: Serialize>(fields: &T) -> String {
349        let string = serde_json::to_string(fields).unwrap();
350        let cipherbytes = TEST_ENCDEC.encrypt(string.as_bytes().into()).unwrap();
351        std::str::from_utf8(&cipherbytes).unwrap().to_owned()
352    }
353    pub fn decrypt_struct<T: DeserializeOwned>(ciphertext: String) -> T {
354        let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap();
355        serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap()
356    }
357}
358
359#[cfg(not(feature = "keydb"))]
360#[cfg(test)]
361mod test {
362    use super::*;
363    use nss::ensure_initialized;
364
365    #[test]
366    fn test_static_key_manager() {
367        ensure_initialized();
368        let key = create_key().unwrap();
369        let key_manager = StaticKeyManager { key: key.clone() };
370        assert_eq!(key.as_bytes(), key_manager.get_key().unwrap());
371    }
372
373    #[test]
374    fn test_managed_encdec_with_invalid_key() {
375        ensure_initialized();
376        let key_manager = Arc::new(StaticKeyManager {
377            key: "bad_key".to_owned(),
378        });
379        let encdec = ManagedEncryptorDecryptor { key_manager };
380        assert!(matches!(
381            encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
382            LoginsApiError::InvalidKey
383        ));
384    }
385
386    #[test]
387    fn test_managed_encdec_with_missing_key() {
388        ensure_initialized();
389        struct MyKeyManager {}
390        impl KeyManager for MyKeyManager {
391            fn get_key(&self) -> ApiResult<Vec<u8>> {
392                Err(LoginsApiError::MissingKey)
393            }
394        }
395        let key_manager = Arc::new(MyKeyManager {});
396        let encdec = ManagedEncryptorDecryptor { key_manager };
397        assert!(matches!(
398            encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
399            LoginsApiError::MissingKey
400        ));
401    }
402
403    #[test]
404    fn test_managed_encdec() {
405        ensure_initialized();
406        let key = create_key().unwrap();
407        let key_manager = Arc::new(StaticKeyManager { key });
408        let encdec = ManagedEncryptorDecryptor { key_manager };
409        let cleartext = "secret";
410        let ciphertext = encdec.encrypt(cleartext.as_bytes().into()).unwrap();
411        assert_eq!(
412            encdec.decrypt(ciphertext.clone()).unwrap(),
413            cleartext.as_bytes()
414        );
415        let other_encdec = ManagedEncryptorDecryptor {
416            key_manager: Arc::new(StaticKeyManager {
417                key: create_key().unwrap(),
418            }),
419        };
420
421        assert_eq!(
422            other_encdec.decrypt(ciphertext).err().unwrap().to_string(),
423            "decryption failed: Crypto error: NSS error: NSS error: -8190 "
424        );
425    }
426
427    #[test]
428    fn test_key_error() {
429        let storage_err = jwcrypto::EncryptorDecryptor::new("bad-key").err().unwrap();
430        println!("{storage_err:?}");
431        assert!(matches!(storage_err, jwcrypto::JwCryptoError::InvalidKey));
432    }
433
434    #[test]
435    fn test_canary_functionality() {
436        ensure_initialized();
437        const CANARY_TEXT: &str = "Arbitrary sequence of text";
438        let key = create_key().unwrap();
439        let canary = create_canary(CANARY_TEXT, &key).unwrap();
440        assert!(check_canary(&canary, CANARY_TEXT, &key).unwrap());
441
442        let different_key = create_key().unwrap();
443        assert!(!check_canary(&canary, CANARY_TEXT, &different_key).unwrap());
444
445        let bad_key = "bad_key".to_owned();
446        assert!(matches!(
447            check_canary(&canary, CANARY_TEXT, &bad_key).err().unwrap(),
448            LoginsApiError::InvalidKey
449        ));
450    }
451}
452
453#[cfg(feature = "keydb")]
454#[cfg(test)]
455mod keydb_test {
456    use super::*;
457    use nss::ensure_initialized_with_profile_dir;
458    use std::path::PathBuf;
459
460    struct MockPrimaryPasswordAuthenticator {
461        password: String,
462    }
463
464    #[async_trait]
465    impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
466        async fn get_primary_password(&self) -> ApiResult<String> {
467            Ok(self.password.clone())
468        }
469        async fn on_authentication_success(&self) -> ApiResult<()> {
470            Ok(())
471        }
472        async fn on_authentication_failure(&self) -> ApiResult<()> {
473            Ok(())
474        }
475    }
476
477    fn profile_path() -> PathBuf {
478        std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/profile")
479    }
480
481    #[test]
482    fn test_ensure_initialized_with_profile_dir() {
483        ensure_initialized_with_profile_dir(profile_path());
484    }
485
486    #[test]
487    fn test_create_key() {
488        ensure_initialized_with_profile_dir(profile_path());
489        let key = create_key().unwrap();
490        assert_eq!(key.len(), 63)
491    }
492
493    #[test]
494    fn test_nss_key_manager() {
495        ensure_initialized_with_profile_dir(profile_path());
496        let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator {
497            password: "password".to_string(),
498        };
499        let nss_key_manager = NSSKeyManager {
500            primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
501        };
502        assert_eq!(nss_key_manager.get_key().unwrap().len(), 63)
503    }
504}