diff --git a/.buildconfig-android.yml b/.buildconfig-android.yml index 98aed1a0628..41f5ad768f6 100644 --- a/.buildconfig-android.yml +++ b/.buildconfig-android.yml @@ -166,3 +166,10 @@ projects: type: aar description: Client for Firefox Relay. + db-crypto: + path: components/support/db-crypto/android + artifactId: db-crypto + publications: + - name: db-crypto + type: aar + description: Database encryption support diff --git a/CHANGELOG.md b/CHANGELOG.md index 3751eee441b..67facd3c5d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ [Full Changelog](In progress) +### Logins + +- Refactor the database encryption support into a new support crate `db-crypto`, which provides all the same functions and traits as were previously available in `logins::encryption` module. However, this leads to two breaking changes: the functionality has been moved into a new `db_crypto` UniFFI namespace, and the error type has changed from `LoginsApiError` to `DbCryptoApiError`. ([#7542](https://github.com/mozilla/application-services/pull/7542)) + # v156.0 (_2026-08-27_) ## ✨ What's Changed ✨ diff --git a/Cargo.lock b/Cargo.lock index 625905a7e70..b1f6a131b5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -926,6 +926,22 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "db-crypto" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "error-support", + "futures", + "jwcrypto", + "nss-as", + "parking_lot", + "serde_json", + "thiserror 2.0.3", + "uniffi", +] + [[package]] name = "deflate64" version = "0.1.9" @@ -1293,6 +1309,7 @@ dependencies = [ "chrono", "clap", "cli-support", + "db-crypto", "fxa-client", "init_rust_components", "log", @@ -2533,6 +2550,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "db-crypto", "error-support", "futures", "interrupt-support", @@ -2606,6 +2624,7 @@ dependencies = [ "ads-client", "autofill", "crashtest", + "db-crypto", "error-support", "fxa-client", "init_rust_components", @@ -2650,6 +2669,7 @@ dependencies = [ "autofill", "context_id", "crashtest", + "db-crypto", "error-support", "fxa-client", "init_rust_components", @@ -4290,6 +4310,7 @@ dependencies = [ "base64 0.21.2", "clap", "cli-support", + "db-crypto", "env_logger", "fxa-client", "interrupt-support", diff --git a/Cargo.toml b/Cargo.toml index c8379d4d827..9a555a48bc6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ members = [ "components/search", "components/suggest", "components/suggest/suggest-bench", + "components/support/db-crypto", "components/support/error", "components/support/error/tests", "components/support/find-places-db", diff --git a/components/logins/Cargo.toml b/components/logins/Cargo.toml index 2f126f9eba2..09894dfdfd8 100644 --- a/components/logins/Cargo.toml +++ b/components/logins/Cargo.toml @@ -12,7 +12,7 @@ default = [] # `key4.db` (wrapped with a key derived from the primary password, if set). # Used on Desktop to integrate with the existing NSS key store and primary # password flow. -keydb = ["nss-as/keydb", "dep:async-trait", "dep:futures"] +keydb = ["nss-as/keydb", "db-crypto/keydb", "dep:async-trait", "dep:futures"] # Allows logins with empty passwords to be imported. Used on Desktop during # migration to accept existing logins that have empty passwords. allow_empty_passwords = [] @@ -53,6 +53,7 @@ anyhow = "1.0" uniffi = { version = "0.31" } async-trait = { version = "0.1", optional = true } futures = { version = "0.3", optional = true, features = ["executor"] } +db-crypto = { path = "../support/db-crypto", default-features = false } [build-dependencies] uniffi = { version = "0.31", features = ["build"] } diff --git a/components/logins/android/build.gradle b/components/logins/android/build.gradle index 4834fb15f14..9608bd29ac7 100644 --- a/components/logins/android/build.gradle +++ b/components/logins/android/build.gradle @@ -44,6 +44,7 @@ dependencies { // Part of the public API. api project(':init_rust_components') api project(':sync15') + api project(':db-crypto') implementation project(':init_rust_components') diff --git a/components/logins/android/src/main/java/mozilla/appservices/logins/DatabaseLoginsStorage.kt b/components/logins/android/src/main/java/mozilla/appservices/logins/DatabaseLoginsStorage.kt index cfb2412eac3..f8a9f86b118 100644 --- a/components/logins/android/src/main/java/mozilla/appservices/logins/DatabaseLoginsStorage.kt +++ b/components/logins/android/src/main/java/mozilla/appservices/logins/DatabaseLoginsStorage.kt @@ -13,6 +13,7 @@ package mozilla.appservices.logins * on version updates. */ +import mozilla.appservices.db_crypto.KeyManager import mozilla.telemetry.glean.private.CounterMetricType import mozilla.telemetry.glean.private.LabeledMetricType import org.mozilla.appservices.logins.GleanMetrics.LoginsStore as LoginsStoreMetrics diff --git a/components/logins/android/src/test/java/mozilla/appservices/logins/DatabaseLoginsStorageTest.kt b/components/logins/android/src/test/java/mozilla/appservices/logins/DatabaseLoginsStorageTest.kt index d00935121c2..c05465c099e 100644 --- a/components/logins/android/src/test/java/mozilla/appservices/logins/DatabaseLoginsStorageTest.kt +++ b/components/logins/android/src/test/java/mozilla/appservices/logins/DatabaseLoginsStorageTest.kt @@ -6,6 +6,7 @@ package mozilla.appservices.logins import androidx.test.core.app.ApplicationProvider import mozilla.appservices.RustComponentsInitializer +import mozilla.appservices.db_crypto.KeyManager import mozilla.appservices.syncmanager.SyncManager import mozilla.telemetry.glean.testing.GleanTestRule import org.junit.Assert.assertEquals diff --git a/components/logins/src/db.rs b/components/logins/src/db.rs index 89ccec39c05..40bae0bde80 100644 --- a/components/logins/src/db.rs +++ b/components/logins/src/db.rs @@ -22,12 +22,12 @@ /// server. /// - After we sync, we move all records from loginsL to loginsM, overwriting any previous data. /// loginsL will be an empty table after this. See mark_as_synchronized() for the details. -use crate::encryption::EncryptorDecryptor; use crate::error::*; use crate::login::*; use crate::schema; use crate::sync::SyncStatus; use crate::util; +use db_crypto::EncryptorDecryptor; use interrupt_support::{SqlInterruptHandle, SqlInterruptScope}; use lazy_static::lazy_static; use rusqlite::{ @@ -84,8 +84,7 @@ impl LoginDb { #[cfg(test)] pub fn open_in_memory() -> Self { - let encdec: Arc = - crate::encryption::test_utils::TEST_ENCDEC.clone(); + let encdec: Arc = crate::test_utils::TEST_ENCDEC.clone(); Self::with_connection(Connection::open_in_memory().unwrap(), encdec).unwrap() } @@ -1202,8 +1201,8 @@ lazy_static! { #[cfg(test)] pub mod test_utils { use super::*; - use crate::encryption::test_utils::decrypt_struct; use crate::login::test_utils::enc_login; + use crate::test_utils::decrypt_struct; use crate::SecureLoginFields; use sync15::ServerTimestamp; @@ -1374,8 +1373,8 @@ pub mod test_utils { mod tests { use super::*; use crate::db::test_utils::{get_local_guids, get_mirror_guids}; - use crate::encryption::test_utils::TEST_ENCDEC; use crate::sync::merge::LocalLogin; + use crate::test_utils::TEST_ENCDEC; use nss_as::ensure_initialized; use std::{thread, time}; diff --git a/components/logins/src/error.rs b/components/logins/src/error.rs index 7d8c27d2f68..2aa1418a11e 100644 --- a/components/logins/src/error.rs +++ b/components/logins/src/error.rs @@ -10,6 +10,7 @@ pub type ApiResult = std::result::Result; pub use error_support::{breadcrumb, handle_error, report_error}; pub use error_support::{debug, error, info, trace, warn}; +use db_crypto::DbCryptoApiError; use error_support::{ErrorHandling, GetErrorHandling}; use jwcrypto::JwCryptoError; @@ -96,7 +97,10 @@ pub enum Error { InvalidPath(OsString), #[error("CryptoError({0})")] - CryptoError(#[from] JwCryptoError), + CryptoError(#[from] DbCryptoApiError), + + #[error("CryptoError({0})")] + JwCryptoError(#[from] JwCryptoError), #[error("{0}")] Interrupted(#[from] interrupt_support::Interrupted), @@ -214,3 +218,30 @@ impl From for LoginsApiError { } } } + +impl From for LoginsApiError { + fn from(error: DbCryptoApiError) -> Self { + match error { + DbCryptoApiError::NSSUninitialized => Self::NSSUninitialized, + DbCryptoApiError::NSSAuthenticationError { reason: x } => { + Self::NSSAuthenticationError { reason: x } + } + DbCryptoApiError::AuthenticationError { reason: x } => { + Self::AuthenticationError { reason: x } + } + DbCryptoApiError::AuthenticationCanceled => Self::AuthenticationCanceled, + DbCryptoApiError::MissingKey => Self::MissingKey, + DbCryptoApiError::InvalidKey => Self::InvalidKey, + DbCryptoApiError::EncryptionFailed { reason: x } => { + Self::EncryptionFailed { reason: x } + } + DbCryptoApiError::DecryptionFailed { reason: x } => { + Self::DecryptionFailed { reason: x } + } + DbCryptoApiError::Interrupted { reason: x } => Self::Interrupted { reason: x }, + DbCryptoApiError::UnexpectedDbCryptoApiError { reason: x } => { + Self::UnexpectedLoginsApiError { reason: x } + } + } + } +} diff --git a/components/logins/src/lib.rs b/components/logins/src/lib.rs index cd430b4e131..97290250b51 100644 --- a/components/logins/src/lib.rs +++ b/components/logins/src/lib.rs @@ -10,28 +10,28 @@ mod error; mod login; mod db; -pub mod encryption; mod schema; mod store; mod sync; mod util; -use crate::encryption::{ - EncryptorDecryptor, KeyManager, ManagedEncryptorDecryptor, StaticKeyManager, -}; +use db_crypto::{EncryptorDecryptor, KeyManager, ManagedEncryptorDecryptor, StaticKeyManager}; uniffi::include_scaffolding!("logins"); #[cfg(feature = "keydb")] -pub use crate::encryption::{NSSKeyManager, PrimaryPasswordAuthenticator}; +pub use db_crypto::{NSSKeyManager, PrimaryPasswordAuthenticator}; pub use crate::db::{LoginDb, LoginsDeletionMetrics}; -use crate::encryption::{check_canary, create_canary, create_key}; pub use crate::error::*; pub use crate::login::*; pub use crate::store::*; pub use crate::sync::{LoginsBridgedEngine, LoginsSyncEngine}; use std::sync::Arc; +/// Identifier for the logins key, under which the key is stored in NSS. +#[cfg(feature = "keydb")] +static KEY_NAME: &str = "as-logins-key"; + // Utility function to create a StaticKeyManager to be used for the time being until support lands // for [trait implementation of an UniFFI // interface](https://mozilla.github.io/uniffi-rs/next/proc_macro/index.html#structs-implementing-traits) @@ -57,6 +57,20 @@ pub fn create_login_store_with_static_key_manager(path: String, key: String) -> Arc::new(store) } +#[handle_error(Error)] +pub fn create_canary(text: &str, key: &str) -> ApiResult { + Ok(db_crypto::create_canary(text, key)?) +} + +pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult { + Ok(db_crypto::check_canary(canary, text, key)?) +} + +#[handle_error(Error)] +pub fn create_key() -> ApiResult { + Ok(db_crypto::create_key()?) +} + // Create a LoginStore with NSSKeyManager by passing in a db path and a PrimaryPasswordAuthenticator. // // Note this is only temporarily needed until a bug with UniFFI and JavaScript is fixed, which @@ -68,8 +82,29 @@ pub fn create_login_store_with_nss_keymanager( primary_password_authenticator: Arc, ) -> ApiResult> { let encdec: ManagedEncryptorDecryptor = ManagedEncryptorDecryptor::new(Arc::new( - NSSKeyManager::new(primary_password_authenticator), + NSSKeyManager::new(KEY_NAME.to_string(), primary_password_authenticator), )); let store = LoginStore::new(path, Arc::new(encdec))?; Ok(Arc::new(store)) } + +#[cfg(test)] +pub mod test_utils { + use super::*; + use serde::{de::DeserializeOwned, Serialize}; + + lazy_static::lazy_static! { + pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap(); + pub static ref TEST_ENCDEC: Arc = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager::new(TEST_ENCRYPTION_KEY.clone())))); + } + + pub fn encrypt_struct(fields: &T) -> String { + let string = serde_json::to_string(fields).unwrap(); + let cipherbytes = TEST_ENCDEC.encrypt(string.as_bytes().into()).unwrap(); + std::str::from_utf8(&cipherbytes).unwrap().to_owned() + } + pub fn decrypt_struct(ciphertext: String) -> T { + let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap(); + serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap() + } +} diff --git a/components/logins/src/login.rs b/components/logins/src/login.rs index f7e20eff805..c016d3279a8 100644 --- a/components/logins/src/login.rs +++ b/components/logins/src/login.rs @@ -280,7 +280,8 @@ //! - `Login::fixup()`: Returns either the existing login if it is valid, a clone with invalid fields //! fixed up if it was safe to do so, or an error if the login is irreparably invalid. -use crate::{encryption::EncryptorDecryptor, error::*}; +use crate::error::*; +use db_crypto::EncryptorDecryptor; use rusqlite::Row; use serde_derive::*; use sync_guid::Guid; @@ -961,10 +962,11 @@ impl ValidateAndFixup for LoginEntry { } } +#[cfg(not(feature = "keydb"))] #[cfg(test)] pub mod test_utils { use super::*; - use crate::encryption::test_utils::encrypt_struct; + use crate::test_utils::encrypt_struct; // Factory function to make a new login // diff --git a/components/logins/src/logins.udl b/components/logins/src/logins.udl index 8f0492d945b..739fbd678cd 100644 --- a/components/logins/src/logins.udl +++ b/components/logins/src/logins.udl @@ -170,28 +170,11 @@ interface LoginsApiError { UnexpectedLoginsApiError(string reason); }; -[Trait, WithForeign] -interface EncryptorDecryptor { - [Throws=LoginsApiError] - bytes encrypt(bytes cleartext); - - [Throws=LoginsApiError] - bytes decrypt(bytes ciphertext); -}; - -[Trait, WithForeign] -interface KeyManager { - [Throws=LoginsApiError] - bytes get_key(); -}; +[External = "db_crypto"] +typedef trait_with_foreign EncryptorDecryptor; -interface StaticKeyManager { - constructor(string key); -}; - -interface ManagedEncryptorDecryptor { - constructor(KeyManager key_manager); -}; +[External = "db_crypto"] +typedef trait_with_foreign KeyManager; interface LoginStore { [Throws=LoginsApiError] diff --git a/components/logins/src/schema.rs b/components/logins/src/schema.rs index bac2c335c86..221a6283563 100644 --- a/components/logins/src/schema.rs +++ b/components/logins/src/schema.rs @@ -295,7 +295,7 @@ pub(crate) fn create(db: &Connection) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::encryption::test_utils::TEST_ENCDEC; + use crate::test_utils::TEST_ENCDEC; use crate::LoginDb; use nss_as::ensure_initialized; use rusqlite::Connection; diff --git a/components/logins/src/store.rs b/components/logins/src/store.rs index 0854c12e582..05d4af7d84d 100644 --- a/components/logins/src/store.rs +++ b/components/logins/src/store.rs @@ -2,12 +2,12 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::db::{LoginDb, LoginsDeletionMetrics}; -use crate::encryption::EncryptorDecryptor; use crate::error::*; use crate::login::{ BulkResultEntry, EncryptedLogin, Login, LoginCandidate, LoginEntry, LoginEntryWithMeta, }; use crate::LoginsSyncEngine; +use db_crypto::EncryptorDecryptor; use parking_lot::Mutex; use sql_support::run_maintenance; use std::path::Path; @@ -419,8 +419,8 @@ impl Default for RunMaintenanceOptions { #[cfg(test)] mod tests { use super::*; - use crate::encryption::{create_key, KeyManager, ManagedEncryptorDecryptor}; use crate::util; + use db_crypto::{create_key, KeyManager, ManagedEncryptorDecryptor}; use nss_as::ensure_initialized; use std::cmp::Reverse; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -614,7 +614,7 @@ mod tests { } impl KeyManager for CountingKeyManager { - fn get_key(&self) -> ApiResult> { + fn get_key(&self) -> db_crypto::ApiResult> { self.calls.fetch_add(1, Ordering::SeqCst); Ok(self.key.as_bytes().into()) } @@ -768,13 +768,13 @@ mod tests_keydb { #[async_trait] impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator { - async fn get_primary_password(&self) -> ApiResult { + async fn get_primary_password(&self) -> db_crypto::ApiResult { Ok(self.password.clone()) } - async fn on_authentication_success(&self) -> ApiResult<()> { + async fn on_authentication_success(&self) -> db_crypto::ApiResult<()> { Ok(()) } - async fn on_authentication_failure(&self) -> ApiResult<()> { + async fn on_authentication_failure(&self) -> db_crypto::ApiResult<()> { Ok(()) } } @@ -792,7 +792,10 @@ mod tests_keydb { let primary_password_authenticator = MockPrimaryPasswordAuthenticator { password: "password".to_string(), }; - let key_manager = NSSKeyManager::new(Arc::new(primary_password_authenticator)); + let key_manager = NSSKeyManager::new( + crate::KEY_NAME.to_string(), + Arc::new(primary_password_authenticator), + ); let encdec = ManagedEncryptorDecryptor::new(Arc::new(key_manager)); let store = LoginStore::new(profile_path().join("logins.db"), Arc::new(encdec)) .expect("store from fixtures"); diff --git a/components/logins/src/sync/engine.rs b/components/logins/src/sync/engine.rs index 6ca424433c2..becf5d62e22 100644 --- a/components/logins/src/sync/engine.rs +++ b/components/logins/src/sync/engine.rs @@ -6,13 +6,13 @@ use super::merge::{LocalLogin, MirrorLogin, SyncLoginData}; use super::update_plan::UpdatePlan; use super::SyncStatus; use crate::db::CLONE_ENTIRE_MIRROR_SQL; -use crate::encryption::EncryptorDecryptor; use crate::error::*; use crate::login::{EncryptedLogin, FXA_CREDENTIALS_ORIGIN}; use crate::schema; use crate::util; use crate::LoginDb; use crate::LoginStore; +use db_crypto::EncryptorDecryptor; use interrupt_support::SqlInterruptScope; use rusqlite::named_params; use sql_support::ConnExt; @@ -540,8 +540,8 @@ impl SyncEngine for LoginsSyncEngine { mod tests { use super::*; use crate::db::test_utils::insert_login; - use crate::encryption::test_utils::TEST_ENCDEC; use crate::login::test_utils::enc_login; + use crate::test_utils::TEST_ENCDEC; use crate::{LoginEntry, LoginFields, LoginMeta, SecureLoginFields}; use nss_as::ensure_initialized; use std::collections::HashMap; diff --git a/components/logins/src/sync/merge.rs b/components/logins/src/sync/merge.rs index e0b4b7c7ae2..66c2b234949 100644 --- a/components/logins/src/sync/merge.rs +++ b/components/logins/src/sync/merge.rs @@ -4,10 +4,10 @@ // Merging for Sync. use super::{IncomingLogin, LoginPayload}; -use crate::encryption::EncryptorDecryptor; use crate::error::*; use crate::login::EncryptedLogin; use crate::util; +use db_crypto::EncryptorDecryptor; use rusqlite::Row; use std::time::SystemTime; use sync15::bso::{IncomingBso, IncomingKind}; @@ -380,7 +380,7 @@ impl EncryptedLogin { #[cfg(test)] mod tests { use super::*; - use crate::encryption::test_utils::TEST_ENCDEC; + use crate::test_utils::TEST_ENCDEC; use nss_as::ensure_initialized; #[test] diff --git a/components/logins/src/sync/payload.rs b/components/logins/src/sync/payload.rs index a273221f95a..15244cdad3d 100644 --- a/components/logins/src/sync/payload.rs +++ b/components/logins/src/sync/payload.rs @@ -7,11 +7,11 @@ // This struct is used for fetching/sending login records to the server. There are a number // of differences between this and the top-level Login struct; some fields are renamed, some are // locally encrypted, etc. -use crate::encryption::EncryptorDecryptor; use crate::error::*; use crate::login::ValidateAndFixup; use crate::SecureLoginFields; use crate::{EncryptedLogin, LoginEntry, LoginFields, LoginMeta}; +use db_crypto::EncryptorDecryptor; use serde_derive::*; use sync15::bso::OutgoingBso; use sync_guid::Guid; @@ -239,8 +239,8 @@ where #[cfg(test)] mod tests { use super::*; - use crate::encryption::test_utils::{encrypt_struct, TEST_ENCDEC}; use crate::sync::merge::SyncLoginData; + use crate::test_utils::{encrypt_struct, TEST_ENCDEC}; use crate::{EncryptedLogin, LoginFields, LoginMeta, SecureLoginFields}; use sync15::bso::IncomingBso; diff --git a/components/logins/src/sync/update_plan.rs b/components/logins/src/sync/update_plan.rs index c890b3f34da..ef145794c27 100644 --- a/components/logins/src/sync/update_plan.rs +++ b/components/logins/src/sync/update_plan.rs @@ -4,9 +4,9 @@ use super::merge::{LocalLogin, MirrorLogin}; use super::{IncomingLogin, SyncStatus}; -use crate::encryption::EncryptorDecryptor; use crate::error::*; use crate::util; +use db_crypto::EncryptorDecryptor; use interrupt_support::SqlInterruptScope; use rusqlite::{named_params, Connection}; use std::time::SystemTime; @@ -325,8 +325,8 @@ mod tests { get_server_modified, insert_encrypted_login, insert_login, }; use crate::db::LoginDb; - use crate::encryption::test_utils::TEST_ENCDEC; use crate::login::test_utils::enc_login; + use crate::test_utils::TEST_ENCDEC; fn inc_login(id: &str, password: &str) -> crate::sync::IncomingLogin { IncomingLogin { diff --git a/components/support/db-crypto/Cargo.toml b/components/support/db-crypto/Cargo.toml new file mode 100644 index 00000000000..e401a5acf22 --- /dev/null +++ b/components/support/db-crypto/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "db-crypto" +version = "0.1.0" +edition = "2021" +authors = ["Naomi Kirby "] +description = "Database cryptography support library" +license = "MPL-2.0" + +[features] +default = [] +# Enables `NSSKeyManager`, which stores the logins encryption key in NSS's +# `key4.db` (wrapped with a key derived from the primary password, if set). +# Used on Desktop to integrate with the existing NSS key store and primary +# password flow. +keydb = ["nss-as/keydb", "dep:async-trait", "dep:futures"] + +[dependencies] +uniffi = { version = "0.31" } +anyhow = "1.0" +async-trait = { version = "0.1", optional = true } +error-support = { path = "../error" } +futures = { version = "0.3", optional = true, features = ["executor"] } +jwcrypto = { path = "../jwcrypto" } +nss-as = { path = "../rc_crypto/nss", default-features = false } +serde_json = "1" +parking_lot = ">=0.11,<=0.12" + +thiserror = "2" + +[build-dependencies] +uniffi = { version = "0.31", features = ["build"] } diff --git a/components/support/db-crypto/android/build.gradle b/components/support/db-crypto/android/build.gradle new file mode 100644 index 00000000000..9afec5d5f69 --- /dev/null +++ b/components/support/db-crypto/android/build.gradle @@ -0,0 +1,10 @@ +apply from: "$appServicesRootDir/build-scripts/component-common.gradle" +apply from: "$publishDir/publish.gradle" + +android { + namespace 'org.mozilla.appservices.db_crypto' +} + +ext.configureUniFFIBindgen("db_crypto") +ext.dependsOnTheMegazord() +ext.configurePublish(appServicesGroupId, project.name, project.ext.description) diff --git a/components/support/db-crypto/android/proguard-rules.pro b/components/support/db-crypto/android/proguard-rules.pro new file mode 100644 index 00000000000..cf504086aa2 --- /dev/null +++ b/components/support/db-crypto/android/proguard-rules.pro @@ -0,0 +1,22 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + diff --git a/components/support/db-crypto/android/src/main/AndroidManifest.xml b/components/support/db-crypto/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..e9861f59ac5 --- /dev/null +++ b/components/support/db-crypto/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/components/support/db-crypto/build.rs b/components/support/db-crypto/build.rs new file mode 100644 index 00000000000..a516019b5a6 --- /dev/null +++ b/components/support/db-crypto/build.rs @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +fn main() { + uniffi::generate_scaffolding("./src/db-crypto.udl").unwrap(); +} diff --git a/components/support/db-crypto/src/db-crypto.udl b/components/support/db-crypto/src/db-crypto.udl new file mode 100644 index 00000000000..26a587f894d --- /dev/null +++ b/components/support/db-crypto/src/db-crypto.udl @@ -0,0 +1,81 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +namespace db_crypto { + /// We expose the crypto primitives on the namespace + + /// Create a new, random, encryption key. + [Throws=DbCryptoApiError] + string create_key(); + + /// Create a "canary" string, which can be used to test if the encryption + //key is still valid for the logins data + [Throws=DbCryptoApiError] + string create_canary([ByRef]string text, [ByRef]string encryption_key); + + /// Check that key is still valid using the output of `create_canary`. + //`text` much match the text you initially passed to `create_canary()` + [Throws=DbCryptoApiError] + boolean check_canary([ByRef]string canary, [ByRef]string text, [ByRef]string encryption_key); +}; + +/// These are the errors returned by our public API. +[Error] +interface DbCryptoApiError { + /// NSS not initialized. + NSSUninitialized(); + + /// NSS error during authentication + NSSAuthenticationError(string reason); + + /// error during authentication (in PrimaryPasswordAuthenticator) + AuthenticationError(string reason); + + /// authentication has been cancelled. + AuthenticationCanceled(); + + /// Encryption key is missing. + MissingKey(); + + /// Encryption key is not valid. + InvalidKey(); + + /// encryption failed + EncryptionFailed(string reason); + + /// decryption failed + DecryptionFailed(string reason); + + /// An operation was interrupted at the request of the consuming app. + Interrupted(string reason); + + /// something internal went wrong which doesn't have a public error value + /// because the consuming app can not reasonably take any action to resolve it. + /// The underlying error will have been logged and reported. + /// (ideally would just be `Unexpected`, but that would be a breaking change) + UnexpectedDbCryptoApiError(string reason); +}; + +[Trait, WithForeign] +interface EncryptorDecryptor { + [Throws=DbCryptoApiError] + bytes encrypt(bytes cleartext); + + [Throws=DbCryptoApiError] + bytes decrypt(bytes ciphertext); +}; + +[Trait, WithForeign] +interface KeyManager { + [Throws=DbCryptoApiError] + bytes get_key(); +}; + +interface StaticKeyManager { + constructor(string key); +}; + +interface ManagedEncryptorDecryptor { + constructor(KeyManager key_manager); +}; diff --git a/components/logins/src/encryption.rs b/components/support/db-crypto/src/encryption.rs similarity index 84% rename from components/logins/src/encryption.rs rename to components/support/db-crypto/src/encryption.rs index 9b180e5eb46..d28a414fadc 100644 --- a/components/logins/src/encryption.rs +++ b/components/support/db-crypto/src/encryption.rs @@ -3,14 +3,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -// This is the *local* encryption support - it has nothing to do with the -// encryption used by sync. +// This is the *local* database encryption support - it has nothing to do +// with the encryption used by sync. // For context, what "local encryption" means in this context is: // * We use regular sqlite, but ensure that sensitive data is encrypted in the DB in the // `secure_fields` column. The encryption key is managed by the app. -// * The `decrypt_struct` and `encrypt_struct` functions are used to convert between an encrypted -// `secure_fields` string and a decrypted `SecureFields` struct +// * The `decrypt` and `encrypt` functions are used to convert between an encrypted +// string and a decrypted string. // * Most API functions return `EncryptedLogin` which has its data encrypted. // // This makes life tricky for Sync - sync has its own encryption and its own @@ -27,7 +27,7 @@ // low level sync code. // To make life a little easier, we do that via a struct. // -// Consumers of the Login component have 3 options for setting up encryption: +// Consumers of the an encrypted database have 3 options for setting up encryption: // 1. Implement EncryptorDecryptor directly // eg `LoginStore::new(MyEncryptorDecryptor)` // 2. Implement KeyManager and use ManagedEncryptorDecryptor @@ -112,20 +112,20 @@ impl EncryptorDecryptor for ManagedEncryptorDecryptor { let keybytes = self .key_manager .get_key() - .map_err(|_| LoginsApiError::MissingKey)?; - let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?; + .map_err(|_| DbCryptoApiError::MissingKey)?; + let key = std::str::from_utf8(&keybytes).map_err(|_| DbCryptoApiError::InvalidKey)?; let encdec = jwcrypto::EncryptorDecryptor::new(key) - .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?; + .map_err(|_: jwcrypto::JwCryptoError| DbCryptoApiError::InvalidKey)?; let cleartext = - std::str::from_utf8(&clearbytes).map_err(|e| LoginsApiError::EncryptionFailed { + std::str::from_utf8(&clearbytes).map_err(|e| DbCryptoApiError::EncryptionFailed { reason: e.to_string(), })?; encdec .encrypt(cleartext) .map_err( - |e: jwcrypto::JwCryptoError| LoginsApiError::EncryptionFailed { + |e: jwcrypto::JwCryptoError| DbCryptoApiError::EncryptionFailed { reason: e.to_string(), }, ) @@ -136,20 +136,20 @@ impl EncryptorDecryptor for ManagedEncryptorDecryptor { let keybytes = self .key_manager .get_key() - .map_err(|_| LoginsApiError::MissingKey)?; - let key = std::str::from_utf8(&keybytes).map_err(|_| LoginsApiError::InvalidKey)?; + .map_err(|_| DbCryptoApiError::MissingKey)?; + let key = std::str::from_utf8(&keybytes).map_err(|_| DbCryptoApiError::InvalidKey)?; let encdec = jwcrypto::EncryptorDecryptor::new(key) - .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?; + .map_err(|_: jwcrypto::JwCryptoError| DbCryptoApiError::InvalidKey)?; let ciphertext = - std::str::from_utf8(&cipherbytes).map_err(|e| LoginsApiError::DecryptionFailed { + std::str::from_utf8(&cipherbytes).map_err(|e| DbCryptoApiError::DecryptionFailed { reason: e.to_string(), })?; encdec .decrypt(ciphertext) .map_err( - |e: jwcrypto::JwCryptoError| LoginsApiError::DecryptionFailed { + |e: jwcrypto::JwCryptoError| DbCryptoApiError::DecryptionFailed { reason: e.to_string(), }, ) @@ -212,36 +212,37 @@ pub trait PrimaryPasswordAuthenticator: Send + Sync { /// # Examples /// ```no_run /// use async_trait::async_trait; -/// use logins::encryption::KeyManager; -/// use logins::{PrimaryPasswordAuthenticator, LoginsApiError, NSSKeyManager}; +/// use db_crypto::KeyManager; +/// use db_crypto::{PrimaryPasswordAuthenticator, DbCryptoApiError, NSSKeyManager}; /// use std::sync::Arc; /// /// struct MyPrimaryPasswordAuthenticator {} /// /// #[async_trait] /// impl PrimaryPasswordAuthenticator for MyPrimaryPasswordAuthenticator { -/// async fn get_primary_password(&self) -> Result { +/// async fn get_primary_password(&self) -> Result { /// // Most likely, you would want to prompt for a password. /// // let password = prompt_string("primary password").unwrap_or_default(); /// Ok("secret".to_string()) /// } /// -/// async fn on_authentication_success(&self) -> Result<(), LoginsApiError> { +/// async fn on_authentication_success(&self) -> Result<(), DbCryptoApiError> { /// println!("success"); /// Ok(()) /// } /// -/// async fn on_authentication_failure(&self) -> Result<(), LoginsApiError> { +/// async fn on_authentication_failure(&self) -> Result<(), DbCryptoApiError> { /// println!("this did not work, please try again:"); /// Ok(()) /// } /// } -/// let key_manager = NSSKeyManager::new(Arc::new(MyPrimaryPasswordAuthenticator {})); +/// let key_manager = NSSKeyManager::new(String::from("example"), Arc::new(MyPrimaryPasswordAuthenticator {})); /// assert_eq!(key_manager.get_key().unwrap().len(), 63); /// ``` #[cfg(feature = "keydb")] #[derive(uniffi::Object)] pub struct NSSKeyManager { + key_name: String, primary_password_authenticator: Arc, cached_key: RwLock>>, } @@ -253,9 +254,13 @@ impl NSSKeyManager { /// There must be a previous initializiation of NSS before initializing /// `NSSKeyManager`, otherwise this panics. #[uniffi::constructor()] - pub fn new(primary_password_authenticator: Arc) -> Self { + pub fn new( + key_name: String, + primary_password_authenticator: Arc, + ) -> Self { assert_nss_initialized(); Self { + key_name, primary_password_authenticator, cached_key: RwLock::new(None), } @@ -266,15 +271,11 @@ impl NSSKeyManager { } } -/// Identifier for the logins key, under which the key is stored in NSS. -#[cfg(feature = "keydb")] -static KEY_NAME: &str = "as-logins-key"; - // wrapp `authentication_with_primary_password_is_needed` into an ApiResult #[cfg(feature = "keydb")] fn api_authentication_with_primary_password_is_needed() -> ApiResult { authentication_with_primary_password_is_needed().map_err(|e: nss_as::Error| { - LoginsApiError::NSSAuthenticationError { + DbCryptoApiError::NSSAuthenticationError { reason: e.to_string(), } }) @@ -284,7 +285,7 @@ fn api_authentication_with_primary_password_is_needed() -> ApiResult { #[cfg(feature = "keydb")] fn api_authenticate_with_primary_password(primary_password: &str) -> ApiResult { authenticate_with_primary_password(primary_password).map_err(|e: nss_as::Error| { - LoginsApiError::NSSAuthenticationError { + DbCryptoApiError::NSSAuthenticationError { reason: e.to_string(), } }) @@ -329,7 +330,8 @@ impl KeyManager for NSSKeyManager { return Ok(bytes); } - let key = get_or_create_aes256_key(KEY_NAME).map_err(|_| LoginsApiError::MissingKey)?; + let key = get_or_create_aes256_key(self.key_name.as_str()) + .map_err(|_| DbCryptoApiError::MissingKey)?; let mut bytes: Vec = Vec::new(); serde_json::to_writer( &mut bytes, @@ -348,7 +350,7 @@ pub fn create_canary(text: &str, key: &str) -> ApiResult { pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult { let encdec = jwcrypto::EncryptorDecryptor::new(key) - .map_err(|_: jwcrypto::JwCryptoError| LoginsApiError::InvalidKey)?; + .map_err(|_: jwcrypto::JwCryptoError| DbCryptoApiError::InvalidKey)?; Ok(encdec.check_canary(canary, text).unwrap_or(false)) } @@ -357,27 +359,6 @@ pub fn create_key() -> ApiResult { Ok(jwcrypto::EncryptorDecryptor::create_key()?) } -#[cfg(test)] -pub mod test_utils { - use super::*; - use serde::{de::DeserializeOwned, Serialize}; - - lazy_static::lazy_static! { - pub static ref TEST_ENCRYPTION_KEY: String = serde_json::to_string(&jwcrypto::Jwk::new_direct_key(Some("test-key".to_string())).unwrap()).unwrap(); - pub static ref TEST_ENCDEC: Arc = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager { key: TEST_ENCRYPTION_KEY.clone() }))); - } - - pub fn encrypt_struct(fields: &T) -> String { - let string = serde_json::to_string(fields).unwrap(); - let cipherbytes = TEST_ENCDEC.encrypt(string.as_bytes().into()).unwrap(); - std::str::from_utf8(&cipherbytes).unwrap().to_owned() - } - pub fn decrypt_struct(ciphertext: String) -> T { - let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap(); - serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap() - } -} - #[cfg(not(feature = "keydb"))] #[cfg(test)] mod tests { @@ -401,7 +382,7 @@ mod tests { let encdec = ManagedEncryptorDecryptor { key_manager }; assert!(matches!( encdec.encrypt("secret".as_bytes().into()).err().unwrap(), - LoginsApiError::InvalidKey + DbCryptoApiError::InvalidKey )); } @@ -411,14 +392,14 @@ mod tests { struct MyKeyManager {} impl KeyManager for MyKeyManager { fn get_key(&self) -> ApiResult> { - Err(LoginsApiError::MissingKey) + Err(DbCryptoApiError::MissingKey) } } let key_manager = Arc::new(MyKeyManager {}); let encdec = ManagedEncryptorDecryptor { key_manager }; assert!(matches!( encdec.encrypt("secret".as_bytes().into()).err().unwrap(), - LoginsApiError::MissingKey + DbCryptoApiError::MissingKey )); } @@ -467,7 +448,7 @@ mod tests { let bad_key = "bad_key".to_owned(); assert!(matches!( check_canary(&canary, CANARY_TEXT, &bad_key).err().unwrap(), - LoginsApiError::InvalidKey + DbCryptoApiError::InvalidKey )); } } @@ -498,7 +479,7 @@ mod tests_keydb { fn profile_path() -> PathBuf { std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../support/rc_crypto/nss/fixtures/profile") + .join("../rc_crypto/nss/fixtures/profile") } #[test] @@ -520,7 +501,10 @@ mod tests_keydb { let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator { password: "password".to_string(), }; - let nss_key_manager = NSSKeyManager::new(Arc::new(mock_primary_password_authenticator)); + let nss_key_manager = NSSKeyManager::new( + String::from("as-logins-key"), + Arc::new(mock_primary_password_authenticator), + ); // key from fixtures/profile/key4.db let expected = [ 123, 34, 107, 116, 121, 34, 58, 34, 111, 99, 116, 34, 44, 34, 107, 34, 58, 34, 66, 74, @@ -536,9 +520,13 @@ mod tests_keydb { fn test_nss_key_manager_caching() { ensure_initialized_with_profile_dir(profile_path()); // `password` is the primary password of the profile fixture - let nss_key_manager = NSSKeyManager::new(Arc::new(MockPrimaryPasswordAuthenticator { + let mock_primary_password_authenticator = MockPrimaryPasswordAuthenticator { password: "password".to_string(), - })); + }; + let nss_key_manager = NSSKeyManager::new( + String::from("as-logins-key"), + Arc::new(mock_primary_password_authenticator), + ); let key = nss_key_manager.get_key().unwrap(); assert_eq!(*nss_key_manager.cached_key.read(), Some(key.clone())); diff --git a/components/support/db-crypto/src/error.rs b/components/support/db-crypto/src/error.rs new file mode 100644 index 00000000000..76bf4b2a61a --- /dev/null +++ b/components/support/db-crypto/src/error.rs @@ -0,0 +1,102 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +pub type Result = std::result::Result; +// Functions which are part of the public API should use this Result. +pub type ApiResult = std::result::Result; + +pub use error_support::{breadcrumb, handle_error, report_error}; +pub use error_support::{debug, error, info, trace, warn}; + +use error_support::{ErrorHandling, GetErrorHandling}; +use jwcrypto::JwCryptoError; + +// Errors we return via the public interface. +#[derive(Debug, thiserror::Error)] +pub enum DbCryptoApiError { + #[error("NSS not initialized")] + NSSUninitialized, + + #[error("NSS error during authentication: {reason}")] + NSSAuthenticationError { reason: String }, + + #[error("error during authentication: {reason}")] + AuthenticationError { reason: String }, + + #[error("authentication cancelled")] + AuthenticationCanceled, + + #[error("Encryption key is missing.")] + MissingKey, + + #[error("Encryption key is not valid.")] + InvalidKey, + + #[error("encryption failed: {reason}")] + EncryptionFailed { reason: String }, + + #[error("decryption failed: {reason}")] + DecryptionFailed { reason: String }, + + #[error("{reason}")] + Interrupted { reason: String }, + + #[error("Unexpected Error: {reason}")] + UnexpectedDbCryptoApiError { reason: String }, +} + +/// Encryption error type +/// These are "internal" errors used by the implementation. This error type +/// is never returned to the consumer. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("encryption failed: {0:?}")] + EncryptionFailed(String), + + #[error("decryption failed: {0:?}")] + DecryptionFailed(String), + + #[error("CryptoError({0})")] + CryptoError(#[from] JwCryptoError), + + #[error("IOError: {0}")] + IOError(#[from] std::io::Error), +} + +// Define how our internal errors are handled and converted to external errors +// See `support/error/README.md` for how this works, especially the warning about PII. +impl GetErrorHandling for Error { + type ExternalError = DbCryptoApiError; + + fn get_error_handling(&self) -> ErrorHandling { + // Unexpected errors that we report to Sentry. We should watch the reports for these + // and do one or more of these things if we see them: + // - Fix the underlying issue + // - Add breadcrumbs or other context to help uncover the issue + // - Decide that these are expected errors and move them to the above case + ErrorHandling::convert(DbCryptoApiError::UnexpectedDbCryptoApiError { + reason: self.to_string(), + }) + .report_error("encdec-unexpected") + } +} + +// The bridged sync engine (`sync::bridge`) deals in `anyhow::Result`, as that's +// what the `sync15` BridgedEngine traits use. This lets UniFFI map those errors +// onto our public error type when the bridge methods are exposed via the UDL. +impl From for DbCryptoApiError { + fn from(value: anyhow::Error) -> Self { + DbCryptoApiError::UnexpectedDbCryptoApiError { + reason: value.to_string(), + } + } +} + +impl From for DbCryptoApiError { + fn from(error: uniffi::UnexpectedUniFFICallbackError) -> Self { + DbCryptoApiError::UnexpectedDbCryptoApiError { + reason: error.to_string(), + } + } +} diff --git a/components/support/db-crypto/src/lib.rs b/components/support/db-crypto/src/lib.rs new file mode 100644 index 00000000000..60f384da850 --- /dev/null +++ b/components/support/db-crypto/src/lib.rs @@ -0,0 +1,22 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#![allow(unknown_lints)] +#![warn(rust_2018_idioms)] + +#[macro_use] +mod error; + +mod encryption; + +pub use crate::encryption::{ + EncryptorDecryptor, KeyManager, ManagedEncryptorDecryptor, StaticKeyManager, +}; +uniffi::include_scaffolding!("db-crypto"); + +#[cfg(feature = "keydb")] +pub use crate::encryption::{NSSKeyManager, PrimaryPasswordAuthenticator}; + +pub use crate::encryption::{check_canary, create_canary, create_key}; +pub use crate::error::*; diff --git a/components/support/db-crypto/uniffi.toml b/components/support/db-crypto/uniffi.toml new file mode 100644 index 00000000000..0cddacfb6ef --- /dev/null +++ b/components/support/db-crypto/uniffi.toml @@ -0,0 +1,7 @@ +[bindings.kotlin] +package_name = "mozilla.appservices.db_crypto" +omit_checksums = true + +[bindings.swift] +ffi_module_name = "MozillaRustComponents" +ffi_module_filename = "db_cryptoFFI" diff --git a/examples/sync-pass/Cargo.toml b/examples/sync-pass/Cargo.toml index 7c43428d973..a82d208aeea 100644 --- a/examples/sync-pass/Cargo.toml +++ b/examples/sync-pass/Cargo.toml @@ -13,6 +13,7 @@ path = "src/sync-pass.rs" [dev-dependencies] base64 = "0.21" logins = { path = "../../components/logins", features = ["keydb"] } +db-crypto = { path = "../../components/support/db-crypto", features = ["keydb"] } sync15 = { path = "../../components/sync15" } sync-guid = { path = "../../components/support/guid" } log = "0.4" diff --git a/examples/sync-pass/src/sync-pass.rs b/examples/sync-pass/src/sync-pass.rs index d27b4ad4385..4af1ec083e2 100644 --- a/examples/sync-pass/src/sync-pass.rs +++ b/examples/sync-pass/src/sync-pass.rs @@ -7,8 +7,10 @@ use cli_support::fxa_creds::{get_default_fxa_config, CliFxa, SYNC_SCOPE}; use cli_support::prompt::{prompt_char, prompt_password, prompt_string, prompt_usize}; -use logins::encryption::{ManagedEncryptorDecryptor, NSSKeyManager, PrimaryPasswordAuthenticator}; -use logins::{Login, LoginEntry, LoginStore, LoginsApiError, LoginsSyncEngine, ValidateAndFixup}; +use db_crypto::{ + DbCryptoApiError, ManagedEncryptorDecryptor, NSSKeyManager, PrimaryPasswordAuthenticator, +}; +use logins::{Login, LoginEntry, LoginStore, LoginsSyncEngine, ValidateAndFixup}; use async_trait::async_trait; use std::sync::Arc; @@ -296,24 +298,28 @@ fn prompt_record_id(s: &LoginStore, action: &str) -> Result> { struct MyPrimaryPasswordAuthenticator {} #[async_trait] impl PrimaryPasswordAuthenticator for MyPrimaryPasswordAuthenticator { - async fn get_primary_password(&self) -> Result { + async fn get_primary_password(&self) -> Result { let password = prompt_password("primary password").unwrap_or_default(); Ok(password) } - async fn on_authentication_success(&self) -> Result<(), LoginsApiError> { + async fn on_authentication_success(&self) -> Result<(), DbCryptoApiError> { println!("success"); Ok(()) } - async fn on_authentication_failure(&self) -> Result<(), LoginsApiError> { + async fn on_authentication_failure(&self) -> Result<(), DbCryptoApiError> { println!("this did not work, please try again:"); Ok(()) } } fn open_database(db_path: &str) -> Result { - let key_manager = NSSKeyManager::new(Arc::new(MyPrimaryPasswordAuthenticator {})); + let key_name: &str = "as-logins-key"; + let key_manager = NSSKeyManager::new( + key_name.to_string(), + Arc::new(MyPrimaryPasswordAuthenticator {}), + ); let encdec = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(key_manager))); let store = LoginStore::new(db_path, encdec)?; Ok(store) diff --git a/megazords/fenix-dylib/megazord_stub.c b/megazords/fenix-dylib/megazord_stub.c index 2d59c462746..5984bca6931 100644 --- a/megazords/fenix-dylib/megazord_stub.c +++ b/megazords/fenix-dylib/megazord_stub.c @@ -13,6 +13,7 @@ extern int MOZ_EXPORT ffi_ads_client_uniffi_contract_version(); extern int MOZ_EXPORT ffi_autofill_uniffi_contract_version(); extern int MOZ_EXPORT ffi_crashtest_uniffi_contract_version(); +extern int MOZ_EXPORT ffi_dbcrypto_uniffi_contract_version(); extern int MOZ_EXPORT ffi_fxa_client_uniffi_contract_version(); extern int MOZ_EXPORT ffi_init_rust_components_uniffi_contract_version(); extern int MOZ_EXPORT ffi_logins_uniffi_contract_version(); @@ -77,6 +78,7 @@ void _local_megazord_dummy_symbol() { ffi_ads_client_uniffi_contract_version(); ffi_autofill_uniffi_contract_version(); ffi_crashtest_uniffi_contract_version(); + ffi_dbcrypto_uniffi_contract_version(); ffi_fxa_client_uniffi_contract_version(); ffi_init_rust_components_uniffi_contract_version(); ffi_logins_uniffi_contract_version(); diff --git a/megazords/full/Cargo.toml b/megazords/full/Cargo.toml index da6d3a77133..a136d6d59c4 100644 --- a/megazords/full/Cargo.toml +++ b/megazords/full/Cargo.toml @@ -40,6 +40,7 @@ mozilla-central-workspace-hack = { version = "0.1", features = ["megazord"], opt # NSS link chain. The megazord cdylib produces a self-contained Rust artifact # and needs static mozpkix + pure_virtual on top of the NSS dylibs. rc_crypto = { path = "../../components/support/rc_crypto" } +db-crypto = { path = "../../components/support/db-crypto" } [features] mozbuild-rustlib = ["rc_crypto/mozbuild-rustlib"] diff --git a/megazords/full/src/lib.rs b/megazords/full/src/lib.rs index 62b19107279..369bdd676af 100644 --- a/megazords/full/src/lib.rs +++ b/megazords/full/src/lib.rs @@ -12,6 +12,7 @@ use std::os::raw::c_char; pub use ads_client; pub use autofill; pub use crashtest; +pub use db_crypto; pub use error_support; pub use fxa_client; pub use init_rust_components; diff --git a/megazords/ios-rust/Cargo.toml b/megazords/ios-rust/Cargo.toml index a44e55f4fc4..faa4ea21e24 100644 --- a/megazords/ios-rust/Cargo.toml +++ b/megazords/ios-rust/Cargo.toml @@ -23,6 +23,7 @@ places = { path = "../../components/places" } remote_settings = { path = "../../components/remote_settings", features=["telemetry-submission"] } suggest = { path = "../../components/suggest" } sync15 = { path = "../../components/sync15" } +db-crypto = { path = "../../components/support/db-crypto" } error-support = { path = "../../components/support/error" } tracing-support = { path = "../../components/support/tracing" } sync_manager = { path = "../../components/sync_manager" } diff --git a/megazords/ios-rust/src/lib.rs b/megazords/ios-rust/src/lib.rs index 0580781e784..bff1167ffb9 100644 --- a/megazords/ios-rust/src/lib.rs +++ b/megazords/ios-rust/src/lib.rs @@ -10,6 +10,7 @@ pub use as_ohttp_client; pub use autofill; pub use context_id; pub use crashtest; +pub use db_crypto; pub use error_support; pub use fxa_client; pub use init_rust_components; diff --git a/testing/sync-test/Cargo.toml b/testing/sync-test/Cargo.toml index 817f46d93cd..62084f01e9b 100644 --- a/testing/sync-test/Cargo.toml +++ b/testing/sync-test/Cargo.toml @@ -30,3 +30,4 @@ serde_json = "1.0" base64 = "0.21" cli-support = { path = "../../examples/cli-support" } nss-as = { path = "../../components/support/rc_crypto/nss" } +db-crypto = { path = "../../components/support/db-crypto" } diff --git a/testing/sync-test/src/auth.rs b/testing/sync-test/src/auth.rs index a24d60642f3..a78d3c91102 100644 --- a/testing/sync-test/src/auth.rs +++ b/testing/sync-test/src/auth.rs @@ -4,10 +4,8 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ use anyhow::Result; use autofill::db::store::Store as AutofillStore; use cli_support::fxa_creds::CliFxa; +use db_crypto::{create_key, EncryptorDecryptor, ManagedEncryptorDecryptor, StaticKeyManager}; use fxa_client::{Device, FxaConfig, FxaServer}; -use logins::encryption::{ - create_key, EncryptorDecryptor, ManagedEncryptorDecryptor, StaticKeyManager, -}; use logins::LoginStore; use std::collections::{hash_map::RandomState, HashMap}; use std::sync::Arc; diff --git a/testing/sync-test/src/logins.rs b/testing/sync-test/src/logins.rs index de478811d60..d0e1f614176 100644 --- a/testing/sync-test/src/logins.rs +++ b/testing/sync-test/src/logins.rs @@ -4,10 +4,8 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ use crate::auth::TestClient; use crate::testing::TestGroup; use anyhow::Result; -use logins::{ - encryption::{create_key, ManagedEncryptorDecryptor, StaticKeyManager}, - ApiResult as LoginResult, Login, LoginEntry, LoginStore, -}; +use db_crypto::{create_key, ManagedEncryptorDecryptor, StaticKeyManager}; +use logins::{ApiResult as LoginResult, Login, LoginEntry, LoginStore}; use std::sync::Arc; use std::{collections::hash_map::RandomState, collections::HashMap};