1use 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
68pub 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
90pub 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
153pub trait KeyManager: Send + Sync {
156 fn get_key(&self) -> ApiResult<Vec<u8>>;
157}
158
159pub 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#[cfg(feature = "keydb")]
181#[uniffi::export(with_foreign)]
182#[async_trait]
183pub trait PrimaryPasswordAuthenticator: Send + Sync {
184 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#[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 #[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#[cfg(feature = "keydb")]
258static KEY_NAME: &str = "as-logins-key";
259
260#[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#[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 {
498 password: "password".to_string(),
499 };
500 let nss_key_manager = NSSKeyManager {
501 primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
502 };
503 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}