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

[Desktop] Secure saved credentials. #3733

Closed
Tracked by #3444
bedhub opened this issue Dec 14, 2021 · 3 comments · Fixed by #3833 or #3866
Closed
Tracked by #3444

[Desktop] Secure saved credentials. #3733

bedhub opened this issue Dec 14, 2021 · 3 comments · Fixed by #3833 or #3866
Labels
desktop Desktop client related issues state:done meets our definition of done state:tested We tested it and are about to release it
Milestone

Comments

@bedhub
Copy link
Contributor

bedhub commented Dec 14, 2021

This is a subtask of #3444 for the desktop client to secure the stored credentials with a device key.
There should be only the option "Automatic" which creates a key, encrypts the credentials and stores that key in the secure storage of the device. As discussed in #3444 there should be no other biometric or system password option.

Implementation hints
Usually the keychain should be unlocked after the user logged in to their accounts but we have to consider the case that the keychain is still locked. In that case we need to show a dialog to prompt for unlocking the keychain.

There is currently a problem when using keytar because the cancel path is broken: atom/node-keytar#422

We like to replace keytar with electrons api anyway so we need to do this task first:
#3676

@bedhub bedhub added this to the Next release milestone Dec 14, 2021
@bedhub bedhub changed the title Desktop [Desktop] Secure saved credentials. Dec 14, 2021
@charlag charlag added the desktop Desktop client related issues label Dec 14, 2021
ganthern added a commit that referenced this issue Dec 16, 2021
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
@ganthern
Copy link
Contributor

ganthern commented Dec 21, 2021

