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_as::assert_initialized as assert_nss_initialized;
62#[cfg(feature = "keydb")]
63use nss_as::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.
75#[uniffi::trait_interface]
76pub trait EncryptorDecryptor: Send + Sync {
77    fn encrypt(&self, cleartext: Vec<u8>) -> ApiResult<Vec<u8>>;
78    fn decrypt(&self, ciphertext: Vec<u8>) -> ApiResult<Vec<u8>>;
79}
80
81impl<T: EncryptorDecryptor> EncryptorDecryptor for Arc<T> {
82    fn encrypt(&self, clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
83        (**self).encrypt(clearbytes)
84    }
85
86    fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
87        (**self).decrypt(cipherbytes)
88    }
89}
90
91/// A placeholder `EncryptorDecryptor` used after the store has been shut down.
92///
93/// On shutdown we swap the real encryptor (which, for foreign consumers like
94/// Desktop, is a JS-backed callback interface) out for this one. That drops our
95/// reference to the foreign callback so its handle is unregistered during
96/// shutdown rather than lingering. Any call that still reaches it (e.g. via a
97/// stray clone) fails cleanly instead of calling into torn-down foreign code.
98pub struct NoopEncryptorDecryptor;
99
100impl EncryptorDecryptor for NoopEncryptorDecryptor {
101    fn encrypt(&self, _clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
102        Err(LoginsApiError::UnexpectedLoginsApiError {
103            reason: "encrypt called on a shut-down store".to_string(),
104        })
105    }
106
107    fn decrypt(&self, _cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
108        Err(LoginsApiError::UnexpectedLoginsApiError {
109            reason: "decrypt called on a shut-down store".to_string(),
110        })
111    }
112}
113
114/// The ManagedEncryptorDecryptor makes use of the NSS provided cryptographic algorithms. The
115/// ManagedEncryptorDecryptor uses a KeyManager for encryption key retrieval.
116pub struct ManagedEncryptorDecryptor {
117    key_manager: Arc<dyn KeyManager>,
118}
119
120impl ManagedEncryptorDecryptor {
121    #[uniffi::constructor()]
122    pub fn new(key_manager: Arc<dyn KeyManager>) -> Self {
123        Self { key_manager }
124    }
125}
126
127impl EncryptorDecryptor for ManagedEncryptorDecryptor {
128    fn encrypt(&self, clearbytes: 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 cleartext =
139            std::str::from_utf8(&clearbytes).map_err(|e| LoginsApiError::EncryptionFailed {
140                reason: e.to_string(),
141            })?;
142        encdec
143            .encrypt(cleartext)
144            .map_err(
145                |e: jwcrypto::JwCryptoError| LoginsApiError::EncryptionFailed {
146                    reason: e.to_string(),
147                },
148            )
149            .map(|text| text.into())
150    }
151
152    fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
153        let keybytes = self
154            .key_manager
155            .get_key()
156            .map_err(|_| LoginsApiError::MissingKey)?;
157        let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?;
158
159        let encdec = jwcrypto::EncryptorDecryptor::new(key)
160            .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
161
162        let ciphertext =
163            std::str::from_utf8(&cipherbytes).map_err(|e| LoginsApiError::DecryptionFailed {
164                reason: e.to_string(),
165            })?;
166        encdec
167            .decrypt(ciphertext)
168            .map_err(
169                |e: jwcrypto::JwCryptoError| LoginsApiError::DecryptionFailed {
170                    reason: e.to_string(),
171                },
172            )
173            .map(|text| text.into())
174    }
175}
176
177/// Consumers can implement the KeyManager in combination with the ManagedEncryptorDecryptor to hand
178/// over the encryption key whenever encryption or decryption happens.
179#[uniffi::trait_interface]
180pub trait KeyManager: Send + Sync {
181    fn get_key(&self) -> ApiResult<Vec<u8>>;
182}
183
184/// Last but not least we provide a StaticKeyManager, which can be
185/// used in cases where there is a single key during runtime, for example in tests.
186pub struct StaticKeyManager {
187    key: String,
188}
189
190impl StaticKeyManager {
191    pub fn new(key: String) -> Self {
192        Self { key }
193    }
194}
195
196impl KeyManager for StaticKeyManager {
197    #[handle_error(Error)]
198    fn get_key(&self) -> ApiResult<Vec<u8>> {
199        Ok(self.key.as_bytes().into())
200    }
201}
202
203/// `PrimaryPasswordAuthenticator` is used in conjunction with `NSSKeyManager` to provide the
204/// primary password and the success or failure actions of the authentication process.
205#[cfg(feature = "keydb")]
206#[uniffi::export(with_foreign)]
207#[async_trait]
208pub trait PrimaryPasswordAuthenticator: Send + Sync {
209    /// Get a primary password for authentication, otherwise return the
210    /// AuthenticationCancelled error to cancel the authentication process.
211    async fn get_primary_password(&self) -> ApiResult<String>;
212    async fn on_authentication_success(&self) -> ApiResult<()>;
213    async fn on_authentication_failure(&self) -> ApiResult<()>;
214}
215
216/// Use the `NSSKeyManager` to use NSS for key management.
217///
218/// NSS stores keys in `key4.db` within the profile and wraps the key with a key derived from the
219/// primary password, if set. It defers to the provided `PrimaryPasswordAuthenticator`
220/// implementation to handle user authentication.  Note that if no primary password is set, the
221/// wrapping key is deterministically derived from an empty string.
222///
223/// Make sure to initialize NSS using `ensure_initialized_with_profile_dir` before creating a
224/// NSSKeyManager.
225///
226/// # Examples
227/// ```no_run
228/// use async_trait::async_trait;
229/// use logins::encryption::KeyManager;
230/// use logins::{PrimaryPasswordAuthenticator, LoginsApiError, NSSKeyManager};
231/// use std::sync::Arc;
232///
233/// struct MyPrimaryPasswordAuthenticator {}
234///
235/// #[async_trait]
236/// impl PrimaryPasswordAuthenticator for MyPrimaryPasswordAuthenticator {
237///     async fn get_primary_password(&self) -> Result<String, LoginsApiError> {
238///         // Most likely, you would want to prompt for a password.
239///         // let password = prompt_string("primary password").unwrap_or_default();
240///         Ok("secret".to_string())
241///     }
242///
243///     async fn on_authentication_success(&self) -> Result<(), LoginsApiError> {
244///         println!("success");
245///         Ok(())
246///     }
247///
248///     async fn on_authentication_failure(&self) -> Result<(), LoginsApiError> {
249///         println!("this did not work, please try again:");
250///         Ok(())
251///     }
252/// }
253/// let key_manager = NSSKeyManager::new(Arc::new(MyPrimaryPasswordAuthenticator {}));
254/// assert_eq!(key_manager.get_key().unwrap().len(), 63);
255/// ```
256#[cfg(feature = "keydb")]
257#[derive(uniffi::Object)]
258pub struct NSSKeyManager {
259    primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>,
260}
261
262#[cfg(feature = "keydb")]
263#[uniffi::export]
264impl NSSKeyManager {
265    /// Initialize new `NSSKeyManager` with a given `PrimaryPasswordAuthenticator`.
266    /// There must be a previous initializiation of NSS before initializing
267    /// `NSSKeyManager`, otherwise this panics.
268    #[uniffi::constructor()]
269    pub fn new(primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>) -> Self {
270        assert_nss_initialized();
271        Self {
272            primary_password_authenticator,
273        }
274    }
275
276    pub fn into_dyn_key_manager(self: Arc<Self>) -> Arc<dyn KeyManager> {
277        self
278    }
279}
280
281/// Identifier for the logins key, under which the key is stored in NSS.
282#[cfg(feature = "keydb")]
283static KEY_NAME: &str = "as-logins-key";
284
285// wrapp `authentication_with_primary_password_is_needed` into an ApiResult
286#[cfg(feature = "keydb")]
287fn api_authentication_with_primary_password_is_needed() -> ApiResult<bool> {
288    authentication_with_primary_password_is_needed().map_err(|e: nss_as::Error| {
289        LoginsApiError::NSSAuthenticationError {
290            reason: e.to_string(),
291        }
292    })
293}
294
295// wrapp `authenticate_with_primary_password` into an ApiResult
296#[cfg(feature = "keydb")]
297fn api_authenticate_with_primary_password(primary_password: &str) -> ApiResult<bool> {
298    authenticate_with_primary_password(primary_password).map_err(|e: nss_as::Error| {
299        LoginsApiError::NSSAuthenticationError {
300            reason: e.to_string(),
301        }
302    })
303}
304
305#[cfg(feature = "keydb")]
306impl KeyManager for NSSKeyManager {
307    fn get_key(&self) -> ApiResult<Vec<u8>> {
308        if api_authentication_with_primary_password_is_needed()? {
309            let primary_password =
310                block_on(self.primary_password_authenticator.get_primary_password())?;
311            let mut result = api_authenticate_with_primary_password(&primary_password)?;
312
313            if result {
314                block_on(
315                    self.primary_password_authenticator
316                        .on_authentication_success(),
317                )?;
318            } else {
319                while !result {
320                    block_on(
321                        self.primary_password_authenticator
322                            .on_authentication_failure(),
323                    )?;
324
325                    let primary_password =
326                        block_on(self.primary_password_authenticator.get_primary_password())?;
327                    result = api_authenticate_with_primary_password(&primary_password)?;
328                }
329                block_on(
330                    self.primary_password_authenticator
331                        .on_authentication_success(),
332                )?;
333            }
334        }
335
336        let key = get_or_create_aes256_key(KEY_NAME).map_err(|_| LoginsApiError::MissingKey)?;
337        let mut bytes: Vec<u8> = Vec::new();
338        serde_json::to_writer(
339            &mut bytes,
340            &jwcrypto::Jwk::new_direct_from_bytes(None, &key),
341        )
342        .unwrap();
343        Ok(bytes)
344    }
345}
346
347#[handle_error(Error)]
348pub fn create_canary(text: &str, key: &str) -> ApiResult<String> {
349    Ok(jwcrypto::EncryptorDecryptor::new(key)?.create_canary(text)?)
350}
351
352pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool> {
353    let encdec = jwcrypto::EncryptorDecryptor::new(key)
354        .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
355    Ok(encdec.check_canary(canary, text).unwrap_or(false))
356}
357
358#[handle_error(Error)]
359pub fn create_key() -> ApiResult<String> {
360    Ok(jwcrypto::EncryptorDecryptor::create_key()?)
361}
362
363#[cfg(test)]
364pub mod test_utils {
365    use super::*;
366    use serde::{de::DeserializeOwned, Serialize};
367
368    lazy_static::lazy_static! {
369        pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap();
370        pub static ref TEST_ENCDEC: Arc<ManagedEncryptorDecryptor> = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager { key: TEST_ENCRYPTION_KEY.clone() })));
371    }
372
373    pub fn encrypt_struct<T: Serialize>(fields: &T) -> String {
374        let string = serde_json::to_string(fields).unwrap();
375        let cipherbytes = TEST_ENCDEC.encrypt(string.as_bytes().into()).unwrap();
376        std::str::from_utf8(&cipherbytes).unwrap().to_owned()
377    }
378    pub fn decrypt_struct<T: DeserializeOwned>(ciphertext: String) -> T {
379        let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap();
380        serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap()
381    }
382}
383
384#[cfg(not(feature = "keydb"))]
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use nss_as::ensure_initialized;
389
390    #[test]
391    fn test_static_key_manager() {
392        ensure_initialized();
393        let key = create_key().unwrap();
394        let key_manager = StaticKeyManager { key: key.clone() };
395        assert_eq!(key.as_bytes(), key_manager.get_key().unwrap());
396    }
397
398    #[test]
399    fn test_noop_encdec_errors() {
400        // The placeholder we swap in on shutdown must never encrypt/decrypt; it
401        // should fail cleanly instead.
402        let encdec = NoopEncryptorDecryptor;
403        assert!(matches!(
404            encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
405            LoginsApiError::UnexpectedLoginsApiError { .. }
406        ));
407        assert!(matches!(
408            encdec.decrypt("secret".as_bytes().into()).err().unwrap(),
409            LoginsApiError::UnexpectedLoginsApiError { .. }
410        ));
411    }
412
413    #[test]
414    fn test_managed_encdec_with_invalid_key() {
415        ensure_initialized();
416        let key_manager = Arc::new(StaticKeyManager {
417            key: "bad_key".to_owned(),
418        });
419        let encdec = ManagedEncryptorDecryptor { key_manager };
420        assert!(matches!(
421            encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
422            LoginsApiError::InvalidKey
423        ));
424    }
425
426    #[test]
427    fn test_managed_encdec_with_missing_key() {
428        ensure_initialized();
429        struct MyKeyManager {}
430        impl KeyManager for MyKeyManager {
431            fn get_key(&self) -> ApiResult<Vec<u8>> {
432                Err(LoginsApiError::MissingKey)
433            }
434        }
435        let key_manager = Arc::new(MyKeyManager {});
436        let encdec = ManagedEncryptorDecryptor { key_manager };
437        assert!(matches!(
438            encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
439            LoginsApiError::MissingKey
440        ));
441    }
442
443    #[test]
444    fn test_managed_encdec() {
445        ensure_initialized();
446        let key = create_key().unwrap();
447        let key_manager = Arc::new(StaticKeyManager { key });
448        let encdec = ManagedEncryptorDecryptor { key_manager };
449        let cleartext = "secret";
450        let ciphertext = encdec.encrypt(cleartext.as_bytes().into()).unwrap();
451        assert_eq!(
452            encdec.decrypt(ciphertext.clone()).unwrap(),
453            cleartext.as_bytes()
454        );
455        let other_encdec = ManagedEncryptorDecryptor {
456            key_manager: Arc::new(StaticKeyManager {
457                key: create_key().unwrap(),
458            }),
459        };
460
461        assert_eq!(
462            other_encdec.decrypt(ciphertext).err().unwrap().to_string(),
463            "decryption failed: Crypto error: NSS error: NSS error: -8190 "
464        );
465    }
466
467    #[test]
468    fn test_key_error() {
469        let storage_err = jwcrypto::EncryptorDecryptor::new("bad-key").err().unwrap();
470        println!("{storage_err:?}");
471        assert!(matches!(storage_err, jwcrypto::JwCryptoError::InvalidKey));
472    }
473
474    #[test]
475    fn test_canary_functionality() {
476        ensure_initialized();
477        const CANARY_TEXT: &str = "Arbitrary sequence of text";
478        let key = create_key().unwrap();
479        let canary = create_canary(CANARY_TEXT, &key).unwrap();
480        assert!(check_canary(&canary, CANARY_TEXT, &key).unwrap());
481
482        let different_key = create_key().unwrap();
483        assert!(!check_canary(&canary, CANARY_TEXT, &different_key).unwrap());
484
485        let bad_key = "bad_key".to_owned();
486        assert!(matches!(
487            check_canary(&canary, CANARY_TEXT, &bad_key).err().unwrap(),
488            LoginsApiError::InvalidKey
489        ));
490    }
491}
492
493#[cfg(feature = "keydb")]
494#[cfg(test)]
495mod tests_keydb {
496    use super::*;
497    use nss_as::ensure_initialized_with_profile_dir;
498    use std::path::PathBuf;
499
500    struct MockPrimaryPasswordAuthenticator {
501        password: String,
502    }
503
504    #[async_trait]
505    impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
506        async fn get_primary_password(&self) -> ApiResult<String> {
507            Ok(self.password.clone())
508        }
509        async fn on_authentication_success(&self) -> ApiResult<()> {
510            Ok(())
511        }
512        async fn on_authentication_failure(&self) -> ApiResult<()> {
513            Ok(())
514        }
515    }
516
517    fn profile_path() -> PathBuf {
518        std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
519            .join("../support/rc_crypto/nss/fixtures/profile")
520    }
521
522    #[test]
523    fn test_ensure_initialized_with_profile_dir() {
524        ensure_initialized_with_profile_dir(profile_path());
525    }
526
527    #[test]
528    fn test_create_key() {
529        ensure_initialized_with_profile_dir(profile_path());
530        let key = create_key().unwrap();
531        assert_eq!(key.len(), 63)
532    }
533
534    #[test]
535    fn test_nss_key_manager() {
536        ensure_initialized_with_profile_dir(profile_path());
537        // `password` is the primary password of the profile fixture
538        let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator {
539            password: "password".to_string(),
540        };
541        let nss_key_manager = NSSKeyManager {
542            primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
543        };
544        // key from fixtures/profile/key4.db
545        assert_eq!(
546            nss_key_manager.get_key().unwrap(),
547            [
548                123, 34, 107, 116, 121, 34, 58, 34, 111, 99, 116, 34, 44, 34, 107, 34, 58, 34, 66,
549                74, 104, 84, 108, 103, 51, 118, 56, 49, 65, 66, 51, 118, 87, 50, 71, 122, 54, 104,
550                69, 54, 84, 116, 75, 83, 112, 85, 102, 84, 86, 75, 73, 83, 99, 74, 45, 77, 78, 83,
551                67, 117, 99, 34, 125
552            ]
553            .to_vec()
554        )
555    }
556
557    #[test]
558    fn test_primary_password_authentication() {
559        ensure_initialized_with_profile_dir(profile_path());
560        assert!(authenticate_with_primary_password("password").unwrap());
561    }
562}