1 ////////////////////////////////////////////////////////////////////////////
3 // Copyright 2016 Realm Inc.
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
17 ////////////////////////////////////////////////////////////////////////////
19 #include "impl/apple/keychain_helper.hpp"
22 #include <realm/util/cf_ptr.hpp>
23 #include <realm/util/optional.hpp>
25 #include <Security/Security.h>
29 using realm::util::CFPtr;
30 using realm::util::adoptCF;
31 using realm::util::retainCF;
36 KeychainAccessException::KeychainAccessException(int32_t error_code)
37 : std::runtime_error(util::format("Keychain returned unexpected status code: %1", error_code)) { }
41 constexpr size_t key_size = 64;
43 #if !TARGET_IPHONE_SIMULATOR
44 CFPtr<CFStringRef> convert_string(const std::string& string)
46 auto result = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(string.data()),
47 string.size(), kCFStringEncodingASCII, false));
49 throw std::bad_alloc();
55 CFPtr<CFMutableDictionaryRef> build_search_dictionary(CFStringRef account, CFStringRef service,
56 __unused util::Optional<std::string> group)
58 auto d = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
59 &kCFTypeDictionaryValueCallBacks));
61 throw std::bad_alloc();
63 CFDictionaryAddValue(d.get(), kSecClass, kSecClassGenericPassword);
64 CFDictionaryAddValue(d.get(), kSecReturnData, kCFBooleanTrue);
65 CFDictionaryAddValue(d.get(), kSecAttrAccessible, kSecAttrAccessibleAlways);
66 CFDictionaryAddValue(d.get(), kSecAttrAccount, account);
67 CFDictionaryAddValue(d.get(), kSecAttrService, service);
68 #if !TARGET_IPHONE_SIMULATOR
70 CFDictionaryAddValue(d.get(), kSecAttrAccessGroup, convert_string(*group).get());
75 /// Get the encryption key for a given service, returning it only if it exists.
76 util::Optional<std::vector<char>> get_key(CFStringRef account, CFStringRef service)
78 auto search_dictionary = build_search_dictionary(account, service, none);
79 CFDataRef retained_key_data;
80 if (OSStatus status = SecItemCopyMatching(search_dictionary.get(), (CFTypeRef *)&retained_key_data)) {
81 if (status != errSecItemNotFound)
82 throw KeychainAccessException(status);
88 // Key was previously stored. Extract it.
89 CFPtr<CFDataRef> key_data = adoptCF(retained_key_data);
90 if (key_size != CFDataGetLength(key_data.get()))
91 throw std::runtime_error("Password stored in keychain was not expected size.");
93 auto key_bytes = reinterpret_cast<const char *>(CFDataGetBytePtr(key_data.get()));
94 return std::vector<char>(key_bytes, key_bytes + key_size);
97 void set_key(const std::vector<char>& key, CFStringRef account, CFStringRef service)
99 auto search_dictionary = build_search_dictionary(account, service, none);
100 auto key_data = adoptCF(CFDataCreate(nullptr, reinterpret_cast<const UInt8 *>(key.data()), key_size));
102 throw std::bad_alloc();
104 CFDictionaryAddValue(search_dictionary.get(), kSecValueData, key_data.get());
105 if (OSStatus status = SecItemAdd(search_dictionary.get(), nullptr))
106 throw KeychainAccessException(status);
109 } // anonymous namespace
111 std::vector<char> metadata_realm_encryption_key(bool check_legacy_service)
113 CFStringRef account = CFSTR("metadata");
114 CFStringRef legacy_service = CFSTR("io.realm.sync.keychain");
116 CFPtr<CFStringRef> service;
117 if (CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle()))
118 service = adoptCF(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ - Realm Sync Metadata Key"), bundle_id));
120 service = retainCF(legacy_service);
121 check_legacy_service = false;
124 // Try retrieving the key.
125 if (auto existing_key = get_key(account, service.get())) {
126 return *existing_key;
127 } else if (check_legacy_service) {
128 // See if there's a key stored using the legacy shared keychain item.
129 if (auto existing_legacy_key = get_key(account, legacy_service)) {
130 // If so, copy it to the per-app keychain item before returning it.
131 set_key(*existing_legacy_key, account, service.get());
132 return *existing_legacy_key;
135 // Make a completely new key.
136 std::vector<char> key(key_size);
137 arc4random_buf(key.data(), key_size);
138 set_key(key, account, service.get());