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_as::assert_initialized as assert_nss_initialized;
62#[cfg(feature = "keydb")]
63use nss_as::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 NoopEncryptorDecryptor;
99
100impl EncryptorDecryptor for NoopEncryptorDecryptor {
101 fn encrypt(&self, _clearbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
102 Err(LoginsApiError::UnexpectedLoginsApiError {
103 reason: "encrypt called on a shut-down store".to_string(),
104 })
105 }
106
107 fn decrypt(&self, _cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
108 Err(LoginsApiError::UnexpectedLoginsApiError {
109 reason: "decrypt called on a shut-down store".to_string(),
110 })
111 }
112}
113
114pub struct ManagedEncryptorDecryptor {
117 key_manager: Arc<dyn KeyManager>,
118}
119
120impl ManagedEncryptorDecryptor {
121 #[uniffi::constructor()]
122 pub fn new(key_manager: Arc<dyn KeyManager>) -> Self {
123 Self { key_manager }
124 }
125}
126
127impl EncryptorDecryptor for ManagedEncryptorDecryptor {
128 fn encrypt(&self, clearbytes: 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 cleartext =
139 std::str::from_utf8(&clearbytes).map_err(|e| LoginsApiError::EncryptionFailed {
140 reason: e.to_string(),
141 })?;
142 encdec
143 .encrypt(cleartext)
144 .map_err(
145 |e: jwcrypto::JwCryptoError| LoginsApiError::EncryptionFailed {
146 reason: e.to_string(),
147 },
148 )
149 .map(|text| text.into())
150 }
151
152 fn decrypt(&self, cipherbytes: Vec<u8>) -> ApiResult<Vec<u8>> {
153 let keybytes = self
154 .key_manager
155 .get_key()
156 .map_err(|_| LoginsApiError::MissingKey)?;
157 let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?;
158
159 let encdec = jwcrypto::EncryptorDecryptor::new(key)
160 .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
161
162 let ciphertext =
163 std::str::from_utf8(&cipherbytes).map_err(|e| LoginsApiError::DecryptionFailed {
164 reason: e.to_string(),
165 })?;
166 encdec
167 .decrypt(ciphertext)
168 .map_err(
169 |e: jwcrypto::JwCryptoError| LoginsApiError::DecryptionFailed {
170 reason: e.to_string(),
171 },
172 )
173 .map(|text| text.into())
174 }
175}
176
177#[uniffi::trait_interface]
180pub trait KeyManager: Send + Sync {
181 fn get_key(&self) -> ApiResult<Vec<u8>>;
182}
183
184pub struct StaticKeyManager {
187 key: String,
188}
189
190impl StaticKeyManager {
191 pub fn new(key: String) -> Self {
192 Self { key }
193 }
194}
195
196impl KeyManager for StaticKeyManager {
197 #[handle_error(Error)]
198 fn get_key(&self) -> ApiResult<Vec<u8>> {
199 Ok(self.key.as_bytes().into())
200 }
201}
202
203#[cfg(feature = "keydb")]
206#[uniffi::export(with_foreign)]
207#[async_trait]
208pub trait PrimaryPasswordAuthenticator: Send + Sync {
209 async fn get_primary_password(&self) -> ApiResult<String>;
212 async fn on_authentication_success(&self) -> ApiResult<()>;
213 async fn on_authentication_failure(&self) -> ApiResult<()>;
214}
215
216#[cfg(feature = "keydb")]
257#[derive(uniffi::Object)]
258pub struct NSSKeyManager {
259 primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>,
260}
261
262#[cfg(feature = "keydb")]
263#[uniffi::export]
264impl NSSKeyManager {
265 #[uniffi::constructor()]
269 pub fn new(primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>) -> Self {
270 assert_nss_initialized();
271 Self {
272 primary_password_authenticator,
273 }
274 }
275
276 pub fn into_dyn_key_manager(self: Arc<Self>) -> Arc<dyn KeyManager> {
277 self
278 }
279}
280
281#[cfg(feature = "keydb")]
283static KEY_NAME: &str = "as-logins-key";
284
285#[cfg(feature = "keydb")]
287fn api_authentication_with_primary_password_is_needed() -> ApiResult<bool> {
288 authentication_with_primary_password_is_needed().map_err(|e: nss_as::Error| {
289 LoginsApiError::NSSAuthenticationError {
290 reason: e.to_string(),
291 }
292 })
293}
294
295#[cfg(feature = "keydb")]
297fn api_authenticate_with_primary_password(primary_password: &str) -> ApiResult<bool> {
298 authenticate_with_primary_password(primary_password).map_err(|e: nss_as::Error| {
299 LoginsApiError::NSSAuthenticationError {
300 reason: e.to_string(),
301 }
302 })
303}
304
305#[cfg(feature = "keydb")]
306impl KeyManager for NSSKeyManager {
307 fn get_key(&self) -> ApiResult<Vec<u8>> {
308 if api_authentication_with_primary_password_is_needed()? {
309 let primary_password =
310 block_on(self.primary_password_authenticator.get_primary_password())?;
311 let mut result = api_authenticate_with_primary_password(&primary_password)?;
312
313 if result {
314 block_on(
315 self.primary_password_authenticator
316 .on_authentication_success(),
317 )?;
318 } else {
319 while !result {
320 block_on(
321 self.primary_password_authenticator
322 .on_authentication_failure(),
323 )?;
324
325 let primary_password =
326 block_on(self.primary_password_authenticator.get_primary_password())?;
327 result = api_authenticate_with_primary_password(&primary_password)?;
328 }
329 block_on(
330 self.primary_password_authenticator
331 .on_authentication_success(),
332 )?;
333 }
334 }
335
336 let key = get_or_create_aes256_key(KEY_NAME).map_err(|_| LoginsApiError::MissingKey)?;
337 let mut bytes: Vec<u8> = Vec::new();
338 serde_json::to_writer(
339 &mut bytes,
340 &jwcrypto::Jwk::new_direct_from_bytes(None, &key),
341 )
342 .unwrap();
343 Ok(bytes)
344 }
345}
346
347#[handle_error(Error)]
348pub fn create_canary(text: &str, key: &str) -> ApiResult<String> {
349 Ok(jwcrypto::EncryptorDecryptor::new(key)?.create_canary(text)?)
350}
351
352pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool> {
353 let encdec = jwcrypto::EncryptorDecryptor::new(key)
354 .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?;
355 Ok(encdec.check_canary(canary, text).unwrap_or(false))
356}
357
358#[handle_error(Error)]
359pub fn create_key() -> ApiResult<String> {
360 Ok(jwcrypto::EncryptorDecryptor::create_key()?)
361}
362
363#[cfg(test)]
364pub mod test_utils {
365 use super::*;
366 use serde::{de::DeserializeOwned, Serialize};
367
368 lazy_static::lazy_static! {
369 pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap();
370 pub static ref TEST_ENCDEC: Arc<ManagedEncryptorDecryptor> = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager { key: TEST_ENCRYPTION_KEY.clone() })));
371 }
372
373 pub fn encrypt_struct<T: Serialize>(fields: &T) -> String {
374 let string = serde_json::to_string(fields).unwrap();
375 let cipherbytes = TEST_ENCDEC.encrypt(string.as_bytes().into()).unwrap();
376 std::str::from_utf8(&cipherbytes).unwrap().to_owned()
377 }
378 pub fn decrypt_struct<T: DeserializeOwned>(ciphertext: String) -> T {
379 let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap();
380 serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap()
381 }
382}
383
384#[cfg(not(feature = "keydb"))]
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use nss_as::ensure_initialized;
389
390 #[test]
391 fn test_static_key_manager() {
392 ensure_initialized();
393 let key = create_key().unwrap();
394 let key_manager = StaticKeyManager { key: key.clone() };
395 assert_eq!(key.as_bytes(), key_manager.get_key().unwrap());
396 }
397
398 #[test]
399 fn test_noop_encdec_errors() {
400 let encdec = NoopEncryptorDecryptor;
403 assert!(matches!(
404 encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
405 LoginsApiError::UnexpectedLoginsApiError { .. }
406 ));
407 assert!(matches!(
408 encdec.decrypt("secret".as_bytes().into()).err().unwrap(),
409 LoginsApiError::UnexpectedLoginsApiError { .. }
410 ));
411 }
412
413 #[test]
414 fn test_managed_encdec_with_invalid_key() {
415 ensure_initialized();
416 let key_manager = Arc::new(StaticKeyManager {
417 key: "bad_key".to_owned(),
418 });
419 let encdec = ManagedEncryptorDecryptor { key_manager };
420 assert!(matches!(
421 encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
422 LoginsApiError::InvalidKey
423 ));
424 }
425
426 #[test]
427 fn test_managed_encdec_with_missing_key() {
428 ensure_initialized();
429 struct MyKeyManager {}
430 impl KeyManager for MyKeyManager {
431 fn get_key(&self) -> ApiResult<Vec<u8>> {
432 Err(LoginsApiError::MissingKey)
433 }
434 }
435 let key_manager = Arc::new(MyKeyManager {});
436 let encdec = ManagedEncryptorDecryptor { key_manager };
437 assert!(matches!(
438 encdec.encrypt("secret".as_bytes().into()).err().unwrap(),
439 LoginsApiError::MissingKey
440 ));
441 }
442
443 #[test]
444 fn test_managed_encdec() {
445 ensure_initialized();
446 let key = create_key().unwrap();
447 let key_manager = Arc::new(StaticKeyManager { key });
448 let encdec = ManagedEncryptorDecryptor { key_manager };
449 let cleartext = "secret";
450 let ciphertext = encdec.encrypt(cleartext.as_bytes().into()).unwrap();
451 assert_eq!(
452 encdec.decrypt(ciphertext.clone()).unwrap(),
453 cleartext.as_bytes()
454 );
455 let other_encdec = ManagedEncryptorDecryptor {
456 key_manager: Arc::new(StaticKeyManager {
457 key: create_key().unwrap(),
458 }),
459 };
460
461 assert_eq!(
462 other_encdec.decrypt(ciphertext).err().unwrap().to_string(),
463 "decryption failed: Crypto error: NSS error: NSS error: -8190 "
464 );
465 }
466
467 #[test]
468 fn test_key_error() {
469 let storage_err = jwcrypto::EncryptorDecryptor::new("bad-key").err().unwrap();
470 println!("{storage_err:?}");
471 assert!(matches!(storage_err, jwcrypto::JwCryptoError::InvalidKey));
472 }
473
474 #[test]
475 fn test_canary_functionality() {
476 ensure_initialized();
477 const CANARY_TEXT: &str = "Arbitrary sequence of text";
478 let key = create_key().unwrap();
479 let canary = create_canary(CANARY_TEXT, &key).unwrap();
480 assert!(check_canary(&canary, CANARY_TEXT, &key).unwrap());
481
482 let different_key = create_key().unwrap();
483 assert!(!check_canary(&canary, CANARY_TEXT, &different_key).unwrap());
484
485 let bad_key = "bad_key".to_owned();
486 assert!(matches!(
487 check_canary(&canary, CANARY_TEXT, &bad_key).err().unwrap(),
488 LoginsApiError::InvalidKey
489 ));
490 }
491}
492
493#[cfg(feature = "keydb")]
494#[cfg(test)]
495mod tests_keydb {
496 use super::*;
497 use nss_as::ensure_initialized_with_profile_dir;
498 use std::path::PathBuf;
499
500 struct MockPrimaryPasswordAuthenticator {
501 password: String,
502 }
503
504 #[async_trait]
505 impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
506 async fn get_primary_password(&self) -> ApiResult<String> {
507 Ok(self.password.clone())
508 }
509 async fn on_authentication_success(&self) -> ApiResult<()> {
510 Ok(())
511 }
512 async fn on_authentication_failure(&self) -> ApiResult<()> {
513 Ok(())
514 }
515 }
516
517 fn profile_path() -> PathBuf {
518 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
519 .join("../support/rc_crypto/nss/fixtures/profile")
520 }
521
522 #[test]
523 fn test_ensure_initialized_with_profile_dir() {
524 ensure_initialized_with_profile_dir(profile_path());
525 }
526
527 #[test]
528 fn test_create_key() {
529 ensure_initialized_with_profile_dir(profile_path());
530 let key = create_key().unwrap();
531 assert_eq!(key.len(), 63)
532 }
533
534 #[test]
535 fn test_nss_key_manager() {
536 ensure_initialized_with_profile_dir(profile_path());
537 let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator {
539 password: "password".to_string(),
540 };
541 let nss_key_manager = NSSKeyManager {
542 primary_password_authenticator: Arc::new(mock_primary_password_authenticator),
543 };
544 assert_eq!(
546 nss_key_manager.get_key().unwrap(),
547 [
548 123, 34, 107, 116, 121, 34, 58, 34, 111, 99, 116, 34, 44, 34, 107, 34, 58, 34, 66,
549 74, 104, 84, 108, 103, 51, 118, 56, 49, 65, 66, 51, 118, 87, 50, 71, 122, 54, 104,
550 69, 54, 84, 116, 75, 83, 112, 85, 102, 84, 86, 75, 73, 83, 99, 74, 45, 77, 78, 83,
551 67, 117, 99, 34, 125
552 ]
553 .to_vec()
554 )
555 }
556
557 #[test]
558 fn test_primary_password_authentication() {
559 ensure_initialized_with_profile_dir(profile_path());
560 assert!(authenticate_with_primary_password("password").unwrap());
561 }
562}