Page MenuHomePhabricator

Implement encryption key migration to new accessibility options
ClosedPublic

Authored by marcin on Aug 10 2022, 8:11 AM.
Tags
None
Referenced Files
Unknown Object (File)
Sun, Jun 30, 2:28 PM
Unknown Object (File)
Sun, Jun 30, 11:25 AM
Unknown Object (File)
Sun, Jun 30, 11:03 AM
Unknown Object (File)
Sun, Jun 30, 7:10 AM
Unknown Object (File)
Sat, Jun 29, 11:00 PM
Unknown Object (File)
Sat, Jun 29, 8:36 PM
Unknown Object (File)
Sat, Jun 29, 6:53 PM
Unknown Object (File)
Wed, Jun 26, 3:57 PM

Details

Summary

This differential introduces migration for SQLite encryption key we store in secure store in such a way that it is accessible even if the device is locked. expo-secure-store does not provide possibility to update accessibility settings associated with particular key soe it needs to be deleted ans set again with new default options of CommSecureStoreIOSWrapper where new accessbility setting in defined.

Test Plan

Build the app without he changes, set debugger breakpoint in place where you try to access encryption key. Lock the device before you pass throught that breakpoint and wait about a minute. then try to pass through breakpoint. You should not be able to. The add thos changes, build the app a repeate the same steps, but after you launch the app for the first time. Now you should be able to access encryption key.

Diff Detail

Repository
rCOMM Comm
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

The scope of this differential is quite broad and should probably be split into separate diffs in normal circumstances. But since we plan to use it as a release build with potential crash fix we should probably have a single commit with those changes.

atul added subscribers: anunay, karol.

I read through this as best as I could (not very experienced with Objective-C), and it seems good at a high level.

Are the changes made here easily undoable? Like if we decide to remove this for build 142, will we need to clean up any stored secrets or whatever?

I'll go ahead and make the staff release, but I think it would be really important to have this looked at closely by @ashoat, @tomek, @karol, @anunay(?) etc before landing on master. I'm generally paranoid about any security/encryption sort of stuff and would definitely want to have others take a thorough look (not just the code but like ensuring the design is correct, implications with guarantees we provide, etc).

