]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSStore.m
Security-59754.41.1.tar.gz
[apple/security.git] / KVSKeychainSyncingProxy / CKDKVSStore.m
1 //
2 // CKDKVSStore.m
3 // Security
4 //
5 // Created by Mitch Adler on 5/15/16.
6 //
7 //
8
9 #import "CKDKVSStore.h"
10 #import "CKDKVSProxy.h"
11
12 #include "SOSCloudKeychainConstants.h"
13 #include <utilities/debugging.h>
14
15 #import <Foundation/NSUbiquitousKeyValueStore.h>
16 #import <Foundation/NSUbiquitousKeyValueStore_Private.h>
17 #import "SyncedDefaults/SYDConstants.h"
18 #include <os/activity.h>
19
20 #import "Analytics/Clients/SOSAnalytics.h"
21
22 #include "keychain/SecureObjectSync/SOSKVSKeys.h"
23 #include <Security/OTConstants.h>
24
25 struct CKDKVSCounters {
26 uint64_t synchronize;
27 uint64_t synchronizeWithCompletionHandler;
28 uint64_t incomingMessages;
29 uint64_t outgoingMessages;
30 uint64_t totalWaittimeSynchronize;
31 uint64_t longestWaittimeSynchronize;
32 uint64_t synchronizeFailures;
33 };
34
35 @interface CKDKVSStore ()
36 @property (readwrite, weak) UbiqitousKVSProxy* proxy;
37 @property (readwrite) NSUbiquitousKeyValueStore* cloudStore;
38 @property (assign,readwrite) struct CKDKVSCounters* perfCounters;
39 @property dispatch_queue_t perfQueue;
40 @end
41
42 @implementation CKDKVSStore
43
44 + (instancetype)kvsInterface {
45 return [[CKDKVSStore alloc] init];
46 }
47
48 - (instancetype)init {
49 if ((self = [super init])) {
50
51 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
52 self->_proxy = nil;
53
54 if (!self.cloudStore) {
55 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
56 return nil;
57 }
58 self.perfQueue = dispatch_queue_create("CKDKVSStorePerfQueue", NULL);
59 self.perfCounters = calloc(1, sizeof(struct CKDKVSCounters));
60
61 [self setupSamplers];
62 }
63 return self;
64 }
65
66 - (void)dealloc {
67 if (_perfCounters)
68 free(_perfCounters);
69 }
70
71 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
72 _proxy = proxy;
73
74 [[NSNotificationCenter defaultCenter] addObserver:self
75 selector:@selector(kvsStoreChangedAsync:)
76 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
77 object:nil];
78
79 }
80
81 - (void)setObject:(id)obj forKey:(NSString*)key {
82 secdebug("kvsdebug", "%@ key %@ set to: %@ from: %@", self, key, obj, [self.cloudStore objectForKey:key]);
83 [self.cloudStore setObject:obj forKey:key];
84 }
85
86 - (NSDictionary<NSString *, id>*) copyAsDictionary {
87 return [self.cloudStore dictionaryRepresentation];
88 }
89
90 - (void)addEntriesFromDictionary:(NSDictionary<NSString*, NSObject*> *)otherDictionary {
91 [otherDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) {
92 [self setObject:obj forKey:key];
93 }];
94 }
95
96 - (id)objectForKey:(NSString*)key {
97 return [self.cloudStore objectForKey:key];
98 }
99
100 - (void)removeObjectForKey:(NSString*)key {
101 return [self.cloudStore removeObjectForKey:key];
102 }
103
104 - (void)removeAllObjects {
105 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
106 [self.cloudStore removeObjectForKey:obj];
107 }];
108 }
109
110
111 - (void)forceSynchronizeWithKVS
112 {
113 secnoticeq("pushWrites", "requesting force synchronization with KVS on CloudKit");
114
115 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
116 NSError *error = nil;
117 bool success = [self pullUpdates:&error];
118 if(!success || error != nil) {
119 secerror("pushWrites: failed to synchronize with KVS: %@", error);
120 } else {
121 secnoticeq("pushWrites", "successfully synced with KVS!");
122 }
123 });
124 dispatch_async(self.perfQueue, ^{
125 self.perfCounters->synchronize++;
126 });
127 }
128
129 - (void)pushWrites:(NSArray<NSString*>*)keys requiresForceSync:(BOOL)requiresForceSync
130 {
131 secnoticeq("pushWrites", "Push writes");
132
133 if (SecKVSOnCloudKitIsEnabled() == NO) {
134 secnoticeq("pushWrites", "KVS on CloudKit not enabled");
135
136 [[self cloudStore] synchronize];
137 dispatch_async(self.perfQueue, ^{
138 self.perfCounters->synchronize++;
139 });
140 return;
141 }
142
143 if(requiresForceSync == YES) {
144 secnoticeq("pushWrites", "requested to force synchronize");
145 [self forceSynchronizeWithKVS];
146 return;
147 }
148
149 //if KVS on CK is enabled we should only force sync rings, circles, and key parameters
150 secnoticeq("pushWrites", "KVS on CloudKit enabled. Evaluating changed keys");
151
152 if (keys == nil || [keys count] == 0){
153 secnoticeq("pushWrites", "key set is empty, returning");
154 return;
155 }
156
157 __block BOOL proceedWithSync = NO;
158 [keys enumerateObjectsUsingBlock:^(NSString *kvsKey, NSUInteger idx, BOOL *stop) {
159 if ([kvsKey containsString:(__bridge_transfer NSString*)sRingPrefix] ||
160 [kvsKey containsString:(__bridge_transfer NSString*)sCirclePrefix] ||
161 [kvsKey containsString:(__bridge_transfer NSString*)kSOSKVSKeyParametersKey]) {
162 proceedWithSync = YES;
163 }
164 }];
165
166 if (proceedWithSync == NO) {
167 secnoticeq("pushWrites", "no keys to force push, returning");
168 return;
169 }
170
171 [self forceSynchronizeWithKVS];
172 }
173
174 // Runs on the same thread that posted the notification, and that thread _may_ be the
175 // kdkvsproxy_queue (see 30470419). Avoid deadlock by bouncing through global queue.
176 - (void)kvsStoreChangedAsync:(NSNotification *)notification
177 {
178 secnotice("CloudKeychainProxy", "%@ KVS Remote changed notification: %@", self, notification);
179 dispatch_async(self.perfQueue, ^{
180 self.perfCounters->incomingMessages++;
181 });
182 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
183 [self kvsStoreChanged:notification];
184 });
185 }
186
187 - (void) kvsStoreChanged:(NSNotification *)notification {
188 /*
189 Posted when the value of one or more keys in the local key-value store
190 changed due to incoming data pushed from iCloud. This notification is
191 sent only upon a change received from iCloud; it is not sent when your
192 app sets a value.
193
194 The user info dictionary can contain the reason for the notification as
195 well as a list of which values changed, as follows:
196
197 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
198 present, indicates why the key-value store changed. Its value is one of
199 the constants in "Change Reason Values."
200
201 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
202 is an array of strings, each the name of a key whose value changed. The
203 notification object is the NSUbiquitousKeyValueStore object whose contents
204 changed.
205
206 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
207 local value that has been overwritten by a distant value. If there is no
208 conflict between the local and the distant values when doing the initial
209 sync (e.g. if the cloud has no data stored or the client has not stored
210 any data yet), you'll never see that notification.
211
212 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
213 with server but initial round trip with server does not imply
214 NSUbiquitousKeyValueStoreInitialSyncChange.
215 */
216 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
217 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
218
219 UbiqitousKVSProxy* proxy = self.proxy;
220 if(!proxy) {
221 // Weak reference went away.
222 return;
223 }
224
225 NSDictionary *userInfo = [notification userInfo];
226 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
227 NSArray *keysChangedArray = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
228 NSSet *keysChanged = keysChangedArray ? [NSSet setWithArray: keysChangedArray] : nil;
229
230 if (reason) switch ([reason integerValue]) {
231 case NSUbiquitousKeyValueStoreInitialSyncChange:
232 [proxy storeKeysChanged: keysChanged initial: YES];
233 break;
234
235 case NSUbiquitousKeyValueStoreServerChange:
236 [proxy storeKeysChanged: keysChanged initial: NO];
237 break;
238
239 case NSUbiquitousKeyValueStoreQuotaViolationChange:
240 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
241 break;
242
243 case NSUbiquitousKeyValueStoreAccountChange:
244 [proxy storeAccountChanged];
245 break;
246
247 default:
248 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
249 break;
250 }
251 });
252 }
253
254 - (BOOL) pullUpdates:(NSError **)failure
255 {
256 __block NSError *tempFailure = nil;
257
258 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
259
260 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH: %@", kWAIT2MINID, self);
261
262 dispatch_async(self.perfQueue, ^{
263 self.perfCounters->synchronizeWithCompletionHandler++;
264 });
265
266 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
267 if (error) {
268 dispatch_async(self.perfQueue, ^{
269 self.perfCounters->synchronizeFailures++;
270 });
271 tempFailure = error;
272 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
273 } else {
274 dispatch_async(self.perfQueue, ^{
275 self.perfCounters->synchronize++;
276 });
277 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
278 if(SecKVSOnCloudKitIsEnabled() == NO) {
279 [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
280 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
281 }
282 }
283 dispatch_semaphore_signal(freshSemaphore);
284 }];
285 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
286
287 if (failure && (*failure == NULL)) {
288 *failure = tempFailure;
289 }
290
291 return tempFailure == nil;
292 }
293
294 - (void)perfCounters:(void(^)(NSDictionary *counters))callback
295 {
296 dispatch_async(self.perfQueue, ^{
297 callback(@{
298 CKDKVSPerfCounterSynchronize : @(self.perfCounters->synchronize),
299 CKDKVSPerfCounterSynchronizeWithCompletionHandler : @(self.perfCounters->synchronizeWithCompletionHandler),
300 CKDKVSPerfCounterIncomingMessages : @(self.perfCounters->incomingMessages),
301 CKDKVSPerfCounterOutgoingMessages : @(self.perfCounters->outgoingMessages),
302 CKDKVSPerfCounterTotalWaitTimeSynchronize : @(self.perfCounters->totalWaittimeSynchronize),
303 CKDKVSPerfCounterLongestWaitTimeSynchronize : @(self.perfCounters->longestWaittimeSynchronize),
304 CKDKVSPerfCounterSynchronizeFailures : @(self.perfCounters->synchronizeFailures),
305 });
306 });
307 }
308
309 #if __OBJC2__
310 - (void)setupSamplers
311 {
312 [[SOSAnalytics logger] AddMultiSamplerForName:CKDKVSPerformanceCountersSampler
313 withTimeInterval:SFAnalyticsSamplerIntervalOncePerReport
314 block:^NSDictionary<NSString *,NSNumber *> *{
315 __block NSDictionary<NSString *,NSNumber *>* data;
316 [self perfCounters:^(NSDictionary *counters) {
317 data = counters;
318 }];
319
320 dispatch_sync(self.perfQueue, ^{
321 memset(self.perfCounters, 0, sizeof(struct CKDKVSCounters));
322 });
323 return data;
324 }];
325 }
326 #else
327 - (void)setupSamplers
328 {
329 // SFA is only for 64 bit cool kids
330 }
331 #endif
332
333 - (void)addOneToOutGoing
334 {
335 dispatch_async(self.perfQueue, ^{
336 self.perfCounters->outgoingMessages++;
337 });
338 }
339
340
341 @end