Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EncryptorDecryptor Trait for Logins Component #6469

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

jo
Copy link
Contributor

@jo jo commented Nov 12, 2024

The encryption and decryption of credentials is outsourced to a Foreign Trait so that Android, desktop and iOS can each bring their own implementations of encryption, including key management. This makes the Logins API way simpler and cleaner. It is the first step in making AS-Logins desktop-ready.

The new EncryptorDecryptor trait replaces the current EncryptorDecryptor struct. Instead of using the decrypt_struct method, which involves serializing and deserializing the string to be encrypted, this trait uses only byte-based operations and serialization is up to the consumer.

We do not Uniffi-expose the crypto primitives encrypt, decrypt, encrypt_struct and decrypt_struct anymore. Also EncryptedLogin will not be exposed anymore.

A ManagedEncryptorDecryptor will provide an EncryptorDecryptor implementation which uses the currently used crypto methods, given a KeyManager implementation to ease adaption for mobile.

BREAKING CHANGE

This commit introduces breaking changes to the Logins component:

During initialization, it receives an additional argument, a EncryptorDecryptor implementation. In addition, several LoginsStore API methods have been changed to not require an encryption key argument anymore, and return Login objects instead of EncryptedLogin.

Additionally, a new API method has been added to the LoginsStore, has_logins_by_base_domain(&self, base_domain: &str), which can be used to check for the existence of a login for a given base domain.

EncryptorDecryptor

With the introduction of the EncryptorDecryptor trait, encryption becomes transparent. That means, the LoginStore API receives some breaking changes as outlined above. A ManagedEncryptorDecryptor will provide an EncryptorDecryptor implementation which uses the currently used crypto methods, given a KeyManager implementation. This eases adaption for mobile. Furthermore, we provide a StaticKeyManager implementation, which can be used in tests and in cases where the key is - you name it - static. Constructors Now an implementation of the above property must be passed to the constructors. To do this, the signatures are extended as follows:

pub fn new(path: impl AsRef<Path>, encdec: Arc<dyn EncryptorDecryptor>) -> ApiResult<Self>
pub fn new_from_db(db: LoginDb, encdec: Arc<dyn EncryptorDecryptor>) -> Self
pub fn new_in_memory(encdec: Arc<dyn EncryptorDecryptor>) -> ApiResult<Self>

LoginStore API Methods

This allows the LoginStore API to be simplified as follows, making encryption transparent by eliminating the need to pass the key and allowing the methods to return decrypted login objects.

pub fn list(&self) -> ApiResult<Vec<Login>>
pub fn get(&self, id: &str) -> ApiResult<Option<Login>>
pub fn get_by_base_domain(&self, base_domain: &str) -> ApiResult<Vec<Login>>
pub fn find_login_to_update(&self, entry: LoginEntry) -> ApiResult<Option<Login>>
pub fn update(&self, id: &str, entry: LoginEntry) -> ApiResult<Login>
pub fn add(&self, entry: LoginEntry) -> ApiResult<Login>
pub fn add_or_update(&self, entry: LoginEntry) -> ApiResult<Login>

New LoginsStore methods:

// Checking whether the database contains logins (does not utilize the `EncryptorDecryptor`):
is_empty(&self) -> ApiResult<bool>
// Checking for the Existence of Logins for a given base domain (also does not utilize the `EncryptorDecryptor`):
has_logins_by_base_domain(&self, base_domain: &str) -> ApiResult<bool>

We will stop Uniffi-exposing the crypto primitives encrypt, decrypt, encrypt_struct and decrypt_struct. Also EncryptedLogin will not be exposed anymore.

SyncEngine

The logins sync engine has been adapted for above EncryptorDecryptor trait and therefore does not support set_local_encryption_key anymore.

Flattened Login Struct

The current Login structure is nested:

Login {
  RecordFields record;
  LoginFields fields;
  SecureLoginFields sec_fields;
}

and thus exposes internal data structuring to the consumer. Since we make the encryption transparent for the consumer (Android, iOS, desktop) here, such a separation no longer makes sense here and above can be simplified to

Login {
    // record fields
    string id;
    i64 times_used;
    i64 time_created;
    i64 time_last_used;
    i64 time_password_changed;

    // login fields
    string origin;
    string? http_realm;
    string? form_action_origin;
    string username_field;
    string password_field;

    // secure login fields
    string password;
    string username;
}

The advantage of eliminating this separation lies, on the one hand, in the simplification of the API and the resulting easier use of the component, and on the other hand, in the easier changeability of the internal data structure. If, for example, we decide later to encrypt additional fields, such a change is possible without having to adapt the consumers.

