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 */
56// This is the *local* encryption support - it has nothing to do with the
7// encryption used by sync.
89// 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.
5051use crate::error::*;
52use std::sync::Arc;
5354#[cfg(feature = "keydb")]
55use futures::executor::block_on;
5657#[cfg(feature = "keydb")]
58use async_trait::async_trait;
5960#[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};
6768/// 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 {
76fn encrypt(&self, cleartext: Vec<u8>) -> ApiResult<Vec<u8>>;
77fn decrypt(&self, ciphertext: Vec<u8>) -> ApiResult<Vec<u8>>;
78}
7980impl<T: EncryptorDecryptor> EncryptorDecryptor for Arc<T> {
81fn encrypt(&self, clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
82 (**self).encrypt(clearbytes)
83 }
8485fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
86 (**self).decrypt(cipherbytes)
87 }
88}
8990/// 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}
9596impl ManagedEncryptorDecryptor {
97#[uniffi::constructor()]
98pub fn new(key_manager: Arc<dyn KeyManager>) -> Self {
99Self { key_manager }
100 }
101}
102103impl EncryptorDecryptor for ManagedEncryptorDecryptor {
104fn encrypt(&self, clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
105let keybytes = self
106.key_manager
107 .get_key()
108 .map_err(|_| LoginsApiError::MissingKey)?;
109let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?;
110111let encdec = jwcrypto::EncryptorDecryptor::new(key)
112 .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
113114let 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 }
127128fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
129let keybytes = self
130.key_manager
131 .get_key()
132 .map_err(|_| LoginsApiError::MissingKey)?;
133let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?;
134135let encdec = jwcrypto::EncryptorDecryptor::new(key)
136 .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
137138let 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}
152153/// 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 {
156fn get_key(&self) -> ApiResult<Vec<u8>>;
157}
158159/// 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}
164165impl StaticKeyManager {
166pub fn new(key: String) -> Self {
167Self { key }
168 }
169}
170171impl KeyManager for StaticKeyManager {
172#[handle_error(Error)]
173fn get_key(&self) -> ApiResult<Vec<u8>> {
174Ok(self.key.as_bytes().into())
175 }
176}
177178/// `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.
186async fn get_primary_password(&self) -> ApiResult<String>;
187async fn on_authentication_success(&self) -> ApiResult<()>;
188async fn on_authentication_failure(&self) -> ApiResult<()>;
189}
190191/// 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}
236237#[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()]
244pub fn new(primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>) -> Self {
245 assert_nss_initialized();
246Self {
247 primary_password_authenticator,
248 }
249 }
250251pub fn into_dyn_key_manager(self: Arc<Self>) -> Arc<dyn KeyManager> {
252self
253}
254}
255256/// Identifier for the logins key, under which the key is stored in NSS.
257#[cfg(feature = "keydb")]
258static KEY_NAME: &str = "as-logins-key";
259260// 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}
269270// 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}
279280#[cfg(feature = "keydb")]
281impl KeyManager for NSSKeyManager {
282fn get_key(&self) -> ApiResult<Vec<u8>> {
283if api_authentication_with_primary_password_is_needed()? {
284let primary_password =
285 block_on(self.primary_password_authenticator.get_primary_password())?;
286let mut result = api_authenticate_with_primary_password(&primary_password)?;
287288if result {
289 block_on(
290self.primary_password_authenticator
291 .on_authentication_success(),
292 )?;
293 } else {
294while !result {
295 block_on(
296self.primary_password_authenticator
297 .on_authentication_failure(),
298 )?;
299300let 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(
305self.primary_password_authenticator
306 .on_authentication_success(),
307 )?;
308 }
309 }
310311let key = get_or_create_aes256_key(KEY_NAME).expect("Could not get or create key via NSS");
312let 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();
318Ok(bytes)
319 }
320}
321322#[handle_error(Error)]
323pub fn create_canary(text: &str, key: &str) -> ApiResult<String> {
324Ok(jwcrypto::EncryptorDecryptor::new(key)?.create_canary(text)?)
325}
326327pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool> {
328let encdec = jwcrypto::EncryptorDecryptor::new(key)
329 .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
330Ok(encdec.check_canary(canary, text).unwrap_or(false))
331}
332333#[handle_error(Error)]
334pub fn create_key() -> ApiResult<String> {
335Ok(jwcrypto::EncryptorDecryptor::create_key()?)
336}
337338#[cfg(test)]
339pub mod test_utils {
340use super::*;
341use serde::{de::DeserializeOwned, Serialize};
342343lazy_static::lazy_static! {
344pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap();
345pub static ref TEST_ENCDEC: Arc<ManagedEncryptorDecryptor> = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager { key: TEST_ENCRYPTION_KEY.clone() })));
346 }
347348pub fn encrypt_struct<T: Serialize>(fields: &T) -> String {
349let string = serde_json::to_string(fields).unwrap();
350let cipherbytes = TEST_ENCDEC.encrypt(string.as_bytes().into()).unwrap();
351 std::str::from_utf8(&cipherbytes).unwrap().to_owned()
352 }
353pub fn decrypt_struct<T: DeserializeOwned>(ciphertext: String) -> T {
354let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap();
355 serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap()
356 }
357}
358359#[cfg(not(feature = "keydb"))]
360#[cfg(test)]
361mod test {
362use super::*;
363use nss::ensure_initialized;
364365#[test]
366fn test_static_key_manager() {
367 ensure_initialized();
368let key = create_key().unwrap();
369let key_manager = StaticKeyManager { key: key.clone() };
370assert_eq!(key.as_bytes(), key_manager.get_key().unwrap());
371 }
372373#[test]
374fn test_managed_encdec_with_invalid_key() {
375 ensure_initialized();
376let key_manager = Arc::new(StaticKeyManager {
377 key: "bad_key".to_owned(),
378 });
379let encdec = ManagedEncryptorDecryptor { key_manager };
380assert!(matches!(
381 encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
382 LoginsApiError::InvalidKey
383 ));
384 }
385386#[test]
387fn test_managed_encdec_with_missing_key() {
388 ensure_initialized();
389struct MyKeyManager {}
390impl KeyManager for MyKeyManager {
391fn get_key(&self) -> ApiResult<Vec<u8>> {
392Err(LoginsApiError::MissingKey)
393 }
394 }
395let key_manager = Arc::new(MyKeyManager {});
396let encdec = ManagedEncryptorDecryptor { key_manager };
397assert!(matches!(
398 encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
399 LoginsApiError::MissingKey
400 ));
401 }
402403#[test]
404fn test_managed_encdec() {
405 ensure_initialized();
406let key = create_key().unwrap();
407let key_manager = Arc::new(StaticKeyManager { key });
408let encdec = ManagedEncryptorDecryptor { key_manager };
409let cleartext = "secret";
410let ciphertext = encdec.encrypt(cleartext.as_bytes().into()).unwrap();
411assert_eq!(
412 encdec.decrypt(ciphertext.clone()).unwrap(),
413 cleartext.as_bytes()
414 );
415let other_encdec = ManagedEncryptorDecryptor {
416 key_manager: Arc::new(StaticKeyManager {
417 key: create_key().unwrap(),
418 }),
419 };
420421assert_eq!(
422 other_encdec.decrypt(ciphertext).err().unwrap().to_string(),
423"decryption failed: Crypto error: NSS error: NSS error: -8190 "
424);
425 }
426427#[test]
428fn test_key_error() {
429let storage_err = jwcrypto::EncryptorDecryptor::new("bad-key").err().unwrap();
430println!("{storage_err:?}");
431assert!(matches!(storage_err, jwcrypto::JwCryptoError::InvalidKey));
432 }
433434#[test]
435fn test_canary_functionality() {
436 ensure_initialized();
437const CANARY_TEXT: &str = "Arbitrary sequence of text";
438let key = create_key().unwrap();
439let canary = create_canary(CANARY_TEXT, &key).unwrap();
440assert!(check_canary(&canary, CANARY_TEXT, &key).unwrap());
441442let different_key = create_key().unwrap();
443assert!(!check_canary(&canary, CANARY_TEXT, &different_key).unwrap());
444445let bad_key = "bad_key".to_owned();
446assert!(matches!(
447 check_canary(&canary, CANARY_TEXT, &bad_key).err().unwrap(),
448 LoginsApiError::InvalidKey
449 ));
450 }
451}
452453#[cfg(feature = "keydb")]
454#[cfg(test)]
455mod keydb_test {
456use super::*;
457use nss::ensure_initialized_with_profile_dir;
458use std::path::PathBuf;
459460struct MockPrimaryPasswordAuthenticator {
461 password: String,
462 }
463464#[async_trait]
465impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
466async fn get_primary_password(&self) -> ApiResult<String> {
467Ok(self.password.clone())
468 }
469async fn on_authentication_success(&self) -> ApiResult<()> {
470Ok(())
471 }
472async fn on_authentication_failure(&self) -> ApiResult<()> {
473Ok(())
474 }
475 }
476477fn profile_path() -> PathBuf {
478 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/profile")
479 }
480481#[test]
482fn test_ensure_initialized_with_profile_dir() {
483 ensure_initialized_with_profile_dir(profile_path());
484 }
485486#[test]
487fn test_create_key() {
488 ensure_initialized_with_profile_dir(profile_path());
489let key = create_key().unwrap();
490assert_eq!(key.len(), 63)
491 }
492493#[test]
494fn test_nss_key_manager() {
495 ensure_initialized_with_profile_dir(profile_path());
496let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator {
497 password: "password".to_string(),
498 };
499let nss_key_manager = NSSKeyManager {
500 primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
501 };
502assert_eq!(nss_key_manager.get_key().unwrap().len(), 63)
503 }
504}