Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d191081
Create encryption crate with login crypto code
oskirby Jul 30, 2026
604ce0f
Rename LoginApiError to EncryptionApiError
oskirby Jul 31, 2026
8b2c8c1
Make the login key functions wrappers of the encryption crate
oskirby Aug 4, 2026
c5dc5b9
Add uniffi interfaces to the encryption crate
oskirby Aug 12, 2026
3f899b6
Drop logins/src/encryption.rs in favor of new crate
oskirby Aug 12, 2026
bea8318
Fixup the sync-pass example
oskirby Aug 12, 2026
2379943
NSSKeyManager::new() should take a std::String instead of std::str
oskirby Aug 12, 2026
af8f312
Fixup test_utils import when keydb feature not enabled
oskirby Aug 13, 2026
4775c65
Fix doc tests
oskirby Aug 13, 2026
897e7ef
Remove unused std::sync::Arc
oskirby Aug 13, 2026
9472018
Attempt to fixup formatting checks
oskirby Aug 13, 2026
b96b0b0
Add android bindings too?
oskirby Aug 13, 2026
009473e
Maybe fix android KeyManager import?
oskirby Aug 13, 2026
7ee96d5
Try to make the clippy linter happy
oskirby Aug 14, 2026
20b2992
And once more with feeling
oskirby Aug 14, 2026
b8f636c
I think the test enc_login() function is only used when keydb is unset
oskirby Aug 14, 2026
f926ac5
Fix key_name used by sync-pass
oskirby Aug 17, 2026
acca902
Add megazord bindings for iOS and Android
oskirby Aug 17, 2026
a825e2b
Add Swift bindings to uniffi as well
oskirby Aug 17, 2026
0e83e8e
Align build.gradle to latest template
oskirby Aug 17, 2026
86798de
Add license, drop unused deps and set to rust 2021 edition
oskirby Aug 17, 2026
6511739
Remove some unused stuff from logins.udl
oskirby Aug 18, 2026
51cb1fa
Add parking_lot dependency after rebase
oskirby Aug 19, 2026
dd6c72e
Add create_key wrappers back after rebase
oskirby Aug 19, 2026
b30c769
Fix keydb tests after rebase
oskirby Aug 19, 2026
2db0ee2
Rename crate from encryption to db-crypto
oskirby Aug 26, 2026
7786b5b
Fix sync-test and lint
oskirby Aug 27, 2026
1b440eb
Add a changelog entry
oskirby Aug 27, 2026
93e6e5d
Fix external UniFFI reference
oskirby Aug 27, 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.

encryption:
path: components/support/encryption/android
artifactId: encryption
publications:
- name: encryption
type: aar
description: Credential encryption support
20 changes: 20 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/encryption",
"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", "encryption/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"] }
encryption = { path = "../support/encryption", 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(':encryption')

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.encryption.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.encryption.KeyManager
import mozilla.appservices.syncmanager.SyncManager
import mozilla.telemetry.glean.testing.GleanTestRule
import org.junit.Assert.assertEquals
Expand Down
7 changes: 3 additions & 4 deletions components/logins/src/db.rs
Original file line number Diff line number Diff line change
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 @@ -1181,8 +1180,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 @@ -1353,8 +1352,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 encryption::EncryptionApiError;
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] EncryptionApiError),

#[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<EncryptionApiError> for LoginsApiError {
fn from(error: EncryptionApiError) -> Self {
match error {
EncryptionApiError::NSSUninitialized => Self::NSSUninitialized,
EncryptionApiError::NSSAuthenticationError { reason: x } => {
Self::NSSAuthenticationError { reason: x }
}
EncryptionApiError::AuthenticationError { reason: x } => {
Self::AuthenticationError { reason: x }
}
EncryptionApiError::AuthenticationCanceled => Self::AuthenticationCanceled,
EncryptionApiError::MissingKey => Self::MissingKey,
EncryptionApiError::InvalidKey => Self::InvalidKey,
EncryptionApiError::EncryptionFailed { reason: x } => {
Self::EncryptionFailed { reason: x }
}
EncryptionApiError::DecryptionFailed { reason: x } => {
Self::DecryptionFailed { reason: x }
}
EncryptionApiError::Interrupted { reason: x } => Self::Interrupted { reason: x },
EncryptionApiError::UnexpectedEncryptionApiError { reason: x } => {
Self::UnexpectedLoginsApiError { reason: x }
}
}
}
}
50 changes: 43 additions & 7 deletions components/logins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,29 @@ mod error;
mod login;

mod db;
pub mod encryption;
pub use encryption;
mod schema;
mod store;
mod sync;
mod util;

use crate::encryption::{
EncryptorDecryptor, KeyManager, ManagedEncryptorDecryptor, StaticKeyManager,
};
use encryption::{EncryptorDecryptor, KeyManager, ManagedEncryptorDecryptor, StaticKeyManager};
uniffi::include_scaffolding!("logins");

#[cfg(feature = "keydb")]
pub use crate::encryption::{NSSKeyManager, PrimaryPasswordAuthenticator};
pub use encryption::{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 +58,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(encryption::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(encryption::check_canary(canary, text, key)?)
}

#[handle_error(Error)]
pub fn create_key() -> ApiResult<String> {
Ok(encryption::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 +83,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()
}
}
3 changes: 2 additions & 1 deletion components/logins/src/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,10 +913,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 @@ -148,28 +148,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 = "encryption"]
typedef trait_with_foreign EncryptorDecryptor;

interface StaticKeyManager {
constructor(string key);
};

interface ManagedEncryptorDecryptor {
constructor(KeyManager key_manager);
};
[External = "encryption"]
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
11 changes: 7 additions & 4 deletions components/logins/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,13 @@ mod tests_keydb {

#[async_trait]
impl PrimaryPasswordAuthenticator for MockPrimaryPasswordAuthenticator {
async fn get_primary_password(&self) -> ApiResult<String> {
async fn get_primary_password(&self) -> encryption::ApiResult<String> {
Ok(self.password.clone())
}
async fn on_authentication_success(&self) -> ApiResult<()> {
async fn on_authentication_success(&self) -> encryption::ApiResult<()> {
Ok(())
}
async fn on_authentication_failure(&self) -> ApiResult<()> {
async fn on_authentication_failure(&self) -> encryption::ApiResult<()> {
Ok(())
}
}
Expand All @@ -629,7 +629,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");
Expand Down
2 changes: 1 addition & 1 deletion components/logins/src/sync/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion components/logins/src/sync/merge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion components/logins/src/sync/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading
Loading