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 {
497 password: "password".to_string(),
498 };
499 let nss_key_manager = NSSKeyManager {
500 primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
501 };
502 assert_eq!(nss_key_manager.get_key().unwrap().len(), 63)
503 }
504}