So in addition to the changes mentioned above we will stop Uniffi-exposing the structs RecordFields, LoginFields and SecureLoginFields.

Pull Request checklist

  • Breaking changes: This PR follows our breaking change policy
    • This PR follows the breaking change policy:
      • This PR has no breaking API changes, or
      • There are corresponding PRs for our consumer applications that resolve the breaking changes and have been approved
  • Quality: This PR builds and tests run cleanly
    • Note:
      • For changes that need extra cross-platform testing, consider adding [ci full] to the PR title.
      • If this pull request includes a breaking change, consider cutting a new release after merging.
  • Tests: This PR includes thorough tests or an explanation of why it does not
  • Changelog: This PR includes a changelog entry in CHANGELOG.md or an explanation of why it does not need one
    • Any breaking changes to Swift or Kotlin binding APIs are noted explicitly
  • Dependencies: This PR follows our dependency management guidelines
    • Any new dependencies are accompanied by a summary of the due diligence applied in selecting them.

Branch builds: add [firefox-android: branch-name] to the PR title.

@jo jo changed the title Encryptor decryptor trait WIP: Encryptor decryptor trait Nov 12, 2024
@jo jo force-pushed the encryptor-decryptor-trait branch 11 times, most recently from 03f06ad to a3c0e96 Compare November 19, 2024 10:48
@jo jo force-pushed the encryptor-decryptor-trait branch from be673a2 to af91a5e Compare November 25, 2024 11:40
@jo jo changed the title WIP: Encryptor decryptor trait WIP: EncryptorDecryptor Trait Nov 25, 2024
@linabutler linabutler self-requested a review November 25, 2024 16:37
Copy link
Member

@mhammond mhammond left a comment

Choose a reason for hiding this comment

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

This is fantastic and a massive improvement, thanks! Dealing with Android and iOS will be a challenge though.

components/logins/src/db.rs Outdated Show resolved Hide resolved
@jo jo mentioned this pull request Nov 26, 2024
5 tasks
bendk
bendk previously approved these changes Nov 26, 2024
Copy link
Contributor

@bendk bendk left a comment

Choose a reason for hiding this comment

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

Looks really great to me, I like the overall approach. I commented on some of the details -- treat them as suggestions only. I don't have strong feelings either way.

Hopefully the consumer app updates aren't too bad.

components/logins/src/logins.udl Show resolved Hide resolved
components/logins/src/encryption.rs Show resolved Hide resolved
components/logins/src/logins.udl Show resolved Hide resolved
@mergify mergify bot dismissed bendk’s stale review December 2, 2024 09:18

The pull request has been modified, dismissing previous reviews.

@jo jo force-pushed the encryptor-decryptor-trait branch 4 times, most recently from 2f13b8a to 497ca51 Compare December 3, 2024 10:15
@jo jo changed the title WIP: EncryptorDecryptor Trait EncryptorDecryptor Trait Dec 3, 2024
@jo jo changed the title EncryptorDecryptor Trait EncryptorDecryptor Trait for Logins Component Dec 3, 2024
@jo jo force-pushed the encryptor-decryptor-trait branch 4 times, most recently from 8a434a3 to 4cbc6da Compare December 4, 2024 15:45
@jo jo force-pushed the encryptor-decryptor-trait branch 2 times, most recently from 949c187 to ac69d88 Compare December 17, 2024 12:49
@jo jo force-pushed the encryptor-decryptor-trait branch 9 times, most recently from 789c735 to b615389 Compare December 18, 2024 10:24
@jo jo requested a review from mhammond December 18, 2024 12:31
mhammond
mhammond previously approved these changes Dec 18, 2024
Copy link
Member

@mhammond mhammond left a comment

Choose a reason for hiding this comment

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

This is great!

@jo jo force-pushed the encryptor-decryptor-trait branch from b615389 to 033d8de Compare January 2, 2025 12:07
@mergify mergify bot dismissed mhammond’s stale review January 2, 2025 12:08

The pull request has been modified, dismissing previous reviews.

@jo jo requested review from bendk and mhammond January 2, 2025 12:08
@jo
Copy link
Contributor Author

jo commented Jan 2, 2025

Have rebased this so I need approval again, @mhammond @bendk - is there a better way?

Copy link
Member

@mhammond mhammond left a comment

Choose a reason for hiding this comment

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

I added you as an admin I think, so hopefully you will not need re-approval for all new pushes! Note however you have an iOS test failure

