8000 [BITAU-141] [BITAU-142] [BITAU-143] [BITAU-150] Add Vault Unlock by brant-livefront · Pull Request #1032 · bitwarden/ios · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[BITAU-141] [BITAU-142] [BITAU-143] [BITAU-150] Add Vault Unlock #1032

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

Merged
merged 30 commits into from
Oct 17, 2024

Conversation

brant-livefront
Copy link
Collaborator

🎟️ Tracking

[BITAU-141] Add ability to read vault key from user's unlocked vault and save to keychain
[BITAU-142] Unlock Vault using stored Authenticator Vault Key
[BITAU-143] Delete Authenticator Vault Key on Turning Sync Off
[BITAU-150] Create Keychain Item for Authenticator Vault Key

📔 Objective

This PR adds the ability to handle background and locked-vault syncs for the user. There's a few different parts:

  • If the vault is locked when the user turns sync on, we don't take any action. This is the same as before.
  • When the user has an unlocked vault and has turned on sync for the active user, just as before we kick off sync. However we now also store a copy of their vault key in the keychain as their authenticatorVaultKey.
  • This allows us to unlock their vault when a Cipher update happens, but they have a locked vault. For instance: Unlock Account 1, switch to Account 2, account 1's vault timeout occurs and then you receive a Push for Account 1 (this is similar to the case Matt mentioned on the previous PR)
  • Lastly, when they turn off sync, we clean everything up
    • Remove their authenticatorVaultKey
    • (From a previous PR) Remove their sync'd data and (if this is the last account) the shared key

Note: There is one known issue with this PR (I'll handle it as part of BITAU-153): Since the keys in the PM app are stored with an accessibility level of kSecAttrAccessibleWhenUnlockedThisDeviceOnly they are not available if the app is truly in the background (i.e. an update comes in via Push notification while the app is backgrounded and the device is locked). Thus we can't unlock the vault if the app is not in the foreground. I will need to add a refactor to move just this one key to kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly which does allow background access.

⏰ Reminders before review

  • Contributor guidelines followed
  • All formatters and local linters executed and passed
  • Written new unit and / or integration tests where applicable
  • Protected functional changes with optionality (feature flags)
  • Used internationalization (i18n) for all UI strings
  • CI builds passed
  • Communicated to DevOps any deployment requirements
  • Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team

🦮 Reviewer guidelines

  • 👍 (:+1:) or similar for great changes
  • 📝 (:memo:) or ℹ️ (:information_source:) for notes or general info
  • ❓ (:question:) for questions
  • 🤔 (:thinking:) or 💭 (:thought_balloon:) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
  • 🎨 (:art:) for suggestions / improvements
  • ❌ (:x:) or ⚠️ (:warning:) for more significant problems or concerns needing attention
  • 🌱 (:seedling:) or ♻️ (:recycle:) for future improvements or indications of technical debt
  • ⛏ (:pick:) for minor or nitpick changes

Copy link
Contributor
github-actions bot commented Oct 10, 2024

Logo
Checkmarx One – Scan Summary & Details4bb403b7-5177-4f96-9598-85542f2bae32

No New Or Fixed Issues Found

Copy link
codecov bot commented Oct 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 89.30%. Comparing base (2b2652c) to head (d3ed92d).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1032      +/-   ##
==========================================
+ Coverage   89.29%   89.30%   +0.01%     
==========================================
  Files         670      670              
  Lines       42056    42098      +42     
==========================================
+ Hits        37553    37595      +42     
  Misses       4503     4503              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Comment on lines 260 to 267
let useKey = vaultTimeoutService.isLocked(userId: userId)
if useKey {
try await authRepository.unlockVaultWithAuthenticatorVaultKey(userId: userId)
}
let items = try await decryptTOTPs(ciphers, userId: userId)
if useKey {
await vaultTimeoutService.lockVault(userId: userId)
}
Copy link
Member

Choose a reason for hiding this comment

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

🤔 Hmmm TBH I wouldn't include an automatic unlock without the user explicitly knowing, I feel like this would at least need to be an opt-in feature so the user consents on having this behavior and we only automatically do this if the vault is currently unlocked. Could you check with Product about this?

Copy link
Collaborator

Choose a reason for hiding this comment

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

When we originally put together a POC around this behavior, we presented options for warning the user about this (similar to the never lock) to Kyle Spearrin and Brian Gentry. Where we landed was that the toggle to turn on the feature was the opt in enough, and doesn't leave the entire PM permanently unlocked like never lock does.

Copy link
Member

Choose a reason for hiding this comment

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

Oh alright, if they agree then I'll approve; thanks for clarifying! 😄

Copy link
Member
@fedemkr fedemkr Oct 15, 2024

Choose a reason for hiding this comment

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

@victor-livefront something else now that I re-read it is if let items = try await decryptTOTPs(ciphers, userId: userId) throws then the vault would remain unlocked. I think a do .... catch should be added here to lock the vault if the func throws and then re-throw the error for the caller.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That's a good callout. Brant and I discussed threading here but overlooked the throw.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@fedemkr @victor-livefront OK, I've pushed a commit that I think addresses all of these issues:

  • I've moved the fetching of the account to this method. If that fails, it will throw immediately and get caught/logged by the CipherPublisherTask's catch.
  • I've changed the keychainService.getAuthenticatorVaultKey to return a non-optional String. It was always throwing KeychainServiceError.keyNotFound in the case of the key missing, so there's no reason for it to be optional
    • if this is thrown in the course of unlocking their vault, it will now be caught by the catch in writeCiphers and immediately logged.
  • Likewise, if decrypting ciphers or writing them to the shared store fails, we're catching that explicitly and logging it right here in the method, so we should have insight into what's failing
  • Lastly, outside of all of that, we check the useKey at the end of the method and make sure to lock the Vault back up even if any errors have occurred.

Comment on lines 771 to 772
let authenticatorKey = try await keychainService.getAuthenticatorVaultKey(userId: userId) ?? ""
try await unlockVault(method: .decryptedKey(decryptedUserKey: authenticatorKey), hadUserInteraction: false)
Copy link
Member

Choose a reason for hiding this comment

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

🤔 If keychainService.getAuthenticatorVaultKey is nil then unlocking will definitely fail so it feels like a guard should be added here and throw an error if that happens so we have a more specific error as to why this failed.

Copy link
Collaborator
@victor-livefront victor-livefront left a comment

Choose a reason for hiding this comment

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

Updated logic makes sense to me!

XCTAssertTrue(vaultTimeoutService.isLocked(userId: "1"))
}

/// Verify that `writeCiphers()` correctly catches and logs errors that occur in `replaceAllItems`. The user's vault is
Copy link
Member

Choose a reason for hiding this comment

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

⛏️ Line length > 120.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

🤦‍♂️ Fixed in latest.

@brant-livefront brant-livefront merged commit 67af2ec into main Oct 17, 2024
9 checks passed
@brant-livefront brant-livefront deleted the brant/add-vault-background-unlock branch October 17, 2024 22:36
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.

4 participants
0