Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e744487
Create encryption crate with login crypto code
oskirby Jul 30, 2026
a508894
Rename LoginApiError to EncryptionApiError
oskirby Jul 31, 2026
a88c4d7
Make the login key functions wrappers of the encryption crate
oskirby Aug 4, 2026
d6de42e
Add uniffi interfaces to the encryption crate
oskirby Aug 12, 2026
af37a09
Drop logins/src/encryption.rs in favor of new crate
oskirby Aug 12, 2026
53f7f64
Fixup the sync-pass example
oskirby Aug 12, 2026
c6a8a2c
NSSKeyManager::new() should take a std::String instead of std::str
oskirby Aug 12, 2026
8c7eada
Fixup test_utils import when keydb feature not enabled
oskirby Aug 13, 2026
943abbf
Fix doc tests
oskirby Aug 13, 2026
c097382
Remove unused std::sync::Arc
oskirby Aug 13, 2026
6f5ef46
Attempt to fixup formatting checks
oskirby Aug 13, 2026
d9404a4
Add android bindings too?
oskirby Aug 13, 2026
d350b60
Maybe fix android KeyManager import?
oskirby Aug 13, 2026
f7e60b0
Try to make the clippy linter happy
oskirby Aug 14, 2026
8c4bd88
And once more with feeling
oskirby Aug 14, 2026
c3166d6
I think the test enc_login() function is only used when keydb is unset
oskirby Aug 14, 2026
696c357
Fix key_name used by sync-pass
oskirby Aug 17, 2026
085f323
Add megazord bindings for iOS and Android
oskirby Aug 17, 2026
7524b80
Add Swift bindings to uniffi as well
oskirby Aug 17, 2026
209bf05
Align build.gradle to latest template
oskirby Aug 17, 2026
3b67d6e
Add license, drop unused deps and set to rust 2021 edition
oskirby Aug 17, 2026
b1c2941
Remove some unused stuff from logins.udl
oskirby Aug 18, 2026
4b5402d
Add parking_lot dependency after rebase
oskirby Aug 19, 2026
39a9868
Add create_key wrappers back after rebase
oskirby Aug 19, 2026
a322759
Fix keydb tests after rebase
oskirby Aug 19, 2026
73a002e
Rename crate from encryption to db-crypto
oskirby Aug 26, 2026
ffde274
Fix sync-test and lint
oskirby Aug 27, 2026
9a43954
Add a changelog entry
oskirby Aug 27, 2026
ef101dd
Fix external UniFFI reference
oskirby Aug 27, 2026
cf77c1e
I am running out of ideas on this android UniFFI thing
oskirby Aug 27, 2026
be4f0a1
Fix login tests after rebase
oskirby Aug 28, 2026
b110afd
Fix formatting
oskirby Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .buildconfig-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ✨
Expand Down
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion components/logins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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"] }
Expand Down
1 change: 1 addition & 0 deletions components/logins/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 4 additions & 5 deletions components/logins/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -84,8 +84,7 @@ impl LoginDb {

#[cfg(test)]
pub fn open_in_memory() -> Self {
let encdec: Arc<dyn EncryptorDecryptor> =
crate::encryption::test_utils::TEST_ENCDEC.clone();
let encdec: Arc<dyn EncryptorDecryptor> = crate::test_utils::TEST_ENCDEC.clone();
Self::with_connection(Connection::open_in_memory().unwrap(), encdec).unwrap()
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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};

Expand Down
33 changes: 32 additions & 1 deletion components/logins/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub type ApiResult<T> = std::result::Result<T, LoginsApiError>;
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;

Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -214,3 +218,30 @@ impl From<uniffi::UnexpectedUniFFICallbackError> for LoginsApiError {
}
}
}

impl From<DbCryptoApiError> 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 }
}
}
}
}
49 changes: 42 additions & 7 deletions components/logins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<String> {
Ok(db_crypto::create_canary(text, key)?)
}

pub fn check_canary(canary: &str, text: &str, key: &str) -> ApiResult<bool> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#[handle_error(Error)] here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The original code didn't have it before this PR, but I also don't see a reason why not to add it.

Ok(db_crypto::check_canary(canary, text, key)?)
}

#[handle_error(Error)]
pub fn create_key() -> ApiResult<String> {
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
Expand All @@ -68,8 +82,29 @@ pub fn create_login_store_with_nss_keymanager(
primary_password_authenticator: Arc<dyn PrimaryPasswordAuthenticator>,
) -> ApiResult<Arc<LoginStore>> {
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<ManagedEncryptorDecryptor> = Arc::new(ManagedEncryptorDecryptor::new(Arc::new(StaticKeyManager::new(TEST_ENCRYPTION_KEY.clone()))));
}

pub fn encrypt_struct<T: Serialize>(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<T: DeserializeOwned>(ciphertext: String) -> T {
let jsonbytes = TEST_ENCDEC.decrypt(ciphertext.as_bytes().into()).unwrap();
serde_json::from_str(std::str::from_utf8(&jsonbytes).unwrap()).unwrap()
}
}
6 changes: 4 additions & 2 deletions components/logins/src/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
//
Expand Down
25 changes: 4 additions & 21 deletions components/logins/src/logins.udl
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion components/logins/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading
Loading