diff --git a/src/impl/apple/keychain_helper.cpp b/src/impl/apple/keychain_helper.cpp index bdaafee63..f2cbe1ffd 100644 --- a/src/impl/apple/keychain_helper.cpp +++ b/src/impl/apple/keychain_helper.cpp @@ -29,6 +29,7 @@ using realm::util::CFPtr; using realm::util::adoptCF; +using realm::util::retainCF; namespace realm { namespace keychain { @@ -36,9 +37,9 @@ namespace keychain { KeychainAccessException::KeychainAccessException(int32_t error_code) : std::runtime_error(util::format("Keychain returned unexpected status code: %1", error_code)) { } -CFPtr convert_string(const std::string& string); -CFPtr build_search_dictionary(const std::string& account, const std::string& service, - util::Optional group); +namespace { + +constexpr size_t key_size = 64; CFPtr convert_string(const std::string& string) { @@ -50,66 +51,94 @@ CFPtr convert_string(const std::string& string) return result; } -CFPtr build_search_dictionary(const std::string& account, const std::string& service, +CFPtr build_search_dictionary(CFStringRef account, CFStringRef service, __unused util::Optional 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 metadata_realm_encryption_key() +/// Get the encryption key for a given service, returning it only if it exists. +util::Optional> 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 key(key_size); - arc4random_buf(key.data(), key_size); - auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast(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 key_data = adoptCF(retained_key_data); // Key was previously stored. Extract it. - if (key_size != CFDataGetLength(key_data.get())) { + CFPtr 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(CFDataGetBytePtr(key_data.get())); return std::vector(key_bytes, key_bytes + key_size); } +void set_key(const std::vector& key, CFStringRef account, CFStringRef service) +{ + auto search_dictionary = build_search_dictionary(account, service, none); + auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast(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 metadata_realm_encryption_key(bool check_legacy_service) +{ + CFStringRef account = CFSTR("metadata"); + CFStringRef legacy_service = CFSTR("io.realm.sync.keychain"); + + CFPtr service; + CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle()); + if (CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle())) + service = adoptCF(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ - Realm Sync Metadata Key"), bundle_id)); + else { + service = retainCF(legacy_service); + check_legacy_service = false; + } + + // Try retrieving the key. + if (auto existing_key = get_key(account, service.get())) { + return *existing_key; + } else if (check_legacy_service) { + // See if there's a key stored using the legacy shared keychain item. + if (auto existing_legacy_key = get_key(account, legacy_service)) { + // If so, copy it to the per-app keychain item before returning it. + set_key(*existing_legacy_key, account, service.get()); + return *existing_legacy_key; + } + } + // Make a completely new key. + std::vector key(key_size); + arc4random_buf(key.data(), key_size); + set_key(key, account, service.get()); + return key; } + +} // keychain +} // realm diff --git a/src/impl/apple/keychain_helper.hpp b/src/impl/apple/keychain_helper.hpp index fd3c27ae2..8915e78ee 100644 --- a/src/impl/apple/keychain_helper.hpp +++ b/src/impl/apple/keychain_helper.hpp @@ -26,7 +26,7 @@ namespace realm { namespace keychain { -std::vector metadata_realm_encryption_key(); +std::vector metadata_realm_encryption_key(bool check_legacy_service); class KeychainAccessException : public std::runtime_error { public: diff --git a/src/sync/impl/sync_metadata.cpp b/src/sync/impl/sync_metadata.cpp index cc11d5ef5..4c2c9beb3 100644 --- a/src/sync/impl/sync_metadata.cpp +++ b/src/sync/impl/sync_metadata.cpp @@ -87,7 +87,7 @@ SyncMetadataManager::SyncMetadataManager(std::string path, config.schema_mode = SchemaMode::Automatic; #if REALM_PLATFORM_APPLE if (should_encrypt && !encryption_key) { - encryption_key = keychain::metadata_realm_encryption_key(); + encryption_key = keychain::metadata_realm_encryption_key(File::exists(path)); } #endif if (should_encrypt) {