This repository has been archived by the owner on Nov 8, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 37
Store sync metadata keychain info on per-app basis #521
Merged
Merged
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4236715
Allow the keychain service to be customized when configuring sync met…
austinzheng 9bafa2a
Make object store get bundle identifier
austinzheng 9828bee
Remove irrelevant message
austinzheng da5d3ed
Address feedback
austinzheng 9a917a7
Address feedback
austinzheng 11a40be
Fix issue with `retainCF` and null pointers
austinzheng 1688498
Remove unnecessary retain/release
austinzheng d9c727a
Address feedback
austinzheng File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,16 +29,17 @@ | |
|
||
using realm::util::CFPtr; | ||
using realm::util::adoptCF; | ||
using realm::util::retainCF; | ||
|
||
namespace realm { | ||
namespace keychain { | ||
|
||
KeychainAccessException::KeychainAccessException(int32_t error_code) | ||
: std::runtime_error(util::format("Keychain returned unexpected status code: %1", error_code)) { } | ||
|
||
CFPtr<CFStringRef> convert_string(const std::string& string); | ||
CFPtr<CFMutableDictionaryRef> build_search_dictionary(const std::string& account, const std::string& service, | ||
util::Optional<std::string> group); | ||
namespace { | ||
|
||
constexpr size_t key_size = 64; | ||
|
||
CFPtr<CFStringRef> convert_string(const std::string& string) | ||
{ | ||
|
@@ -50,66 +51,96 @@ CFPtr<CFStringRef> convert_string(const std::string& string) | |
return result; | ||
} | ||
|
||
CFPtr<CFMutableDictionaryRef> build_search_dictionary(const std::string& account, const std::string& service, | ||
CFPtr<CFMutableDictionaryRef> build_search_dictionary(CFStringRef account, CFStringRef service, | ||
__unused util::Optional<std::string> group) | ||
{ | ||
auto d = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks, | ||
&kCFTypeDictionaryValueCallBacks)); | ||
if (!d) { | ||
if (!d) | ||
throw std::bad_alloc(); | ||
} | ||
|
||
CFDictionaryAddValue(d.get(), kSecClass, kSecClassGenericPassword); | ||
CFDictionaryAddValue(d.get(), kSecReturnData, kCFBooleanTrue); | ||
CFDictionaryAddValue(d.get(), kSecAttrAccessible, kSecAttrAccessibleAlways); | ||
CFDictionaryAddValue(d.get(), kSecAttrAccount, convert_string(account).get()); | ||
CFDictionaryAddValue(d.get(), kSecAttrService, convert_string(service).get()); | ||
CFDictionaryAddValue(d.get(), kSecAttrAccount, account); | ||
CFDictionaryAddValue(d.get(), kSecAttrService, service); | ||
#if TARGET_IPHONE_SIMULATOR | ||
#else | ||
if (group) { | ||
if (group) | ||
CFDictionaryAddValue(d.get(), kSecAttrAccessGroup, convert_string(*group).get()); | ||
} | ||
#endif | ||
return d; | ||
} | ||
|
||
std::vector<char> metadata_realm_encryption_key() | ||
/// Get the encryption key for a given service, returning it only if it exists. | ||
util::Optional<std::vector<char>> get_key(CFStringRef account, CFStringRef service) | ||
{ | ||
const size_t key_size = 64; | ||
const std::string service = "io.realm.sync.keychain"; | ||
const std::string account = "metadata"; | ||
|
||
auto search_dictionary = build_search_dictionary(account, service, none); | ||
CFDataRef retained_key_data; | ||
if (OSStatus status = SecItemCopyMatching(search_dictionary.get(), (CFTypeRef *)&retained_key_data)) { | ||
if (status != errSecItemNotFound) { | ||
if (status != errSecItemNotFound) | ||
throw KeychainAccessException(status); | ||
} | ||
|
||
// Key was not found. Generate a new key, store it, and return it. | ||
std::vector<char> key(key_size); | ||
arc4random_buf(key.data(), key_size); | ||
auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast<const UInt8 *>(key.data()), key_size)); | ||
if (!key_data) { | ||
throw std::bad_alloc(); | ||
} | ||
|
||
CFDictionaryAddValue(search_dictionary.get(), kSecValueData, key_data.get()); | ||
if (OSStatus status = SecItemAdd(search_dictionary.get(), nullptr)) { | ||
throw KeychainAccessException(status); | ||
} | ||
|
||
return key; | ||
// Key was not found. | ||
return none; | ||
} | ||
CFPtr<CFDataRef> key_data = adoptCF(retained_key_data); | ||
|
||
// Key was previously stored. Extract it. | ||
if (key_size != CFDataGetLength(key_data.get())) { | ||
CFPtr<CFDataRef> key_data = adoptCF(retained_key_data); | ||
if (key_size != CFDataGetLength(key_data.get())) | ||
throw std::runtime_error("Password stored in keychain was not expected size."); | ||
} | ||
|
||
auto key_bytes = reinterpret_cast<const char *>(CFDataGetBytePtr(key_data.get())); | ||
return std::vector<char>(key_bytes, key_bytes + key_size); | ||
} | ||
|
||
void set_key(const std::vector<char>& key, CFStringRef account, CFStringRef service) | ||
{ | ||
auto search_dictionary = build_search_dictionary(account, service, none); | ||
auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast<const UInt8 *>(key.data()), key_size)); | ||
if (!key_data) | ||
throw std::bad_alloc(); | ||
|
||
CFDictionaryAddValue(search_dictionary.get(), kSecValueData, key_data.get()); | ||
if (OSStatus status = SecItemAdd(search_dictionary.get(), nullptr)) | ||
throw KeychainAccessException(status); | ||
} | ||
|
||
} // anonymous namespace | ||
|
||
std::vector<char> metadata_realm_encryption_key(bool check_legacy_service) | ||
{ | ||
CFStringRef account = CFSTR("metadata"); | ||
CFStringRef default_service = CFSTR("io.realm.sync.keychain"); | ||
|
||
CFPtr<CFStringRef> managed_service; | ||
if (auto bundle_id = retainCF(CFBundleGetIdentifier(CFBundleGetMainBundle()))) { | ||
managed_service = adoptCF(CFStringCreateWithFormat(NULL, NULL, | ||
CFSTR("%@ - Realm Sync Metadata Key"), | ||
bundle_id.get())); | ||
} else { | ||
managed_service = retainCF(default_service); | ||
check_legacy_service = false; | ||
} | ||
|
||
// Try retrieving the key. | ||
CFStringRef service = managed_service.get(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd be inclined to ditch this local and just add the |
||
if (auto existing_key = get_key(account, service)) { | ||
return *existing_key; | ||
} else if (check_legacy_service) { | ||
// See if there's a key in the old shared keychain. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "See if there's a key stored using the legacy shared keychain item"? |
||
if (auto existing_legacy_key = get_key(account, default_service)) { | ||
// If so, copy it to the per-app keychain before returning it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The keychain isn't per application, the item name is. |
||
set_key(*existing_legacy_key, account, service); | ||
return *existing_legacy_key; | ||
} | ||
} | ||
// Make a completely new key. | ||
std::vector<char> key(key_size); | ||
arc4random_buf(key.data(), key_size); | ||
set_key(key, account, service); | ||
return key; | ||
} | ||
|
||
} // keychain | ||
} // realm |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 think
legacy_service
would better reflect how the relationship between this and thecheck_legacy_service
argument.