1/*
2 * Copyright (c) 2012-2013 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24//
25//  CKDKVSProxy.m
26//  ckd-xpc
27//
28
29#import <Foundation/NSUbiquitousKeyValueStore.h>
30#import <Foundation/NSUbiquitousKeyValueStore_Private.h>
31#import <Foundation/NSArray.h>
32#import <Foundation/Foundation.h>
33
34#import <Security/SecBasePriv.h>
35#import <Security/SecItemPriv.h>
36#import <utilities/debugging.h>
37#import <notify.h>
38
39#import "CKDKVSProxy.h"
40#import "CKDPersistentState.h"
41#import "CKDUserInteraction.h"
42
43#import "SOSARCDefines.h"
44
45#include <SecureObjectSync/SOSAccount.h>
46#include <SecureObjectSync/SOSCloudCircleInternal.h>
47#include "SOSCloudKeychainConstants.h"
48
49#include <utilities/SecAKSWrappers.h>
50#include <utilities/SecCFRelease.h>
51
52#define SOSCKCSCOPE "sync"
53
54/*
55    The total space available in your app’s iCloud key-value storage is 1 MB.
56    The maximum number of keys you can specify is 1024, and the size limit for
57    each value associated with a key is 1 MB. So, for example, if you store a
58    single large value of 1 MB for a single key, that consumes your total
59    available storage. If you store 1 KB of data for each key, you can use
60    1,000 key-value pairs.
61*/
62
63static const char *kStreamName = "com.apple.notifyd.matching";
64
65
66static NSString *kKeyAlwaysKeys = @"AlwaysKeys";
67static NSString *kKeyFirstUnlockKeys = @"FirstUnlockKeys";
68static NSString *kKeyUnlockedKeys = @"UnlockedKeys";
69static NSString *kKeyPendingKeys = @"PendingKeys";
70static NSString *kKeyUnsentChangedKeys = @"unsentChangedKeys";
71static NSString *kKeyUnlockNotificationRequested = @"unlockNotificationRequested";
72static NSString *kKeySyncWithPeersPending = @"SyncWithPeersPending";
73
74enum
75{
76    kCallbackMethodSecurityd = 0,
77    kCallbackMethodXPC = 1,
78};
79
80static const int64_t kMinSyncDelay = (NSEC_PER_MSEC * 500);
81static const int64_t kMaxSyncDelay = (NSEC_PER_SEC * 5);
82static const int64_t kMinSyncInterval = (NSEC_PER_SEC * 15);
83static const int64_t kSyncTimerLeeway = (NSEC_PER_MSEC * 250);
84
85@interface NSUbiquitousKeyValueStore (NSUbiquitousKeyValueStore_PrivateZ)
86- (void) _synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
87
88/*
89// SPI For Security
90- (void) synchronizeWithCompletionHandler:(void (^)(NSError *error))completionHandler;
91 */
92
93@end
94
95@implementation UbiqitousKVSProxy
96
97
98- (void)persistState
99{
100    [SOSPersistentState setRegisteredKeys:[self exportKeyInterests]];
101}
102
103+ (UbiqitousKVSProxy *) sharedKVSProxy
104{
105    static UbiqitousKVSProxy *sharedKVSProxy;
106    if (!sharedKVSProxy) {
107        static dispatch_once_t onceToken;
108        dispatch_once(&onceToken, ^{
109            sharedKVSProxy = [[self alloc] init];
110        });
111    }
112    return sharedKVSProxy;
113}
114
115- (id)init
116{
117    if (self = [super init])
118    {
119        secnotice("event", "%@ start", self);
120
121        _syncTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
122        dispatch_source_set_timer(_syncTimer, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
123        dispatch_source_set_event_handler(_syncTimer, ^{
124            [self timerFired];
125        });
126        dispatch_resume(_syncTimer);
127
128        [[NSNotificationCenter defaultCenter]
129            addObserver: self
130               selector: @selector (iCloudAccountAvailabilityChanged:)
131                   name: NSUbiquityIdentityDidChangeNotification
132                 object: nil];
133
134        [[NSNotificationCenter defaultCenter] addObserver:self
135                selector:@selector(cloudChanged:)
136                name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
137                object:nil];
138
139        [self importKeyInterests: [SOSPersistentState registeredKeys]];
140
141        // Register for lock state changes
142        xpc_set_event_stream_handler(kStreamName, dispatch_get_main_queue(),
143                                     ^(xpc_object_t notification){
144                                         [self streamEvent:notification];
145                                     });
146
147        [self updateUnlockedSinceBoot];
148        [self updateIsLocked];
149        if (!_isLocked)
150            [self keybagDidUnlock];
151
152        secdebug(XPROXYSCOPE, "%@ done", self);
153    }
154    return self;
155}
156
157- (NSString *)description
158{
159    return [NSString stringWithFormat:@"<%s%s%s%s%s%s%s%s%s>",
160            _isLocked ? "L" : "U",
161            _unlockedSinceBoot ? "B" : "-",
162            _seenKVSStoreChange ? "K" : "-",
163            _syncTimerScheduled ? "T" : "-",
164            _syncWithPeersPending ? "s" : "-",
165            [_pendingKeys count] ? "p" : "-",
166            _inCallout ? "C" : "-",
167            _shadowSyncWithPeersPending ? "S" : "-",
168            [_shadowPendingKeys count] ? "P" : "-"];
169}
170
171- (void)processAllItems
172{
173    NSDictionary *allItems = [self getAll];
174    if (allItems)
175    {
176        secnotice("event", "%@ sending: %@", self, [[allItems allKeys] componentsJoinedByString: @" "]);
177        [self processKeyChangedEvent:allItems];
178    }
179    else
180        secdebug(XPROXYSCOPE, "%@ No items in KVS", self);
181}
182
183- (void)setItemsChangedBlock:(CloudItemsChangedBlock)itemsChangedBlock
184{
185    self->itemsChangedCallback = itemsChangedBlock;
186}
187
188- (void)dealloc
189{
190    secdebug(XPROXYSCOPE, "%@", self);
191    [[NSNotificationCenter defaultCenter] removeObserver:self
192        name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil];
193
194    [[NSNotificationCenter defaultCenter] removeObserver:self
195        name:NSUbiquityIdentityDidChangeNotification object:nil];
196}
197
198// MARK: ----- Client Interface -----
199
200- (void)setObject:(id)obj forKey:(id)key
201{
202    secdebug("keytrace", "%@ key: %@, obj: %@", self, key, obj);
203    NSUbiquitousKeyValueStore *store = [self cloudStore];
204    if (store)
205    {
206        id value = [store objectForKey:key];
207        if (value)
208            secdebug("keytrace", "%@ Current value (before set) for key %@ is: %@", self, key, value);
209        else
210            secdebug("keytrace", "%@ No current value for key %@", self, key);
211    }
212    else
213        secdebug("keytrace", "Can't get store");
214
215    [[self cloudStore] setObject:obj forKey:key];
216    [self requestSynchronization:NO];
217}
218
219- (void)setObjectsFromDictionary:(NSDictionary *)values
220{
221    secdebug(XPROXYSCOPE, "%@ start: %lu values to put", self, (unsigned long)[values count]);
222
223    NSUbiquitousKeyValueStore *store = [self cloudStore];
224    if (store && values)
225    {
226        [values enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop)
227        {
228            if (obj == NULL || obj == [NSNull null])
229                [store removeObjectForKey:key];
230            else
231                [store setObject:obj forKey:key];
232        }];
233
234        [self requestSynchronization:NO];
235    }
236    else
237        secdebug(XPROXYSCOPE, "%@ NULL? store: %@, values: %@", self, store, values);
238}
239
240- (void)requestSynchronization:(bool)force
241{
242    if (force)
243    {
244        secdebug(XPROXYSCOPE, "%@ synchronize (forced)", self);
245        [[self cloudStore] synchronize];
246    }
247    else
248    {
249        secdebug(XPROXYSCOPE, "%@ synchronize (soon)", self);
250        [[self cloudStore] synchronize];
251    }
252}
253
254- (NSUbiquitousKeyValueStore *)cloudStore
255{
256    NSUbiquitousKeyValueStore *iCloudStore = [NSUbiquitousKeyValueStore defaultStore];
257//    secdebug(XPROXYSCOPE, "cloudStore: %@", iCloudStore);
258    return iCloudStore;
259}
260
261// try to synchronize asap, and invoke the handler on completion to take incoming changes.
262- (void)waitForSynchronization:(NSArray *)keys handler:(void (^)(NSDictionary *values, NSError *err))handler
263{
264    secdebug(XPROXYSCOPE, "%@ synchronize and wait", self);
265
266    if (!keys)
267    {
268        NSError *err = [NSError errorWithDomain:(NSString *)NSPOSIXErrorDomain code:(NSInteger)ENOPROTOOPT userInfo:nil];
269        handler(@{}, err);
270        return;
271    }
272
273    secdebug(XPROXYSCOPE, "%@ Requesting synchronizeWithCompletionHandler", self);
274
275    [[self cloudStore] synchronizeWithCompletionHandler:^(NSError *error) {
276        [[self cloudStore] synchronize]; // Per olivier in <rdar://problem/13412631>, sync before getting values
277
278        NSDictionary * changedKeys = error ? @{} : [self copyChangedValues:[NSSet setWithArray:keys]];
279
280        secdebug(XPROXYSCOPE, "%@ Got synchronize completion %@ (error %@)", self, changedKeys, error);
281        handler(changedKeys, error);
282    }];
283}
284
285- (void)dumpStore
286{
287    NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
288    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
289        secdebug(XPROXYSCOPE, "%@ Dump: value for key %@ is: %@", self, key, obj);
290    }];
291}
292
293- (void)removeObjectForKey:(NSString *)keyToRemove
294{
295    [[self cloudStore] removeObjectForKey:keyToRemove];
296}
297
298- (void)clearStore
299{
300    secdebug(XPROXYSCOPE, "%@ clearStore", self);
301    NSDictionary *dict = [[self cloudStore] dictionaryRepresentation];
302    NSArray *allKeys = [dict allKeys];
303    [allKeys enumerateObjectsUsingBlock:^(id key, NSUInteger idx, BOOL *stop)
304    {
305        secdebug(XPROXYSCOPE, "%@ Clearing value for key %@", self, key);
306        [[self cloudStore] removeObjectForKey:(NSString *)key];
307    }];
308
309    [self requestSynchronization:YES];
310}
311
312
313//
314// MARK: ----- KVS key lists -----
315//
316
317- (id)get:(id)key
318{
319    return [[self cloudStore] objectForKey:key];
320}
321
322- (NSDictionary *)getAll
323{
324    return [[self cloudStore] dictionaryRepresentation];
325}
326
327- (NSDictionary*) exportKeyInterests
328{
329    return @{ kKeyAlwaysKeys:[_alwaysKeys allObjects],
330              kKeyFirstUnlockKeys:[_firstUnlockKeys allObjects],
331              kKeyUnlockedKeys:[_unlockedKeys allObjects],
332              kKeyPendingKeys:[_pendingKeys allObjects],
333              kKeySyncWithPeersPending:[NSNumber numberWithBool:_syncWithPeersPending]
334              };
335}
336
337- (void) importKeyInterests: (NSDictionary*) interests
338{
339    _alwaysKeys = [NSSet setWithArray: interests[kKeyAlwaysKeys]];
340    _firstUnlockKeys = [NSSet setWithArray: interests[kKeyFirstUnlockKeys]];
341    _unlockedKeys = [NSSet setWithArray: interests[kKeyUnlockedKeys]];
342    _pendingKeys = [NSMutableSet setWithArray: interests[kKeyPendingKeys]];
343    _syncWithPeersPending = [interests[kKeySyncWithPeersPending] boolValue];
344}
345
346- (NSMutableSet *)copyAllKeys
347{
348    NSMutableSet *allKeys = [NSMutableSet setWithSet: _alwaysKeys];
349    [allKeys unionSet: _firstUnlockKeys];
350    [allKeys unionSet: _unlockedKeys];
351    return allKeys;
352}
353
354- (NSDictionary *)registerKeysAndGet:(BOOL)getNewKeysOnly always:(NSArray *)keysToRegister reqFirstUnlock:(NSArray *)reqFirstUnlock reqUnlocked:(NSArray *)reqUnlocked
355{
356    NSMutableSet *allOldKeys = [self copyAllKeys];
357    _alwaysKeys = [NSSet setWithArray: keysToRegister];
358    _firstUnlockKeys = [NSSet setWithArray: reqFirstUnlock];
359    _unlockedKeys = [NSSet setWithArray: reqUnlocked];
360    NSMutableSet *allNewKeys = [self copyAllKeys];
361
362    [_pendingKeys intersectSet:allNewKeys];
363
364    NSSet* keysToGet = nil;
365    if (getNewKeysOnly) {
366        [allNewKeys minusSet:allOldKeys];
367        keysToGet = allNewKeys;
368    } else {
369        keysToGet = [self keysForCurrentLockState];
370    }
371
372    // Mark keysToGet for the current lockState as pending
373    NSSet *filteredKeys = [self filterKeySetWithLockState:keysToGet];
374    [self persistState];
375    NSMutableDictionary *returnedValues = [self copyChangedValues: filteredKeys];
376
377    secnotice("keytrace", "%@ [%s] always: %@ firstUnlock: %@ unlocked: %@ got: %@", self, getNewKeysOnly ? "new only " : "", [keysToRegister componentsJoinedByString: @" "], [reqFirstUnlock componentsJoinedByString: @" "], [reqUnlocked componentsJoinedByString: @" "], [[returnedValues allKeys] componentsJoinedByString: @" "]);
378
379    [self processKeyChangedEvent:returnedValues];
380    [returnedValues removeAllObjects];
381
382    return returnedValues;
383}
384
385- (NSDictionary *)localNotification:(NSDictionary *)localNotificationDict outFlags:(int64_t *)outFlags
386{
387    __block bool done = false;
388    __block int64_t returnedFlags = 0;
389    __block NSDictionary *responses = NULL;
390    typedef bool (^CKDUserInteractionBlock) (CFDictionaryRef responses);
391
392    CKDUserInteraction *cui = [CKDUserInteraction sharedInstance];
393    [cui requestShowNotification:localNotificationDict completion:^ bool (CFDictionaryRef userResponses, int64_t flags)
394        {
395            responses = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)userResponses];
396            returnedFlags = flags;
397            secdebug(XPROXYSCOPE, "%@ requestShowNotification: dict: %@, flags: %#llx", self, responses, returnedFlags);
398            done = true;
399            return true;
400        }];
401
402    // TODO: replace with e.g. dispatch calls to wait, or semaphore
403    while (!done)
404        sleep(1);
405    if (outFlags)
406    {
407        secdebug(XPROXYSCOPE, "%@ outFlags: %#llx", self, returnedFlags);
408        *outFlags = returnedFlags;
409    }
410    return responses;
411}
412
413- (void)setParams:(NSDictionary *)paramsDict
414{
415    secdebug(XPROXYSCOPE, "%@ setParams: %@ ", self, paramsDict);
416    NSString *cbmethod = [paramsDict objectForKey:(__bridge id)kParamCallbackMethod];
417    if (!cbmethod)
418        return;
419
420    if ([cbmethod isEqualToString:(__bridge NSString *)kParamCallbackMethodSecurityd])
421        callbackMethod = kCallbackMethodSecurityd;
422    else if ([cbmethod isEqualToString:(__bridge NSString *)kParamCallbackMethodXPC])
423        callbackMethod = kCallbackMethodXPC;
424}
425
426- (void)saveToUbiquitousStore
427{
428    [self requestSynchronization:NO];
429}
430
431// MARK: ----- Event Handling -----
432
433- (void)streamEvent:(xpc_object_t)notification
434{
435#if (!TARGET_IPHONE_SIMULATOR)
436    const char *notificationName = xpc_dictionary_get_string(notification, "Notification");
437    if (!notificationName) {
438    } else if (strcmp(notificationName, kUserKeybagStateChangeNotification)==0) {
439        return [self keybagStateChange];
440    } else if (strcmp(notificationName, kCloudKeychainStorechangeChangeNotification)==0) {
441        return [self kvsStoreChange];
442    } else if (strcmp(notificationName, kNotifyTokenForceUpdate)==0) {
443        // DEBUG -- Possibly remove in future
444        return [self processAllItems];
445    }
446    const char *eventName = xpc_dictionary_get_string(notification, "XPCEventName");
447    char *desc = xpc_copy_description(notification);
448    secerror("%@ event: %s name: %s desc: %s", self, eventName, notificationName, desc);
449    if (desc)
450        free((void *)desc);
451#endif
452}
453
454- (void)iCloudAccountAvailabilityChanged:(NSNotification*)notification
455{
456    /*
457        Continuing this example, you’d then implement an iCloudAccountAvailabilityChanged: method that would:
458
459        Call the ubiquityIdentityToken method and store its return value.
460        Compare the new value to the previous value, to find out if the user logged out of their account or
461        logged in to a different account. If the previously-used account is now unavailable, save the current
462        state locally as needed, empty your iCloud-related data caches, and refresh all iCloud-related user interface elements.
463    */
464    id previCloudToken = currentiCloudToken;
465    currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
466    if (previCloudToken != currentiCloudToken)
467        secnotice("event", "%@ iCloud account changed!", self);
468    else
469        secnotice("event", "%@ %@", self, notification);
470}
471
472- (void)cloudChanged:(NSNotification*)notification
473{
474    /*
475        Posted when the value of one or more keys in the local key-value store
476        changed due to incoming data pushed from iCloud. This notification is
477        sent only upon a change received from iCloud; it is not sent when your
478        app sets a value.
479
480        The user info dictionary can contain the reason for the notification as
481        well as a list of which values changed, as follows:
482
483        The value of the NSUbiquitousKeyValueStoreChangeReasonKey key, when
484        present, indicates why the key-value store changed. Its value is one of
485        the constants in "Change Reason Values."
486
487        The value of the NSUbiquitousKeyValueStoreChangedKeysKey, when present,
488        is an array of strings, each the name of a key whose value changed. The
489        notification object is the NSUbiquitousKeyValueStore object whose contents
490        changed.
491
492        NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
493        local value that has been overwritten by a distant value. If there is no
494        conflict between the local and the distant values when doing the initial
495        sync (e.g. if the cloud has no data stored or the client has not stored
496        any data yet), you'll never see that notification.
497
498        NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
499        with server but initial round trip with server does not imply
500        NSUbiquitousKeyValueStoreInitialSyncChange.
501    */
502
503    secdebug(XPROXYSCOPE, "%@ cloudChanged notification: %@", self, notification);
504
505    NSDictionary *userInfo = [notification userInfo];
506    NSNumber *reason = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
507    if (reason) switch ([reason integerValue]) {
508        case NSUbiquitousKeyValueStoreInitialSyncChange:
509        case NSUbiquitousKeyValueStoreServerChange:
510        {
511            _seenKVSStoreChange = YES;
512            NSSet *keysChangedInCloud = [NSSet setWithArray:[userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]];
513            NSSet *keysOfInterestThatChanged = [self filterKeySetWithLockState:keysChangedInCloud];
514            NSMutableDictionary *changedValues = [self copyChangedValues:keysOfInterestThatChanged];
515            if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
516                changedValues[(__bridge NSString*)kSOSKVSInitialSyncKey] =  @"true";
517
518            secnotice("event", "%@ keysChangedInCloud: %@ keysOfInterest: %@", self, [[keysChangedInCloud allObjects] componentsJoinedByString: @" "], [[changedValues allKeys] componentsJoinedByString: @" "]);
519            if ([changedValues count])
520                [self processKeyChangedEvent:changedValues];
521            break;
522        }
523        case NSUbiquitousKeyValueStoreQuotaViolationChange:
524            seccritical("%@ event received NSUbiquitousKeyValueStoreQuotaViolationChange", self);
525            break;
526        case NSUbiquitousKeyValueStoreAccountChange:
527            // The primary account changed. We do not get this for password changes on the same account
528            secnotice("event", "%@ NSUbiquitousKeyValueStoreAccountChange", self);
529            if ([reason integerValue] == NSUbiquitousKeyValueStoreInitialSyncChange)
530            {
531                NSDictionary *changedValues = @{ (__bridge NSString*)kSOSKVSAccountChangedKey:  @"true" };
532                [self processKeyChangedEvent:changedValues];
533            }
534            break;
535    }
536}
537
538- (void) calloutWith: (void(^)(NSSet *pending, bool syncWithPeersPending, dispatch_queue_t queue, void(^done)(NSSet *handledKeys, bool handledSyncWithPeers))) callout
539{
540    // In CKDKVSProxy's serial queue
541    NSSet *myPending = [_pendingKeys copy];
542    bool mySyncWithPeersPending = _syncWithPeersPending;
543    bool wasLocked = _isLocked;
544    _inCallout = YES;
545    _shadowPendingKeys = [NSMutableSet set];
546    _shadowSyncWithPeersPending = NO;
547    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
548        callout(myPending, mySyncWithPeersPending, dispatch_get_main_queue(), ^(NSSet *handledKeys, bool handledSyncWithPeers) {
549            // In CKDKVSProxy's serial queue
550            _inCallout = NO;
551
552            // Update SyncWithPeers stuff.
553            _syncWithPeersPending = (mySyncWithPeersPending && (!handledSyncWithPeers)) || _shadowSyncWithPeersPending;
554            _shadowSyncWithPeersPending = NO;
555            if (handledSyncWithPeers)
556                _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
557
558            // Update pendingKeys and handle them
559            [_pendingKeys minusSet: handledKeys];
560            bool hadShadowPendingKeys = [_shadowPendingKeys count];
561            NSSet *filteredKeys = [self filterKeySetWithLockState:_shadowPendingKeys];
562            _shadowPendingKeys = nil;
563
564            // Write state to disk
565            [self persistState];
566
567            // Handle shadow pended stuff
568            if (_syncWithPeersPending && !_isLocked)
569                [self scheduleSyncRequestTimer];
570            /* We don't want to call processKeyChangedEvent if we failed to
571               handle pending keys and the device didn't unlock nor receive
572               any kvs changes while we were in our callout.
573               Doing so will lead to securityd and CloudKeychainProxy
574               talking to each other forever in a tight loop if securityd
575               repeatedly returns an error processing the same message.
576               Instead we leave any old pending keys until the next event. */
577            if (hadShadowPendingKeys || (!_isLocked && wasLocked))
578                [self processKeyChangedEvent:[self copyChangedValues:filteredKeys]];
579        });
580    });
581}
582
583- (void) doSyncWithAllPeers
584{
585    [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, dispatch_queue_t queue, void(^done)(NSSet *, bool)) {
586        CFErrorRef error = NULL;
587        SyncWithAllPeersReason reason = SOSCCProcessSyncWithAllPeers(&error);
588        dispatch_async(queue, ^{
589            bool handledSyncWithPeers = NO;
590            if (reason == kSyncWithAllPeersSuccess) {
591                handledSyncWithPeers = YES;
592                secnotice("event", "%@ syncWithAllPeers succeeded", self);
593            } else if (reason == kSyncWithAllPeersLocked) {
594                secnotice("event", "%@ syncWithAllPeers attempted while locked - waiting for unlock", self);
595                handledSyncWithPeers = YES;
596            } else if (reason == kSyncWithAllPeersOtherFail) {
597                // Pretend we handled syncWithPeers, by pushing out the _lastSyncTime
598                // This will cause us to wait for kMinSyncInterval seconds before
599                // retrying, so we don't spam securityd if sync is failing
600                secerror("%@ syncWithAllPeers %@, rescheduling timer", self, error);
601                _lastSyncTime = dispatch_time(DISPATCH_TIME_NOW, 0);
602            } else {
603                secerror("%@ syncWithAllPeers %@, unknown reason: %d", self, error, reason);
604            }
605
606            done(nil, handledSyncWithPeers);
607            CFReleaseSafe(error);
608        });
609    }];
610}
611
612- (void)timerFired
613{
614    secnotice("event", "%@ syncWithPeersPending: %d inCallout: %d isLocked: %d", self, _syncWithPeersPending, _inCallout, _isLocked);
615    _syncTimerScheduled = NO;
616    if (_syncWithPeersPending && !_inCallout && !_isLocked)
617        [self doSyncWithAllPeers];
618}
619
620- (dispatch_time_t) nextSyncTime
621{
622    dispatch_time_t nextSync = dispatch_time(DISPATCH_TIME_NOW, kMinSyncDelay);
623
624    // Don't sync again unless we waited at least kMinSyncInterval
625    if (_lastSyncTime) {
626        dispatch_time_t soonest = dispatch_time(_lastSyncTime, kMinSyncInterval);
627        if (nextSync < soonest || _deadline < soonest) {
628            secdebug("timer", "%@ backing off", self);
629            return soonest;
630        }
631    }
632
633    // Don't delay more than kMaxSyncDelay after the first request.
634    if (nextSync > _deadline) {
635        secdebug("timer", "%@ hit deadline", self);
636        return _deadline;
637    }
638
639    // Bump the timer by kMinSyncDelay
640    if (_syncTimerScheduled)
641        secdebug("timer", "%@ bumped timer", self);
642    else
643        secdebug("timer", "%@ scheduled timer", self);
644
645    return nextSync;
646}
647
648- (void)scheduleSyncRequestTimer
649{
650    dispatch_source_set_timer(_syncTimer, [self nextSyncTime], DISPATCH_TIME_FOREVER, kSyncTimerLeeway);
651    _syncTimerScheduled = YES;
652}
653
654- (void)requestSyncWithAllPeers // secd calling SOSCCSyncWithAllPeers invokes this
655{
656    secdebug("event", "%@ _syncWithPeersPending: %d _inCallout: %d _shadowSyncWithPeersPending: %d _isLocked: %d", self, _syncWithPeersPending, _inCallout, _shadowSyncWithPeersPending, _isLocked);
657
658    if (!_syncWithPeersPending || (_inCallout && !_shadowSyncWithPeersPending))
659        _deadline = dispatch_time(DISPATCH_TIME_NOW, kMaxSyncDelay);
660
661    if (!_syncWithPeersPending) {
662        _syncWithPeersPending = YES;
663        [self persistState];
664    }
665
666    if (_inCallout)
667        _shadowSyncWithPeersPending = YES;
668
669    if (!_isLocked)
670        [self scheduleSyncRequestTimer];
671}
672
673- (BOOL) updateUnlockedSinceBoot
674{
675    CFErrorRef aksError = NULL;
676    if (!SecAKSGetHasBeenUnlocked(&_unlockedSinceBoot, &aksError)) {
677        secerror("%@ Got error from SecAKSGetHasBeenUnlocked: %@", self, aksError);
678        CFReleaseSafe(aksError);
679        return NO;
680    }
681    return YES;
682}
683
684- (BOOL) updateIsLocked
685{
686    CFErrorRef aksError = NULL;
687    if (!SecAKSGetIsLocked(&_isLocked, &aksError)) {
688        secerror("%@ Got error querying lock state: %@", self, aksError);
689        CFReleaseSafe(aksError);
690        return NO;
691    }
692    if (!_isLocked)
693        _unlockedSinceBoot = YES;
694    return YES;
695}
696
697- (void) keybagStateChange
698{
699    BOOL wasLocked = _isLocked;
700    if ([self updateIsLocked]) {
701        if (wasLocked == _isLocked)
702            secdebug("event", "%@ still %s ignoring", self, _isLocked ? "locked" : "unlocked");
703        else if (_isLocked)
704            [self keybagDidLock];
705        else
706            [self keybagDidUnlock];
707    }
708}
709
710- (void) keybagDidLock
711{
712    secnotice("event", "%@", self);
713}
714
715- (void) keybagDidUnlock
716{
717    secnotice("event", "%@", self);
718    // First send changed keys to securityd so it can proccess updates
719    NSSet *filteredKeys = [self filterKeySetWithLockState:nil];
720    [self processKeyChangedEvent:[self copyChangedValues:filteredKeys]];
721
722    // Then, tickle securityd to perform a sync if needed.
723    if (_syncWithPeersPending && !_syncTimerScheduled) {
724        [self doSyncWithAllPeers];
725    }
726}
727
728- (void) kvsStoreChange {
729    if (!_seenKVSStoreChange) {
730        _seenKVSStoreChange = YES; // Only do this once
731        secnotice("event", "%@ received darwin notification before first NSNotification", self);
732        // TODO This might not be needed if we always get the NSNotification
733        // deleived even if we were launched due to a kvsStoreChange
734        // Send all keys for current lock state to securityd so it can proccess them
735        NSSet *filteredKeys = [self filterKeySetWithLockState:[self copyAllKeys]];
736        [self processKeyChangedEvent:[self copyChangedValues:filteredKeys]];
737    } else {
738        secdebug("event", "%@ ignored, waiting for NSNotification", self);
739    }
740}
741
742//
743// MARK: ----- Key Filtering -----
744//
745
746- (NSSet*) keysForCurrentLockState
747{
748    secdebug("keytrace", "%@ Filtering: unlockedSinceBoot: %d\n unlocked: %d\n, _keysOfInterest: %@", self, (int) _unlockedSinceBoot, (int) !_isLocked, [self exportKeyInterests]);
749
750    NSMutableSet *currentStateKeys = [NSMutableSet setWithSet: _alwaysKeys];
751    if (_unlockedSinceBoot)
752        [currentStateKeys unionSet: _firstUnlockKeys];
753
754    if (!_isLocked)
755        [currentStateKeys unionSet: _unlockedKeys];
756
757    return currentStateKeys;
758}
759
760- (NSSet*) filterKeySetWithLockState: (NSSet*) startingSet
761{
762    NSSet* availableKeys = [self keysForCurrentLockState];
763    NSSet *allKeys = [self copyAllKeys];
764    if (_inCallout) {
765        [_shadowPendingKeys unionSet:startingSet];
766        [_shadowPendingKeys intersectSet:allKeys];
767    }
768    [_pendingKeys unionSet:startingSet];
769    [_pendingKeys intersectSet:allKeys];
770
771    NSMutableSet *filtered =[_pendingKeys mutableCopy];
772    [filtered intersectSet:availableKeys];
773
774    return filtered;
775}
776
777- (NSMutableDictionary *)copyChangedValues:(NSSet*)keysOfInterest
778{
779    // Grab values from KVS.
780    NSUbiquitousKeyValueStore *store = [self cloudStore];
781    NSMutableDictionary *changedValues = [NSMutableDictionary dictionaryWithCapacity:0];
782    [keysOfInterest enumerateObjectsUsingBlock:^(id obj, BOOL *stop)
783    {
784        NSString* key = (NSString*) obj;
785        id objval = [store objectForKey:key];
786        if (!objval) objval = [NSNull null];
787
788        [changedValues setObject:objval forKey:key];
789        secdebug(XPROXYSCOPE, "%@ storeChanged updated value for %@", self, key);
790    }];
791    return changedValues;
792}
793
794/*
795    During RegisterKeys, separate keys-of-interest into three disjoint sets:
796    - keys that we always want to be notified about; this means we can get the
797        value at any time
798    - keys that require the device to have been unlocked at least once
799    - keys that require the device to be unlocked now
800
801    Typically, the sets of keys will be:
802
803    - Dk: alwaysKeys
804    - Ck: firstUnlock
805    - Ak: unlocked
806
807    The caller is responsible for making sure that the keys in e.g. alwaysKeys are
808    values that can be handled at any time (that is, not when unlocked)
809
810    Each time we get a notification from ubiquity that keys have changed, we need to
811    see if anything of interest changed. If we don't care, then done.
812
813    For each key-of-interest that changed, we either notify the client that things
814    changed, or add it to a pendingNotifications list. If the notification to the
815    client fails, also add it to the pendingNotifications list. This pending list
816    should be written to persistent storage and consulted any time we either get an
817    item changed notification, or get a stream event signalling a change in lock state.
818
819    We can notify the client either through XPC if a connection is set up, or call a
820    routine in securityd to launch it.
821
822*/
823
824- (void)processKeyChangedEvent:(NSDictionary *)changedValues
825{
826    NSMutableDictionary* filtered = [NSMutableDictionary dictionary];
827    NSMutableArray* nullKeys = [NSMutableArray array];
828    // Remove nulls because we don't want them in securityd.
829    [changedValues enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
830        if (obj == [NSNull null])
831            [nullKeys addObject:key];
832        else
833            filtered[key] = obj;
834    }];
835    if ([filtered count]) {
836        [self calloutWith:^(NSSet *pending, bool syncWithPeersPending, dispatch_queue_t queue, void(^done)(NSSet *, bool)) {
837            CFErrorRef error = NULL;
838            bool bx = [filtered count] && _SecKeychainSyncUpdate((__bridge CFDictionaryRef)filtered, &error);
839            // TODO: Make SecKeychainSyncUpdate return handled keys, for now we
840            // record that we handled everything when _SecKeychainSyncUpdate succeeds.
841            NSSet* handledKeys = [NSSet setWithArray: bx ? [changedValues allKeys] : nullKeys];
842            dispatch_async(queue, ^{
843                done(handledKeys, NO);
844                if (bx)
845                    secnotice("keytrace", "%@ handled: %@ null: %@ pending: %@", self,
846                              [[filtered allKeys] componentsJoinedByString: @" "],
847                              [nullKeys componentsJoinedByString: @" "],
848                              [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
849                else
850                    secerror("%@ failed: %@ not handled: %@ null: %@ pending: %@", self, error,
851                             [[filtered allKeys] componentsJoinedByString: @" "],
852                             [nullKeys componentsJoinedByString: @" "],
853                             [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
854                CFReleaseSafe(error);
855            });
856        }];
857    } else {
858        if ([nullKeys count])
859            [_pendingKeys minusSet: [NSSet setWithArray: nullKeys]];
860
861        secnotice("keytrace", "%@ handled: %@ null: %@ pending: %@", self,
862                  [[filtered allKeys] componentsJoinedByString: @" "],
863                  [nullKeys componentsJoinedByString: @" "],
864                  [[_pendingKeys allObjects] componentsJoinedByString: @" "]);
865    }
866}
867
868@end
869
870