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        // `password` is the primary password of the profile fixture
497        let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator {
498            password: "password".to_string(),
499        };
500        let nss_key_manager = NSSKeyManager {
501            primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
502        };
503        // key from fixtures/profile/key4.db
504        assert_eq!(
505            nss_key_manager.get_key().unwrap(),
506            [
507                123, 34, 107, 116, 121, 34, 58, 34, 111, 99, 116, 34, 44, 34, 107, 34, 58, 34, 66,
508                74, 104, 84, 108, 103, 51, 118, 56, 49, 65, 66, 51, 118, 87, 50, 71, 122, 54, 104,
509                69, 54, 84, 116, 75, 83, 112, 85, 102, 84, 86, 75, 73, 83, 99, 74, 45, 77, 78, 83,
510                67, 117, 99, 34, 125
511            ]
512            .to_vec()
513        )
514    }
515
516    #[test]
517    fn test_primary_password_authentication() {
518        ensure_initialized_with_profile_dir(profile_path());
519        assert!(authenticate_with_primary_password("password").unwrap());
520    }
521}