]> git.saurik.com Git - apple/security.git/blob - KVSKeychainSyncingProxy/CKDKVSStore.m
Security-57740.51.3.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 //KVS error codes
21 #define UPDATE_RESUBMIT 4
22
23 @interface CKDKVSStore ()
24 @property (readwrite, weak) UbiqitousKVSProxy* proxy;
25 @property (readwrite) NSUbiquitousKeyValueStore* cloudStore;
26 @end
27
28 @implementation CKDKVSStore
29
30 + (instancetype)kvsInterface {
31 return [[CKDKVSStore alloc] init];
32 }
33
34 - (instancetype)init {
35 self = [super init];
36
37 self->_cloudStore = [NSUbiquitousKeyValueStore defaultStore];
38 self->_proxy = nil;
39
40 if (!self.cloudStore) {
41 secerror("NO NSUbiquitousKeyValueStore defaultStore!!!");
42 return nil;
43 }
44
45 return self;
46 }
47
48 - (void) connectToProxy: (UbiqitousKVSProxy*) proxy {
49 _proxy = proxy;
50
51 [[NSNotificationCenter defaultCenter] addObserver:self
52 selector:@selector(kvsStoreChangedAsync:)
53 name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
54 object:nil];
55
56 }
57
58 - (void)setObject:(id)obj forKey:(NSString*)key {
59 secdebug("kvsdebug", "%@ key %@ set to: %@ from: %@", self, key, obj, [self.cloudStore objectForKey:key]);
60 [self.cloudStore setObject:obj forKey:key];
61 }
62
63 - (NSDictionary<NSString *, id>*) copyAsDictionary {
64 return [self.cloudStore dictionaryRepresentation];
65 }
66
67 - (void)addEntriesFromDictionary:(NSDictionary<NSString*, NSObject*> *)otherDictionary {
68 [otherDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSObject * _Nonnull obj, BOOL * _Nonnull stop) {
69 [self setObject:obj forKey:key];
70 }];
71 }
72
73 - (id)objectForKey:(NSString*)key {
74 return [self.cloudStore objectForKey:key];
75 }
76
77 - (void)removeObjectForKey:(NSString*)key {
78 return [self.cloudStore removeObjectForKey:key];
79 }
80
81 - (void)removeAllObjects {
82 [[[[self.cloudStore dictionaryRepresentation] allKeys] copy] enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
83 [self.cloudStore removeObjectForKey:obj];
84 }];
85 }
86
87 - (void)pushWrites {
88 [[self cloudStore] synchronize];
89 }
90
91 // Runs on the same thread that posted the notification, and that thread _may_ be the
92 // kdkvsproxy_queue (see 30470419). Avoid deadlock by bouncing through global queue.
93 - (void)kvsStoreChangedAsync:(NSNotification *)notification
94 {
95 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
96 [self kvsStoreChanged:notification];
97 });
98 }
99
100 - (void) kvsStoreChanged:(NSNotification *)notification {
101 /*
102 Posted when the value of one or more keys in the local key-value store
103 changed due to incoming data pushed from iCloud. This notification is
104 sent only upon a change received from iCloud; it is not sent when your
105 app sets a value.
106
107 The user info dictionary can contain the reason for the notification as
108 well as a list of which values changed, as follows:
109
110 The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
111 present, indicates why the key-value store changed. Its value is one of
112 the constants in "Change Reason Values."
113
114 The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
115 is an array of strings, each the name of a key whose value changed. The
116 notification object is the NSUbiquitousKeyValueStore object whose contents
117 changed.
118
119 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
120 local value that has been overwritten by a distant value. If there is no
121 conflict between the local and the distant values when doing the initial
122 sync (e.g. if the cloud has no data stored or the client has not stored
123 any data yet), you'll never see that notification.
124
125 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
126 with server but initial round trip with server does not imply
127 NSUbiquitousKeyValueStoreInitialSyncChange.
128 */
129 os_activity_initiate("cloudChanged", OS_ACTIVITY_FLAG_DEFAULT, ^{
130 secdebug(XPROXYSCOPE, "%@ KVS Remote changed notification: %@", self, notification);
131
132 NSDictionary *userInfo = [notification userInfo];
133 NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
134 NSArray *keysChangedArray = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
135 NSSet *keysChanged = keysChangedArray ? [NSSet setWithArray: keysChangedArray] : nil;
136
137 if (reason) switch ([reason integerValue]) {
138 case NSUbiquitousKeyValueStoreInitialSyncChange:
139 [self.proxy storeKeysChanged: keysChanged initial: YES];
140 break;
141
142 case NSUbiquitousKeyValueStoreServerChange:
143 [self.proxy storeKeysChanged: keysChanged initial: NO];
144 break;
145
146 case NSUbiquitousKeyValueStoreQuotaViolationChange:
147 seccritical("Received NSUbiquitousKeyValueStoreQuotaViolationChange");
148 break;
149
150 case NSUbiquitousKeyValueStoreAccountChange:
151 [self.proxy storeAccountChanged];
152 break;
153
154 default:
155 secinfo("kvsstore", "ignoring unknown notification: %@", reason);
156 break;
157 }
158 });
159 }
160
161 // try to synchronize asap, and invoke the handler on completion to take incoming changes.
162
163 static bool isResubmitError(NSError* error) {
164 return error && (CFErrorGetCode((__bridge CFErrorRef) error) == UPDATE_RESUBMIT) &&
165 (CFErrorGetDomain((__bridge CFErrorRef)error) == __SYDErrorKVSDomain);
166 }
167
168 - (BOOL) pullUpdates:(NSError **)failure
169 {
170 __block NSError *tempFailure = nil;
171 const int kMaximumTries = 10;
172 int tryCount = 0;
173 // Retry up to 10 times, since we're told this can fail and WE have to deal with it.
174
175 dispatch_semaphore_t freshSemaphore = dispatch_semaphore_create(0);
176
177 do {
178 ++tryCount;
179 secnoticeq("fresh", "%s CALLING OUT TO syncdefaultsd SWCH, try %d: %@", kWAIT2MINID, tryCount, self);
180
181 [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
182 if (error) {
183 tempFailure = error;
184 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@: %@", kWAIT2MINID, self, error);
185 } else {
186 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SWCH: %@", kWAIT2MINID, self);
187 [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
188 secnotice("fresh", "%s RETURNING FROM syncdefaultsd SYNC: %@", kWAIT2MINID, self);
189 }
190 dispatch_semaphore_signal(freshSemaphore);
191 }];
192 dispatch_semaphore_wait(freshSemaphore, DISPATCH_TIME_FOREVER);
193 } while (tryCount < kMaximumTries && isResubmitError(tempFailure));
194
195 if (isResubmitError(tempFailure)) {
196 secerrorq("%s %d retry attempts to request freshness exceeded, failing", kWAIT2MINID, tryCount);
197 }
198
199 if (failure && (*failure == NULL)) {
200 *failure = tempFailure;
201 }
202
203 return tempFailure == nil;
204 }
205
206 @end