added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / impl / apple / keychain_helper.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2016 Realm Inc.
4 //
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
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
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.
16 //
17 ////////////////////////////////////////////////////////////////////////////
18
19 #include "impl/apple/keychain_helper.hpp"
20
21
22 #include <realm/util/cf_ptr.hpp>
23 #include <realm/util/optional.hpp>
24
25 #include <Security/Security.h>
26
27 #include <string>
28
29 using realm::util::CFPtr;
30 using realm::util::adoptCF;
31 using realm::util::retainCF;
32
33 namespace realm {
34 namespace keychain {
35
36 KeychainAccessException::KeychainAccessException(int32_t error_code)
37 : std::runtime_error(util::format("Keychain returned unexpected status code: %1", error_code)) { }
38
39 namespace {
40
41 constexpr size_t key_size = 64;
42
43 #if !TARGET_IPHONE_SIMULATOR
44 CFPtr<CFStringRef> convert_string(const std::string& string)
45 {
46     auto result = adoptCF(CFStringCreateWithBytes(nullptr, reinterpret_cast<const UInt8*>(string.data()),
47                                                   string.size(), kCFStringEncodingASCII, false));
48     if (!result) {
49         throw std::bad_alloc();
50     }
51     return result;
52 }
53 #endif
54
55 CFPtr<CFMutableDictionaryRef> build_search_dictionary(CFStringRef account, CFStringRef service,
56                                                       __unused util::Optional<std::string> group)
57 {
58     auto d = adoptCF(CFDictionaryCreateMutable(nullptr, 0, &kCFTypeDictionaryKeyCallBacks,
59                                                &kCFTypeDictionaryValueCallBacks));
60     if (!d)
61         throw std::bad_alloc();
62
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
69     if (group)
70         CFDictionaryAddValue(d.get(), kSecAttrAccessGroup, convert_string(*group).get());
71 #endif
72     return d;
73 }
74
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)
77 {
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);
83
84         // Key was not found.
85         return none;
86     }
87
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.");
92
93     auto key_bytes = reinterpret_cast<const char *>(CFDataGetBytePtr(key_data.get()));
94     return std::vector<char>(key_bytes, key_bytes + key_size);
95 }
96
97 void set_key(const std::vector<char>& key, CFStringRef account, CFStringRef service)
98 {
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));
101     if (!key_data)
102         throw std::bad_alloc();
103
104     CFDictionaryAddValue(search_dictionary.get(), kSecValueData, key_data.get());
105     if (OSStatus status = SecItemAdd(search_dictionary.get(), nullptr))
106         throw KeychainAccessException(status);
107 }
108
109 }   // anonymous namespace
110
111 std::vector<char> metadata_realm_encryption_key(bool check_legacy_service)
112 {
113     CFStringRef account = CFSTR("metadata");
114     CFStringRef legacy_service = CFSTR("io.realm.sync.keychain");
115
116     CFPtr<CFStringRef> service;
117     if (CFStringRef bundle_id = CFBundleGetIdentifier(CFBundleGetMainBundle()))
118         service = adoptCF(CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ - Realm Sync Metadata Key"), bundle_id));
119     else {
120         service = retainCF(legacy_service);
121         check_legacy_service = false;
122     }
123
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;
133         }
134     }
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());
139     return key;
140 }
141
142 }   // keychain
143 }   // realm