the electron part is blocked on this (#3676 (comment)) unless we do something stupid like open an invisible BrowserWindow as soon as app is ready (which could even be too late since we want encryption pretty early).

ganthern added a commit that referenced this issue Dec 21, 2021
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Jan 6, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
@ganthern ganthern added the state:done meets our definition of done label Jan 25, 2022
@ganthern ganthern removed the state:done meets our definition of done label Jan 27, 2022
ganthern added a commit that referenced this issue Jan 27, 2022
don't show credential encryption options if there are
no options

#3733
ganthern added a commit that referenced this issue Feb 1, 2022
don't show credential encryption options if there are
no options

#3733
@ganthern
Copy link
Contributor

ganthern commented Feb 1, 2022

Testing

  • log in with the old client, save the credentials, update tutanota and then check that the credentials still work.
    • Cancellation on Linux:
      • run gnome-keyring-daemon -f -r
      • run client, cancel all the prompts
      • no error should be shown (besides "secure storage not available")
      • try to use saved credentials, another prompt should be shown, cancel, should see no error
      • try to use saved credentials, unlock prompt, should be logged in
      • log out, try to use credentials again, they should unlock you
    • Cancellation on macOS:
      • Run client, unlock all prompts
      • open Keychain Access, find tutanota-credentials entry in login keychain, change its properties to not always unlock for the app and to ask password each time
      • open client again, try to log in and cancel, should see no error
      • log in and unlock prompt
      • log out, log in, should see prompt again and it should let you log in again
  • on linux, delete all credentials, then restart tutanota, then lock the login key chain (using seahorse). Login and make sure the credentials get stored and are available after another restart.
  • on mac, delete all credentials, then restart tutanota, then lock the login key chain (using keychain access). Login and make sure the credentials get stored and are available after another restart.

@charlag charlag added the state:done meets our definition of done label Feb 1, 2022
@charlag charlag modified the milestones: Next release, 3.91.X Feb 3, 2022
charlag pushed a commit that referenced this issue Feb 3, 2022
don't show credential encryption options if there are
no options

#3733
charlag pushed a commit that referenced this issue Feb 3, 2022
don't show credential encryption options if there are
no options

#3733
charlag pushed a commit that referenced this issue Feb 3, 2022
don't show credential encryption options if there are
no options

#3733
@charlag
Copy link
Contributor

charlag commented Feb 4, 2022

I couldn't get it to work yet. Here's what I did:

  • run gnome-keyring-daemon -f -r to effectively "lock" the keychain
  • Start the app
  • See keyring prompt, cancel it
  • See another keyring prompt, cancel it
  • client shows "could not access secret storage" dialog, see from output that client treats it like cancellation (like it should)
  • try to select stored credentials
  • See 2 other prompts and cancel them
  • client output "key credentials-device-lock-key not found, generating a new one" which shouldn't actually happen. Also 2 error dialogs (native and in app)

@charlag charlag reopened this Feb 4, 2022
charlag added a commit that referenced this issue Feb 4, 2022
There were few issues:

1. We would try to resolve any given key only once. If it fails, it
fails until we restart the app. Now we only cache successful
resolutions3

2. We would cache any of the keys forever, including credentials key.
We actually don't want to cache credential keys.

3. Using defer() led to a situation where we would treat error an as a
non-existing key and try to overwrite it. This meant that during
cancellation we would try to rewrite the key (which is bad by itself
and is exactly what we tried to avoid) and would also trigger another
prompt to unlock keychain.

4. Cancellation would not be propagated correctly and would lead to an
error on the client.

Now we only cache device key, we don't cache errors, and we don't
re-wrap errors (so that cancellation is propagated).
charlag added a commit that referenced this issue Feb 4, 2022
There were few issues:

1. We would try to resolve any given key only once. If it fails, it
fails until we restart the app. Now we only cache successful
resolutions3

2. We would cache any of the keys forever, including credentials key.
We actually don't want to cache credential keys.

3. Using defer() led to a situation where we would treat error an as a
non-existing key and try to overwrite it. This meant that during
cancellation we would try to rewrite the key (which is bad by itself
and is exactly what we tried to avoid) and would also trigger another
prompt to unlock keychain.

4. Cancellation would not be propagated correctly and would lead to an
error on the client.

5. macOS would spam user with prompts for password event after other
changes. User has no idea what they give access to.

Now we only cache device key, we don't cache errors, and we don't
re-wrap errors (so that cancellation is propagated). We introduces
second service name for credentials so that user can differentiate
keys better (that's what macOS shows in prompt and in keychain list
instead of account name).
charlag added a commit that referenced this issue Feb 4, 2022
There were few issues:

1. We would try to resolve any given key only once. If it fails, it
fails until we restart the app. Now we only cache successful
resolutions3

2. We would cache any of the keys forever, including credentials key.
We actually don't want to cache credential keys.

3. Using defer() led to a situation where we would treat error an as a
non-existing key and try to overwrite it. This meant that during
cancellation we would try to rewrite the key (which is bad by itself
and is exactly what we tried to avoid) and would also trigger another
prompt to unlock keychain.

4. Cancellation would not be propagated correctly and would lead to an
error on the client.

5. macOS would spam user with prompts for password event after other
changes. User has no idea what they give access to.

Now we only cache device key, we don't cache errors, and we don't
re-wrap errors (so that cancellation is propagated). We introduces
second service name for credentials so that user can differentiate
keys better (that's what macOS shows in prompt and in keychain list
instead of account name).
@charlag charlag added the state:tested We tested it and are about to release it label Feb 4, 2022
ganthern added a commit that referenced this issue Feb 24, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Mar 1, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Mar 3, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Mar 10, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Mar 15, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Mar 22, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Mar 24, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue Apr 26, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
ganthern added a commit that referenced this issue May 4, 2022
* renames PathUtils.swapFilename to replaceLastComponent
* provides a new impl for SecretStorage
* uses electron.safeStorage to encrypt the device key that then gets
stored in a file in app.getPath('userData')/safe_storage/
* from now on, per-user data is encrypted with a per-user key,
even for per-machine installs.

#3733
close #3676
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
desktop Desktop client related issues state:done meets our definition of done state:tested We tested it and are about to release it
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants