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