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
68#[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
91pub 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#[uniffi::trait_interface]
157pub trait KeyManager: Send + Sync {
158 fn get_key(&self) -> ApiResult<Vec<u8>>;
159}
160
161pub 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#[cfg(feature = "keydb")]
183#[uniffi::export(with_foreign)]
184#[async_trait]
185pub trait PrimaryPasswordAuthenticator: Send + Sync {
186 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#[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 #[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#[cfg(feature = "keydb")]
260static KEY_NAME: &str = "as-logins-key";
261
262#[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#[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 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 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}