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