This revision is now accepted and ready to land.Aug 10 2022, 10:53 AM
native/ios/Comm/AppDelegate.mm
75–77 ↗(On Diff #15515)

Man the combination of Objective-C and clang-format here isn't very pretty

Minor high level feedback

set debugger breakpoint in place where you try to access encryption key

As a reviewer I can definitely look through and figure out where to do this, but a function name or line number to help guide me would make things a bit more convenient


The add thos changes, build the app a repeate the same steps

Totally get how easy it is to introduce typos when writing a commit message in vim or whatever editor without spellcheck... but it would be nice to take another pass once it's on Phabricator to clean things up a bit so it's easier for reviewers to parse.


Finally, really appreciate how thoroughly you documented your investigation, potential solutions, designs, etc in Linear. It was really helpful in understanding what was going on. Definitely don't need to copy all of that into the Summary of this diff, but would've been great to link!


Realize that this is an "urgent" issue and appreciate that you were probably trying to get this up ASAP, but think those few things would really help reviewers in the future.

I think we can deploy a staff-only release with this now, but have some questions before we land

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h
23 ↗(On Diff #15515)

Is it necessary to make this public?

native/ios/Comm/CommSecureStoreIOSWrapper.mm
76 ↗(On Diff #15515)

An equality check seems like it won't work if version is incremented, right?

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h
23 ↗(On Diff #15515)

Technically it isn't. secureStoreEncryptionKeyID is a variable under which we store ID for encryption key in secure store. I needed this ID in AppDelegate in a function where I try to change accessibility options for this key. I could just copy-paste the string but in my opinion this is not the best solution since it increases coupling in the codebase.I agree that making class attribute public is not the best solution either. We have Linear task to tackle this issue: https://linear.app/comm/issue/ENG-585/create-common-place-for-constants. Once this Linear task is done we will probably want to make it private again. Finally answering your question again, this attribute can be private at the cost of copy-pasting the string from SQLiteQueryExecutor. We need to make sure that we eventually create some common place for important string constants, since if we continue to copy-paste them wherever we need we might get into trouble.

native/ios/Comm/AppDelegate.mm
75–77 ↗(On Diff #15515)

I cannot commit changes without running clang-format. Should I spent some time searching for better alternatives for Objective-C code formatting? At Software Mansion we have a team of expo developers who code in Objective-C for iOS. I can ask them about their formatting strategies. I suggest this since most of the time I put a diff in Objective-C code formatting is being complained about. Perhaps it is high time we did something about it.

native/ios/Comm/CommSecureStoreIOSWrapper.mm
76 ↗(On Diff #15515)

The purpose of this differential is to delete encryption key from secure store and put it there again, but with new accessibility options (options attribute of shared). Technically we could perform this operations everytime application is foregrounded, but I didn't like this idea so I decided we should mark it somehow that this operation was executed. I was thinking of NSUserDefaults but @tomek suggested not to do so since it will introduce an unexplored dimension along which application could crash and it is better to use secure store for this purpose as we I decided to follow the same pattern we use for SQLite migrations. This way if we ever to decide to modify encryption key settings again we will need to modify options attribute and bump the version argument.

In D4795#138422, @atul wrote:

Minor high level feedback

set debugger breakpoint in place where you try to access encryption key

As a reviewer I can definitely look through and figure out where to do this, but a function name or line number to help guide me would make things a bit more convenient

Lines 71 and 72 of AppDelegate before those changes (master branch) and 78 - 79 of AppDelegate after this differential is applied.


The add thos changes, build the app a repeate the same steps

Totally get how easy it is to introduce typos when writing a commit message in vim or whatever editor without spellcheck... but it would be nice to take another pass once it's on Phabricator to clean things up a bit so it's easier for reviewers to parse.

I will remember next time I put something on Phabricator.


Finally, really appreciate how thoroughly you documented your investigation, potential solutions, designs, etc in Linear. It was really helpful in understanding what was going on. Definitely don't need to copy all of that into the Summary of this diff, but would've been great to link!

In other words - you suggest to simply link Linear issue with relevant comments rather than repeat everything on Phabricator, right?

native/cpp/CommonCpp/DatabaseManagers/SQLiteQueryExecutor.h
23 ↗(On Diff #15515)

Seems like you could just make it private or package-only (what it was before) and also implement a getter...

native/ios/Comm/CommSecureStoreIOSWrapper.mm
76 ↗(On Diff #15515)

I thought about this more...

Usually in migrations code you want to check if the current version is less than the migration version when deciding whether to run the migration. That way, if the current version is lower than the migration version, it will still run.

Three changes I would suggest:

  1. In this case, since it is the first migration, it's actually not possible for version to be less than 0. But I still think it is the right paradigm to check if the value is less than or equal to 0.
  2. We should use an integer instead of a string for the migration version.
  3. The migration version shouldn't be passed into this function... there should be a data structure pointing from 0 to the migration function, where we can add a new migration later if necessary.

It might be a good idea to take a look at existing examples of migrations in our codebase: 1, 2, 3

ashoat requested changes to this revision.Aug 11 2022, 5:59 AM

Requesting changes since I'd like to make some changes here before we land. The code right now works for the test (and has been deployed in version 141), just want to make sure we address the comments before landing

This revision now requires changes to proceed.Aug 11 2022, 5:59 AM

After considering it for a while I realised that we might not need migrations here at all. Those migrations were implemented since I thought encryption key is stored with WHEN_UNLOCK option and I need to remove and insert it again with new option in all existing clients. But due to bug in expo encryption key is actually stored with AFTER_FIRST_UNLOCK option in all our clients, so those migrations are not necessary for existing logged-in clients. The problem is that once expo fixes the bug we need to be sure that new clients and those who log-in again (new encryption key is created) insert it to secure store with AFTER_FIRST_UNLOCK option. Therefore we do not need migration - we just need to explicitly insert it with this option.

Remove unnecessary migration code. Explicitly set AFTER_FIRST_UNLOCK accessibility option unless specified otherwise.

ashoat edited reviewers, added: bartek; removed: atul, ashoat.
ashoat added a subscriber: bartek.

I think @bartek is the right person to take a look, since he has some context on expo-secure-store.

Just a reminder: it's critical we think really carefully about making sure that this change works for old clients (with old Expo and no hack), the hacked clients (all of the recent releases which have been patched, but still use old Expo), as well as the future clients which will use the new Expo with the fix.

Looks good to me.

Just a reminder: it's critical we think really carefully about making sure that this change works for old clients (with old Expo and no hack), the hacked clients (all of the recent releases which have been patched, but still use old Expo), as well as the future clients which will use the new Expo with the fix.

This change should not affect old Expo versions as this was the actual default anyway. And the Expo 47 didn't fix the bug either (git blame)

This revision is now accepted and ready to land.Dec 19 2022, 7:14 AM
native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

Why do we have to provide this option when reading?

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

Under the hood ExpoSecureStore uses SecItemCopyMatching (https://developer.apple.com/documentation/security/1398306-secitemcopymatching?language=objc) which returns items whose attributes sets are supersets of attributes we pass to SecItemCopyMatching. Therefore I thought it will be a good practice (even though not necessary) to pass options we used to put encryption key to a search call. But after diving deeper into _getValueWithKey implementation on the expo side it looks like they do not care about accessibility options (even though they care about them in _setValueWithKey).
In other words - I put options here since apple docs of SecItemCopyMatching encourage to use the same set of attributes for search that was used during insertion. But since expo implementation does not care about options when calling SecItemCopyMatching this is neither necessary nor harmful. Passing options here is a no-op.

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

If this is no-op, it's better to avoid that. The code that does nothing still has to be maintained.

Regarding the internal implementation, what would happen if we pass incorrect option? Are there any downsides of not specifying the option at all?

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

If I understand implementation and apple docs correctly - passing incorrect option might result in an item not being found. Not passing any options simply broadens the scope of results returned. But since we do not store many values under key "encryptionKey" not passing any options does not affect the search.

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

Ok, so an approach where we take this value whatever the accessibility policy is set, is what we need.

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

Do we have a follow-up Linear task here?

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

@tomek please tell me if I am right:

Ok, so an approach where we take this value whatever the accessibility policy is set, is what we need.

Do you mean that we want to change the code so that we don't pass options when retrieving a key?

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80

Yes, exactly

native/ios/Comm/CommSecureStoreIOSWrapper.mm
79–80