-
Notifications
You must be signed in to change notification settings - Fork 226
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
base: main
Are you sure you want to change the base?
Conversation
03f06ad
to
a3c0e96
Compare
be673a2
to
af91a5e
Compare
There was a problem hiding this 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.
There was a problem hiding this 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.
The pull request has been modified, dismissing previous reviews.
2f13b8a
to
497ca51
Compare
8a434a3
to
4cbc6da
Compare
32992bf
to
c941153
Compare
48bfe9f
to
772e1c9
Compare
772e1c9
to
a290d50
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great to me. A lot of code is changing, but in general it seems like the same system as before only better. I mostly just had suggestions around the error handling.
The pull request has been modified, dismissing previous reviews.
9a5f309
to
ac36ae4
Compare
Thanks for the good points! |
ac36ae4
to
c2e2382
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes look great to me, I'm all for merging this. I think we should get a second review if possible though.
c2e2382
to
4641d2f
Compare
The pull request has been modified, dismissing previous reviews.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fantastic!
@@ -72,15 +68,6 @@ dictionary Login { | |||
SecureLoginFields sec_fields; | |||
}; | |||
|
|||
/// An encrypted version of [Login]. This is what we return for all the "read" | |||
/// APIs - we never return the cleartext of encrypted fields. | |||
dictionary EncryptedLogin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm really happy to see this die. Back in the day, we were torn about whether the logins store should be able to function while in a "locked" state - ie, we were picturing a world where the app knows a login exists for a site, but the app must force the user to unlock via biometrics etc before the actual details were available, which then allows the app to "re-lock" the store at its convenience.
It looks like you've decided this is a non-goal, and your team is the correct team to make that decision, and the extra simplicity that comes with that is a nice bonus!
You might be able to take this further though - it seems that without EncryptedLogin
you might also be able to flatten out the Login
record so avoid the nested records (ie, can RecordFields, and SecureLoginFields now die?) While that might be able to be done as a followup, the cost of re-breaking the mobile platforms might mean the opportunity to do so might not arise. IMO, the current splitting of these records makes the use of this component in the mobile code quite complicated to understand so would likely benefit from a simplification here.
(alternatively, a different split might make sense - eg, split out "editable" fields from "computed" fields, but even that might not have enough value)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think the advantages of a simplified way of working for LoginsStore outweigh the disadvantages. The idea of a locked state further complicates things and has a knock-on effect at lower levels. We can still perform certain operations without the store unlocked – for example, I added (thanks @bendk!) the method has_logins_by_base_domain(&self, base_domain: &str)
so that the application can still check whether logins exist for a specific domain.
I like that you suggest further simplifying the login structure. I hadn't even dared to tackle that :) I'll discuss it with the mobile teams. On Android, the code is already abstracted, so the changes would be very simple here. On iOS, I'm not sure. At the latest now, a decoupling on the iOS side seems sensible to me in order to be able to make adjustments more agilely.
Furthermore, we would actually like to encrypt more fields, especially the domain. This information could already be interesting for certain attackers and some users could therefore support such changes. Although this would complicate or even prevent the lookup by domain in the locked state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah! I'm a little torn by RecordFields
though - roughly speaking, they are fields which the consumer should never provide - eg, when creating a login you never specify the id
or the number of times it has been used. This is a tension we often face - use different structs for "get" vs "set" operations, which makes consumers a little more painful in some ways, but makes things clearer in others.
But certainly the flattening of LoginFields
looks like a big improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know, this is an old problem. And then, of course, there is something appealing about grouping all these meta fields, which only play a role when reading, into a separate record struct. And on the other hand, there is this simplicity and clarity ¯_(ツ)_/¯
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
exactly :) It sounds like we agree there's no objectively correct answer here, so I can get behind whatever you decide!
fn get_key(&self) -> ApiResult<Vec<u8>> { | ||
Ok(self.key.as_bytes().into()) | ||
} | ||
} | ||
|
||
#[handle_error(Error)] | ||
pub fn create_canary(text: &str, key: &str) -> ApiResult<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The canary story can probably be better too - the intent there is to better work with keys which are our of our control. IIUC there's no equivalent thing on desktop. Maybe that concept could be optionally added to the traits? This might be good fodder for a followup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, very good idea!
ae5bc5e
to
78ec15d
Compare
The pull request has been modified, dismissing previous reviews.
5d748a1
to
949c187
Compare
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.
949c187
to
ac69d88
Compare
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 currentEncryptorDecryptor
struct. Instead of using thedecrypt_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
anddecrypt_struct
anymore. AlsoEncryptedLogin
will not be exposed anymore.A
ManagedEncryptorDecryptor
will provide anEncryptorDecryptor
implementation which uses the currently used crypto methods, given aKeyManager
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
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 usedto 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
property must be passed to the constructors. To do this, the signatures
are extended as follows:
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.
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.