@jo
Copy link
Contributor Author

jo commented Jan 2, 2025

I added you as an admin I think, so hopefully you will not need re-approval for all new pushes! Note however you have an iOS test failure

that iOS test failure is a flaky one. This time, it passed. It seems as it has nothing to do with the changes of in PR.

@jo jo force-pushed the encryptor-decryptor-trait branch 2 times, most recently from 58877ec to b84b18a Compare January 7, 2025 10:45
jo added 3 commits January 9, 2025 16:42
This prepares the Logins component for the desktop and simplifies its
API.

BREAKING CHANGE:
This commit introduces breaking changes to the Logins component:

During initialization, it receives an additional argument, a
EncryptorDecryptorTrait implementation. In addition, several LoginsStore
API methods have been changed to not require an encryption key argument
anymore, and return Logins objects instead of EncryptedLogins.

Additionally, a new API method has been added to the LoginsStore,
`has_logins_by_base_domain(&self, base_domain: &str)`, which can be used
to check for the existence of a login for a given base domain.

**EncryptorDecryptor**

With the introduction of the EncryptorDecryptor trait, encryption
becomes transparent. That means, the LoginStore API receives some
breaking changes as outlined above.  A ManagedEncryptorDecryptor will
provide an EncryptorDecryptor implementation which uses the currently
used crypto methods, given a KeyManager implementation. This eases
adaption for mobile.  Furthermore, we provide a StaticKeyManager
implementation, which can be used in tests and in cases where the key is
- you name it - static.  Constructors Now an implementation of the above
property must be passed to the constructors. To do this, the signatures
are extended as follows:

```
pub fn new(path: impl AsRef<Path>, encdec: Arc<dyn EncryptorDecryptor>) -> ApiResult<Self>
pub fn new_from_db(db: LoginDb, encdec: Arc<dyn EncryptorDecryptor>) -> Self
pub fn new_in_memory(encdec: Arc<dyn EncryptorDecryptor>) -> ApiResult<Self>
```

**LoginStore API Methods**
This allows the LoginStore API to be simplified as follows, making
encryption transparent by eliminating the need to pass the key and
allowing the methods to return decrypted login objects.

```
pub fn list(&self) -> ApiResult<Vec<Login>>
pub fn get(&self, id: &str) -> ApiResult<Option<Login>>
pub fn get_by_base_domain(&self, base_domain: &str) -> ApiResult<Vec<Login>>
pub fn find_login_to_update(&self, entry: LoginEntry) -> ApiResult<Option<Login>>
pub fn update(&self, id: &str, entry: LoginEntry) -> ApiResult<Login>
pub fn add(&self, entry: LoginEntry) -> ApiResult<Login>
pub fn add_or_update(&self, entry: LoginEntry) -> ApiResult<Login>
```

We will stop Uniffi-exposing the crypto primitives encrypt, decrypt,
encrypt_struct and decrypt_struct. Also EncryptedLogin will not be
exposed anymore.  Checking for the Existence of Logins for a given Base
Domain In order to check for the existence of stored logins for a given
base domain, we provide an additional store method,
has_logins_by_base_domain(&self, base_domain: &str), which does not
utilize the EncryptorDecryptor.

Another by-change is in the `check_canary` function: here we do not
throw anymore if a wrong key is used but return false.
The current Login structure is nested:

```
Login {
  RecordFields record;
  LoginFields fields;
  SecureLoginFields sec_fields;
}
```

and thus exposes internal data structuring to the consumer. Since we make the encryption transparent for the consumer (Android, iOS, desktop) here, such a separation no longer makes sense here and above can be simplified to

```
Login {
    // record fields
    string id;
    i64 times_used;
    i64 time_created;
    i64 time_last_used;
    i64 time_password_changed;

    // login fields
    string origin;
    string? http_realm;
    string? form_action_origin;
    string username_field;
    string password_field;

    // secure login fields
    string password;
    string username;
}
```

The advantage of eliminating this separation lies, on the one hand, in the simplification of the API and the resulting easier use of the component, and on the other hand, in the easier changeability of the internal data structure. If, for example, we decide later to encrypt additional fields, such a change is possible without having to adapt the consumers.
@jo jo force-pushed the encryptor-decryptor-trait branch from 78bc7eb to 4c3f04e Compare January 9, 2025 15:42
to check whether the database is empty
@jo jo force-pushed the encryptor-decryptor-trait branch from 4c3f04e to ec4de55 Compare January 9, 2025 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants