]> git.saurik.com Git - apple/security.git/commitdiff
Security-58286.41.2.tar.gz macos-10133 v58286.41.2
authorApple <opensource@apple.com>
Sun, 4 Feb 2018 13:56:53 +0000 (13:56 +0000)
committerApple <opensource@apple.com>
Sun, 4 Feb 2018 13:56:53 +0000 (13:56 +0000)
119 files changed:
OSX/authd/engine.c
OSX/sec/SOSCircle/SecureObjectSync/SOSAccountTrustClassic+Identity.m
OSX/sec/SOSCircle/SecureObjectSync/SOSExports.exp-in
OSX/sec/SOSCircle/SecureObjectSync/SOSFullPeerInfo.h
OSX/sec/SOSCircle/SecureObjectSync/SOSFullPeerInfo.m
OSX/sec/Security/SecCertificate.c
OSX/sec/Security/SecCertificateInternal.h
OSX/sec/Security/SecExports.exp-in
OSX/sec/Security/SecFramework.c
OSX/sec/ipc/server_xpc.m
OSX/sec/securityd/SecItemDb.c
OSX/sec/securityd/nameconstraints.c
Security.xcodeproj/project.pbxproj
keychain/ckks/CKKS.h
keychain/ckks/CKKS.m
keychain/ckks/CKKSAPSReceiver.h
keychain/ckks/CKKSAPSReceiver.m
keychain/ckks/CKKSCKAccountStateTracker.h
keychain/ckks/CKKSCKAccountStateTracker.m
keychain/ckks/CKKSCondition.h
keychain/ckks/CKKSCondition.m
keychain/ckks/CKKSControl.h
keychain/ckks/CKKSControlProtocol.h
keychain/ckks/CKKSCurrentItemPointer.h
keychain/ckks/CKKSCurrentKeyPointer.h
keychain/ckks/CKKSDeviceStateEntry.h
keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.h
keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.m
keychain/ckks/CKKSFixups.h
keychain/ckks/CKKSFixups.m
keychain/ckks/CKKSGroupOperation.h
keychain/ckks/CKKSGroupOperation.m
keychain/ckks/CKKSHealKeyHierarchyOperation.h
keychain/ckks/CKKSHealKeyHierarchyOperation.m
keychain/ckks/CKKSHealTLKSharesOperation.h
keychain/ckks/CKKSHealTLKSharesOperation.m
keychain/ckks/CKKSIncomingQueueEntry.h
keychain/ckks/CKKSIncomingQueueOperation.h
keychain/ckks/CKKSIncomingQueueOperation.m
keychain/ckks/CKKSItem.h
keychain/ckks/CKKSItem.m
keychain/ckks/CKKSItemEncrypter.h
keychain/ckks/CKKSKey.h
keychain/ckks/CKKSKey.m
keychain/ckks/CKKSKeychainView.h
keychain/ckks/CKKSKeychainView.m
keychain/ckks/CKKSLocalSynchronizeOperation.h [new file with mode: 0644]
keychain/ckks/CKKSLocalSynchronizeOperation.m [new file with mode: 0644]
keychain/ckks/CKKSLockStateTracker.h
keychain/ckks/CKKSManifest.h
keychain/ckks/CKKSManifest.m
keychain/ckks/CKKSManifestLeafRecord.h
keychain/ckks/CKKSMirrorEntry.h
keychain/ckks/CKKSNearFutureScheduler.h
keychain/ckks/CKKSNearFutureScheduler.m
keychain/ckks/CKKSNewTLKOperation.h
keychain/ckks/CKKSNewTLKOperation.m
keychain/ckks/CKKSNotifier.h
keychain/ckks/CKKSNotifier.m
keychain/ckks/CKKSOutgoingQueueEntry.h
keychain/ckks/CKKSOutgoingQueueOperation.h
keychain/ckks/CKKSOutgoingQueueOperation.m
keychain/ckks/CKKSPeer.h
keychain/ckks/CKKSProcessReceivedKeysOperation.m
keychain/ckks/CKKSRateLimiter.h
keychain/ckks/CKKSRateLimiter.m
keychain/ckks/CKKSRecordHolder.h
keychain/ckks/CKKSReencryptOutgoingItemsOperation.h
keychain/ckks/CKKSReencryptOutgoingItemsOperation.m
keychain/ckks/CKKSResultOperation.h
keychain/ckks/CKKSResultOperation.m
keychain/ckks/CKKSSIV.h
keychain/ckks/CKKSSQLDatabaseObject.h
keychain/ckks/CKKSSQLDatabaseObject.m
keychain/ckks/CKKSScanLocalItemsOperation.h
keychain/ckks/CKKSScanLocalItemsOperation.m
keychain/ckks/CKKSSynchronizeOperation.h
keychain/ckks/CKKSTLKShare.h
keychain/ckks/CKKSUpdateCurrentItemPointerOperation.h
keychain/ckks/CKKSUpdateCurrentItemPointerOperation.m
keychain/ckks/CKKSUpdateDeviceStateOperation.h
keychain/ckks/CKKSViewManager.h
keychain/ckks/CKKSViewManager.m
keychain/ckks/CKKSZone.h
keychain/ckks/CKKSZone.m
keychain/ckks/CKKSZoneChangeFetcher.h
keychain/ckks/CKKSZoneStateEntry.h
keychain/ckks/CloudKitCategories.h
keychain/ckks/CloudKitDependencies.h
keychain/ckks/NSOperationCategories.h
keychain/ckks/NSOperationCategories.m
keychain/ckks/RateLimiter.h
keychain/ckks/RateLimiter.m
keychain/ckks/tests/AutoreleaseTest.c [new file with mode: 0644]
keychain/ckks/tests/AutoreleaseTest.h [new file with mode: 0644]
keychain/ckks/tests/CKKSAESSIVEncryptionTests.m
keychain/ckks/tests/CKKSAPSReceiverTests.m
keychain/ckks/tests/CKKSCloudKitTests.m
keychain/ckks/tests/CKKSConditionTests.m
keychain/ckks/tests/CKKSLoggerTests.m
keychain/ckks/tests/CKKSManifestTests.m
keychain/ckks/tests/CKKSNearFutureSchedulerTests.m
keychain/ckks/tests/CKKSSOSTests.m
keychain/ckks/tests/CKKSSQLTests.m
keychain/ckks/tests/CKKSServerValidationRecoveryTests.m
keychain/ckks/tests/CKKSTLKSharingEncryptionTests.m
keychain/ckks/tests/CKKSTLKSharingTests.m
keychain/ckks/tests/CKKSTests+API.h
keychain/ckks/tests/CKKSTests+API.m
keychain/ckks/tests/CKKSTests+CurrentPointerAPI.m
keychain/ckks/tests/CKKSTests.h
keychain/ckks/tests/CKKSTests.m
keychain/ckks/tests/CloudKitKeychainSyncingFixupTests.m
keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h
keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m
keychain/ckks/tests/CloudKitMockXCTest.h
keychain/ckks/tests/CloudKitMockXCTest.m
keychain/ckks/tests/MockCloudKit.h
keychain/ckks/tests/MockCloudKit.m

index 08780ce1d66490f8eff30e3d8bb517eaf52073a2..7991cff5e5b4b564f98994c97209dbc988696bca 100644 (file)
@@ -482,6 +482,12 @@ _evaluate_mechanisms(engine_t engine, CFArrayRef mechanisms)
                        if (engine->la_context) {
                                // sheet variant in progress
                                if (strcmp(mechanism_get_string(mech), "builtin:authenticate") == 0) {
+                                       // set the UID the same way as SecurityAgent would
+                                       if (auth_items_exist(engine->context, "sheet-uid")) {
+                                               os_log_debug(AUTHD_LOG, "engine: setting sheet UID %d to the context", auth_items_get_uint(engine->context, "sheet-uid"));
+                                               auth_items_set_uint(engine->context, "uid", auth_items_get_uint(engine->context, "sheet-uid"));
+                                       }
+
                                        // find out if sheet just provided credentials or did real authentication
                                        // if password is provided or PAM service name exists, it means authd has to evaluate credentials
                                        // otherwise we need to check la_result
@@ -1387,7 +1393,7 @@ OSStatus engine_authorize(engine_t engine, auth_rights_t rights, auth_items_t en
                auth_items_set_string(engine->context, kAuthorizationEnvironmentUsername, user);
                struct passwd *pwd = getpwnam(user);
                require(pwd, done);
-               auth_items_set_int(engine->context, AGENT_CONTEXT_UID, pwd->pw_uid);
+               auth_items_set_uint(engine->context, "sheet-uid", pwd->pw_uid);
 
                // move sheet-specific items from hints to context
                const char *service = auth_items_get_string(engine->hints, AGENT_CONTEXT_AP_PAM_SERVICE_NAME);
@@ -1804,7 +1810,7 @@ CFTypeRef engine_copy_context(engine_t engine, auth_items_t source)
 
 bool engine_acquire_sheet_data(engine_t engine)
 {
-       uid_t uid = auth_items_get_int(engine->context, AGENT_CONTEXT_UID);
+       uid_t uid = auth_items_get_int(engine->context, "sheet-uid");
        if (!uid)
                return false;
 
index 1851591929b764e5044d6eff2b5303ef01628ca5..6c8a90fe487f017797a89c3d9e8a8abb539fc249 100644 (file)
     NSString* octagonKeyName;
     SecKeyRef publicKey;
 
+    if (SOSFullPeerInfoHaveOctagonKeys(self.fullPeerInfo)) {
+        return;
+    }
+
     bool changedSelf = false;
 
     CFErrorRef copyError = NULL;
index 239c1b941240a19765b4570fd2d5fa46eced640a..1434f14d0e9f7f2c83c765dd11dbd224fd244cd2 100644 (file)
@@ -275,6 +275,10 @@ _SOSCircleRequestAdmission
 _SOSCircleAcceptRequest
 _SOSCircleHasPeer
 
+_SOSFullPeerInfoCopyOctagonSigningKey
+_SOSFullPeerInfoCopyOctagonEncryptionKey
+_SOSFullPeerInfoCopyOctagonPublicEncryptionKey
+_SOSFullPeerInfoCopyOctagonPublicSigningKey
 
 _SOSPiggyBackBlobCreateFromData
 _SOSPiggyBackBlobCopyEncodedData
index f6d269fbb42866f7bedddc75d2e732f937722422..d969cf453d285776b6c5dc0c1df719cee4c7a88d 100644 (file)
@@ -60,6 +60,7 @@ SecKeyRef SOSFullPeerInfoCopyOctagonPublicSigningKey(SOSFullPeerInfoRef fullPeer
 SecKeyRef SOSFullPeerInfoCopyOctagonPublicEncryptionKey(SOSFullPeerInfoRef fullPeer, CFErrorRef* error);
 SecKeyRef SOSFullPeerInfoCopyOctagonSigningKey(SOSFullPeerInfoRef fullPeer, CFErrorRef* error);
 SecKeyRef SOSFullPeerInfoCopyOctagonEncryptionKey(SOSFullPeerInfoRef fullPeer, CFErrorRef* error);
+bool      SOSFullPeerInfoHaveOctagonKeys(SOSFullPeerInfoRef fullPeer);
 
 bool SOSFullPeerInfoPurgePersistentKey(SOSFullPeerInfoRef peer, CFErrorRef* error);
 
index 1a5e5a65996fae03cf0e90dfef31e419b0e7ab78..d698f2d58546a947e3354d4fe68034e63f915ceb 100644 (file)
@@ -682,6 +682,19 @@ SecKeyRef SOSFullPeerInfoCopyOctagonEncryptionKey(SOSFullPeerInfoRef fullPeer, C
     return SOSFullPeerInfoCopyMatchingOctagonEncryptionPrivateKey(fullPeer, error);
 }
 
+bool SOSFullPeerInfoHaveOctagonKeys(SOSFullPeerInfoRef fullPeer)
+{
+    SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fullPeer);
+    if (pi == NULL) {
+        return false;
+    }
+
+    return
+        SOSPeerInfoHasOctagonSigningPubKey(pi) &&
+        SOSPeerInfoHasOctagonEncryptionPubKey(pi);
+}
+
+
 //
 // MARK: Encode and decode
 //
index 97e853b17c9a33877c21979aae9371a0edd2d2aa..007c8008d8b49a005afb457675d99fe6ca24b994 100644 (file)
@@ -4317,6 +4317,59 @@ const DERItem * SecCertificateGetSubjectAltName(SecCertificateRef certificate) {
     return &certificate->_subjectAltName->extnValue;
 }
 
+static bool convertIPAddress(CFStringRef name, CFDataRef *dataIP) {
+    /* IPv4: 4 octets in decimal separated by dots. We don't support matching IPv6 already. */
+    bool result = false;
+    /* Check size */
+    if (CFStringGetLength(name) < 7 || /* min size is #.#.#.# */
+        CFStringGetLength(name) > 15) { /* max size is ###.###.###.### */
+        return false;
+    }
+
+    CFCharacterSetRef decimals = CFCharacterSetCreateWithCharactersInString(NULL, CFSTR("0123456789."));
+    CFCharacterSetRef nonDecimals = CFCharacterSetCreateInvertedSet(NULL, decimals);
+    CFMutableDataRef data = CFDataCreateMutable(NULL, 0);
+    CFArrayRef parts = CFStringCreateArrayBySeparatingStrings(NULL, name, CFSTR("."));
+
+    /* Check character set */
+    if (CFStringFindCharacterFromSet(name, nonDecimals,
+                                      CFRangeMake(0, CFStringGetLength(name)),
+                                      kCFCompareForcedOrdering, NULL)) {
+        goto out;
+    }
+
+    /* Check number of labels */
+    if (CFArrayGetCount(parts) != 4) {
+        goto out;
+    }
+
+    /* Check each label and convert */
+    CFIndex i, count = CFArrayGetCount(parts);
+    for (i = 0; i < count; i++) {
+        CFStringRef octet = CFArrayGetValueAtIndex(parts, i);
+        char *cString = CFStringToCString(octet);
+        uint32_t value = atoi(cString);
+        free(cString);
+        if (value > 255) {
+            goto out;
+        } else {
+            uint8_t byte = value;
+            CFDataAppendBytes(data, &byte, 1);
+        }
+    }
+    result = true;
+    if (dataIP) {
+        *dataIP = CFRetain(data);
+    }
+
+out:
+    CFReleaseNull(data);
+    CFReleaseNull(parts);
+    CFReleaseNull(decimals);
+    CFReleaseNull(nonDecimals);
+    return result;
+}
+
 static OSStatus appendIPAddressesFromGeneralNames(void *context,
        SecCEGeneralNameType gnType, const DERItem *generalName) {
        CFMutableArrayRef ipAddresses = (CFMutableArrayRef)context;
@@ -4349,6 +4402,38 @@ CFArrayRef SecCertificateCopyIPAddresses(SecCertificateRef certificate) {
        return ipAddresses;
 }
 
+static OSStatus appendIPAddressesFromX501Name(void *context, const DERItem *type,
+                                              const DERItem *value, CFIndex rdnIX) {
+    CFMutableArrayRef addrs = (CFMutableArrayRef)context;
+    if (DEROidCompare(type, &oidCommonName)) {
+        CFStringRef string = copyDERThingDescription(kCFAllocatorDefault,
+                                                     value, true);
+        if (string) {
+            CFDataRef data = NULL;
+            if (convertIPAddress(string, &data)) {
+                CFArrayAppendValue(addrs, data);
+                CFReleaseNull(data);
+            }
+            CFRelease(string);
+        } else {
+            return errSecInvalidCertificate;
+        }
+    }
+    return errSecSuccess;
+}
+
+CFArrayRef SecCertificateCopyIPAddressesFromSubject(SecCertificateRef certificate) {
+    CFMutableArrayRef addrs = CFArrayCreateMutable(kCFAllocatorDefault,
+                                                      0, &kCFTypeArrayCallBacks);
+    OSStatus status = parseX501NameContent(&certificate->_subject, addrs,
+                                           appendIPAddressesFromX501Name);
+    if (status || CFArrayGetCount(addrs) == 0) {
+        CFReleaseNull(addrs);
+        return NULL;
+    }
+    return addrs;
+}
+
 static OSStatus appendDNSNamesFromGeneralNames(void *context, SecCEGeneralNameType gnType,
        const DERItem *generalName) {
        CFMutableArrayRef dnsNames = (CFMutableArrayRef)context;
@@ -4468,6 +4553,32 @@ static OSStatus appendDNSNamesFromX501Name(void *context, const DERItem *type,
        return errSecSuccess;
 }
 
+CFArrayRef SecCertificateCopyDNSNamesFromSubject(SecCertificateRef certificate) {
+    CFMutableArrayRef dnsNames = CFArrayCreateMutable(kCFAllocatorDefault,
+                                                      0, &kCFTypeArrayCallBacks);
+    OSStatus status = parseX501NameContent(&certificate->_subject, dnsNames,
+                                          appendDNSNamesFromX501Name);
+    if (status || CFArrayGetCount(dnsNames) == 0) {
+        CFReleaseNull(dnsNames);
+        return NULL;
+    }
+
+    /* appendDNSNamesFromX501Name allows IP addresses, we don't want those for this function */
+    __block CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
+    CFArrayForEach(dnsNames, ^(const void *value) {
+        CFStringRef name = (CFStringRef)value;
+        if (!convertIPAddress(name, NULL)) {
+            CFArrayAppendValue(result, name);
+        }
+    });
+    CFReleaseNull(dnsNames);
+    if (CFArrayGetCount(result) == 0) {
+        CFReleaseNull(result);
+    }
+
+    return result;
+}
+
 /* Not everything returned by this function is going to be a proper DNS name,
    we also return the certificates common name entries from the subject,
    assuming they look like dns names as specified in RFC 1035. */
@@ -4568,6 +4679,18 @@ OSStatus SecCertificateCopyEmailAddresses(SecCertificateRef certificate, CFArray
     return errSecSuccess;
 }
 
+CFArrayRef SecCertificateCopyRFC822NamesFromSubject(SecCertificateRef certificate) {
+    CFMutableArrayRef rfc822Names = CFArrayCreateMutable(kCFAllocatorDefault,
+                                                         0, &kCFTypeArrayCallBacks);
+    OSStatus status = parseX501NameContent(&certificate->_subject, rfc822Names,
+                                      appendRFC822NamesFromX501Name);
+    if (status || CFArrayGetCount(rfc822Names) == 0) {
+        CFRelease(rfc822Names);
+        rfc822Names = NULL;
+    }
+    return rfc822Names;
+}
+
 static OSStatus appendCommonNamesFromX501Name(void *context,
     const DERItem *type, const DERItem *value, CFIndex rdnIX) {
        CFMutableArrayRef commonNames = (CFMutableArrayRef)context;
index 948d523ca72e38724a6504b1e583ee322f0302a2..9bb87ba30bf64ff1a5716b808bb59a490831531d 100644 (file)
@@ -181,6 +181,10 @@ bool SecCertificateIsOidString(CFStringRef oid);
 
 DERItem *SecCertificateGetExtensionValue(SecCertificateRef certificate, CFTypeRef oid);
 
+CFArrayRef SecCertificateCopyDNSNamesFromSubject(SecCertificateRef certificate);
+CFArrayRef SecCertificateCopyIPAddressesFromSubject(SecCertificateRef certificate);
+CFArrayRef SecCertificateCopyRFC822NamesFromSubject(SecCertificateRef certificate);
+
 __END_DECLS
 
 #endif /* !_SECURITY_SECCERTIFICATEINTERNAL_H_ */
index d0ea3926258b619404e6555903bc131a3168e4ab..af8c6c758ca52cd9037089bda6d643f27674698d 100644 (file)
@@ -506,12 +506,14 @@ _SecCertificateCopyCommonNames
 _SecCertificateCopyCompanyName
 _SecCertificateCopyCountry
 _SecCertificateCopyDNSNames
+_SecCertificateCopyDNSNamesFromSubject
 _SecCertificateCopyData
 _SecCertificateCopyEmailAddresses
 _SecCertificateCopyEscrowRoots
 _SecCertificateCopyExtendedKeyUsage
 _SecCertificateCopyiAPAuthCapabilities
 _SecCertificateCopyIPAddresses
+_SecCertificateCopyIPAddressesFromSubject
 _SecCertificateCopyiPhoneDeviceCAChain
 _SecCertificateCopyIssuerSHA1Digest
 _SecCertificateCopyIssuerSequence
@@ -528,6 +530,7 @@ _SecCertificateCopyProperties
 _SecCertificateCopyPublicKey
 _SecCertificateCopyPublicKeySHA1Digest
 _SecCertificateCopyRFC822Names
+_SecCertificateCopyRFC822NamesFromSubject
 _SecCertificateCopySerialNumber
 _SecCertificateCopySerialNumberData
 _SecCertificateCopySHA256Digest
index 3a895015ee5ce9ea0d1a0435e03c59f4667ef06b..83d7782cc2b6f6194be7be1863d72e588eb1f52b 100644 (file)
 
 
 /* Security.framework's bundle id. */
+#if TARGET_OS_IPHONE
 static CFStringRef kSecFrameworkBundleID = CFSTR("com.apple.Security");
+#else
+static CFStringRef kSecFrameworkBundleID = CFSTR("com.apple.security");
+#endif
 
 CFGiblisGetSingleton(CFBundleRef, SecFrameworkGetBundle, bundle,  ^{
     *bundle = CFRetainSafe(CFBundleGetBundleWithIdentifier(kSecFrameworkBundleID));
index b4cf801998005a9842b7ddec06fddc484a8b1456..1c7a42cdbe5535b5fef74af9d95ce370c34525c3 100644 (file)
     __block SecDbItemRef oldItem = NULL;
 
     bool ok = kc_with_dbt(false, &cferror, ^bool (SecDbConnectionRef dbt) {
-        Query *q = query_create_with_limit( (__bridge CFDictionaryRef) @{
-                                                                         (__bridge NSString *)kSecValuePersistentRef : newItemPersistentRef,
-                                                                         (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
-                                                                         },
-                                           NULL,
-                                           1,
-                                           &cferror);
-        if(cferror) {
-            secerror("couldn't create query for new item pref: %@", cferror);
-            return false;
-        }
-
-        if(!SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
-            newItem = CFRetainSafe(item);
-        })) {
-            query_destroy(q, NULL);
-            secerror("couldn't run query for new item pref: %@", cferror);
-            return false;
-        }
-
-        if(!query_destroy(q, &cferror)) {
-            secerror("couldn't destroy query for new item pref: %@", cferror);
-            return false;
-        };
-
-        if(oldCurrentItemPersistentRef) {
-            q = query_create_with_limit( (__bridge CFDictionaryRef) @{
-                                                                      (__bridge NSString *)kSecValuePersistentRef : oldCurrentItemPersistentRef,
-                                                                      (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
-                                                                      },
-                                        NULL,
-                                        1,
-                                        &cferror);
+        // Use a DB transaction to gain synchronization with all CKKS zones.
+        return kc_transaction_type(dbt, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, ^bool {
+            Query *q = query_create_with_limit( (__bridge CFDictionaryRef) @{
+                                                                             (__bridge NSString *)kSecValuePersistentRef : newItemPersistentRef,
+                                                                             (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
+                                                                             },
+                                               NULL,
+                                               1,
+                                               &cferror);
             if(cferror) {
-                secerror("couldn't create query: %@", cferror);
+                secerror("couldn't create query for new item pref: %@", cferror);
                 return false;
             }
 
             if(!SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
-                oldItem = CFRetainSafe(item);
+                newItem = CFRetainSafe(item);
             })) {
                 query_destroy(q, NULL);
-                secerror("couldn't run query for old item pref: %@", cferror);
+                secerror("couldn't run query for new item pref: %@", cferror);
                 return false;
             }
 
             if(!query_destroy(q, &cferror)) {
-                secerror("couldn't destroy query for old item pref: %@", cferror);
+                secerror("couldn't destroy query for new item pref: %@", cferror);
                 return false;
             };
-        }
 
-        CKKSViewManager* manager = [CKKSViewManager manager];
-        if(!manager) {
-            secerror("SecItemSetCurrentItemAcrossAllDevices: no view manager?");
-            cferror = (CFErrorRef) CFBridgingRetain([NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"No view manager, cannot forward request"}]);
-            return false;
-        }
-        [manager setCurrentItemForAccessGroup:newItem
-                                         hash:newItemSHA1
-                                  accessGroup:accessGroup
-                                   identifier:identifier
-                                     viewHint:viewHint
-                                    replacing:oldItem
-                                         hash:oldItemSHA1
-                                     complete:complete];
-        return true;
+            if(oldCurrentItemPersistentRef) {
+                q = query_create_with_limit( (__bridge CFDictionaryRef) @{
+                                                                          (__bridge NSString *)kSecValuePersistentRef : oldCurrentItemPersistentRef,
+                                                                          (__bridge NSString *)kSecAttrAccessGroup : accessGroup,
+                                                                          },
+                                            NULL,
+                                            1,
+                                            &cferror);
+                if(cferror) {
+                    secerror("couldn't create query: %@", cferror);
+                    return false;
+                }
+
+                if(!SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
+                    oldItem = CFRetainSafe(item);
+                })) {
+                    query_destroy(q, NULL);
+                    secerror("couldn't run query for old item pref: %@", cferror);
+                    return false;
+                }
+
+                if(!query_destroy(q, &cferror)) {
+                    secerror("couldn't destroy query for old item pref: %@", cferror);
+                    return false;
+                };
+            }
+
+            CKKSViewManager* manager = [CKKSViewManager manager];
+            if(!manager) {
+                secerror("SecItemSetCurrentItemAcrossAllDevices: no view manager?");
+                cferror = (CFErrorRef) CFBridgingRetain([NSError errorWithDomain:@"securityd" code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"No view manager, cannot forward request"}]);
+                return false;
+            }
+            [manager setCurrentItemForAccessGroup:newItem
+                                             hash:newItemSHA1
+                                      accessGroup:accessGroup
+                                       identifier:identifier
+                                         viewHint:viewHint
+                                        replacing:oldItem
+                                             hash:oldItemSHA1
+                                         complete:complete];
+            return true;
+        });
     });
 
     CFReleaseNull(newItem);
index 8a30763c5a282c017a0a155de19d470d1987a373..5ebca2b13e4ef00cefde72090b5c347806bf48df 100644 (file)
@@ -1899,6 +1899,10 @@ bool SecServerImportKeychainInPlist(SecDbConnectionRef dbt, SecurityClient *clie
         ok = false;
     }
 
+    // If CKKS had spun up, it's very likely that we just deleted its data.
+    // Tell it to perform a local resync.
+    SecCKKSPerformLocalResync();
+
 errOut:
     CFReleaseSafe(sys_bound);
     CFReleaseSafe(keybaguuid);
index 11755234f16505de7492ea49e0ef6f9f9af0b6b9..e841a7aefd6f6cdb141bc3ddcddaf8602622e3a2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015 Apple Inc. All Rights Reserved.
+ * Copyright (c) 2015-2017 Apple Inc. All Rights Reserved.
  *
  * @APPLE_LICENSE_HEADER_START@
  *
@@ -283,6 +283,7 @@ typedef struct {
 typedef struct {
     const CFArrayRef subtrees;
     match_t *match;
+    bool permit;
 } nc_san_match_context_t;
 
 static OSStatus nc_compare_subtree(void *context, SecCEGeneralNameType gnType, const DERItem *generalName) {
@@ -329,7 +330,7 @@ static OSStatus nc_compare_subtree(void *context, SecCEGeneralNameType gnType, c
 static void nc_decode_and_compare_subtree(const void *value, void *context) {
     CFDataRef subtree = value;
     nc_match_context_t *match_context = context;
-    if(subtree) {
+    if (subtree) {
         /* convert subtree to DERItem */
         const DERItem general_name = { (unsigned char *)CFDataGetBytePtr(subtree), CFDataGetLength(subtree) };
         DERDecodedInfo general_name_content;
@@ -359,21 +360,72 @@ out:
     return true;
 }
 
-static void nc_compare_subject_to_subtrees(CFDataRef subject, CFArrayRef subtrees, match_t *match) {
-    /* An empty subject name is considered not present */
-    if (isEmptySubject(subject)) {
+/*
+ * We update match structs as follows:
+ * 'present' is true if there's any subtree of the same type as any Subject/SAN
+ * 'match' is false if the subtree(s) and Subject(s)/SAN(s) don't match.
+ * Note: the state of 'match' is meaningless without 'present' also being true.
+ */
+static void update_match(bool permit, match_t *input_match, match_t *output_match) {
+    if (!input_match || !output_match) {
         return;
     }
-    
-    CFIndex num_trees = CFArrayGetCount(subtrees);
-    CFRange range = { 0, num_trees };
-    const DERItem subject_der = { (unsigned char *)CFDataGetBytePtr(subject), CFDataGetLength(subject) };
-    nc_match_context_t context = {GNT_DirectoryName, &subject_der, match};
-    CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &context);
+    if (input_match->present) {
+        output_match->present = true;
+        if (permit) {
+            output_match->isMatch &= input_match->isMatch;
+        } else {
+            output_match->isMatch |= input_match->isMatch;
+        }
+    }
+}
+
+static void nc_compare_DNSName_to_subtrees(const void *value, void *context) {
+    CFStringRef dnsName = (CFStringRef)value;
+    char *dnsNameString = NULL;
+    nc_san_match_context_t *san_context = context;
+    CFArrayRef subtrees = NULL;
+    if (san_context) {
+        subtrees = san_context->subtrees;
+    }
+    if (subtrees) {
+        CFIndex num_trees = CFArrayGetCount(subtrees);
+        CFRange range = { 0, num_trees };
+        match_t match = { false, false };
+        dnsNameString = CFStringToCString(dnsName);
+        if (!dnsNameString) { return; }
+        const DERItem name = { (unsigned char *)dnsNameString,
+                                CFStringGetLength(dnsName) };
+        nc_match_context_t match_context = {GNT_DNSName, &name, &match};
+        CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
+        free(dnsNameString);
+
+        update_match(san_context->permit, &match, san_context->match);
+    }
+}
+
+static void nc_compare_IPAddress_to_subtrees(const void *value, void *context) {
+    CFDataRef ipAddr = (CFDataRef)value;
+    nc_san_match_context_t *san_context = context;
+    CFArrayRef subtrees = NULL;
+    if (san_context) {
+        subtrees = san_context->subtrees;
+    }
+    if (subtrees) {
+        CFIndex num_trees = CFArrayGetCount(subtrees);
+        CFRange range = { 0, num_trees };
+        match_t match = { false, false };
+        const DERItem addr = { (unsigned char *)CFDataGetBytePtr(ipAddr), CFDataGetLength(ipAddr) };
+        nc_match_context_t match_context = {GNT_IPAddress, &addr, &match};
+        CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
+
+        update_match(san_context->permit, &match, san_context->match);
+    }
 }
 
 static void nc_compare_RFC822Name_to_subtrees(const void *value, void *context) {
-    CFStringRef rfc822Name = value;
+    CFStringRef rfc822Name = (CFStringRef)value;
+    char *rfc822NameString = NULL;
     nc_san_match_context_t *san_context = context;
     CFArrayRef subtrees = NULL;
     if (san_context) {
@@ -383,23 +435,70 @@ static void nc_compare_RFC822Name_to_subtrees(const void *value, void *context)
         CFIndex num_trees = CFArrayGetCount(subtrees);
         CFRange range = { 0, num_trees };
         match_t match = { false, false };
-        const DERItem addr = { (unsigned char *)CFStringGetCStringPtr(rfc822Name, kCFStringEncodingUTF8),
+        rfc822NameString = CFStringToCString(rfc822Name);
+        if (!rfc822NameString) { return; }
+        const DERItem addr = { (unsigned char *)rfc822NameString,
                               CFStringGetLength(rfc822Name) };
         nc_match_context_t match_context = {GNT_RFC822Name, &addr, &match};
         CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
-        
-        /*
-         * We set the SAN context match struct as follows:
-         * 'present' is true if there's any subtree of the same type as any SAN
-         * 'match' is false if the present type(s) is/are not supported or the subtree(s) and SAN(s) don't match.
-         * Note: the state of 'match' is meaningless without 'present' also being true.
-         */
-        if (match.present && san_context->match) {
-            san_context->match->present = true;
-            san_context->match->isMatch &= match.isMatch;
-        }
+        free(rfc822NameString);
+
+        update_match(san_context->permit, &match, san_context->match);
+
+    }
+}
+
+static void nc_compare_subject_to_subtrees(SecCertificateRef certificate, CFArrayRef subtrees,
+                                           bool permit, match_t *match) {
+    CFDataRef subject = SecCertificateCopySubjectSequence(certificate);
+    /* An empty subject name is considered not present */
+    if (!subject || isEmptySubject(subject)) {
+        CFReleaseNull(subject);
+        return;
     }
 
+    /* Compare X.500 distinguished name constraints */
+    CFIndex num_trees = CFArrayGetCount(subtrees);
+    CFRange range = { 0, num_trees };
+    match_t x500_match = { false, false };
+    const DERItem subject_der = { (unsigned char *)CFDataGetBytePtr(subject), CFDataGetLength(subject) };
+    nc_match_context_t context = {GNT_DirectoryName, &subject_der, &x500_match};
+    CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &context);
+    CFReleaseNull(subject);
+    update_match(permit, &x500_match, match);
+
+    /* Compare DNSName constraints */
+    match_t dns_match = { false, permit };
+    CFArrayRef dnsNames = SecCertificateCopyDNSNamesFromSubject(certificate);
+    if (dnsNames) {
+        CFRange dnsRange = { 0, CFArrayGetCount(dnsNames) };
+        nc_san_match_context_t dnsContext = { subtrees, &dns_match, permit };
+        CFArrayApplyFunction(dnsNames, dnsRange, nc_compare_DNSName_to_subtrees, &dnsContext);
+    }
+    CFReleaseNull(dnsNames);
+    update_match(permit, &dns_match, match);
+
+    /* Compare IPAddresss constraints */
+    match_t ip_match = { false, permit };
+    CFArrayRef ipAddresses = SecCertificateCopyIPAddressesFromSubject(certificate);
+    if (ipAddresses) {
+        CFRange ipRange = { 0, CFArrayGetCount(ipAddresses) };
+        nc_san_match_context_t ipContext = { subtrees, &ip_match, permit };
+        CFArrayApplyFunction(ipAddresses, ipRange, nc_compare_IPAddress_to_subtrees, &ipContext);
+    }
+    CFReleaseNull(ipAddresses);
+    update_match(permit, &ip_match, match);
+
+    /* Compare RFC822 constraints to subject email address */
+    match_t email_match = { false, permit };
+    CFArrayRef rfc822Names = SecCertificateCopyRFC822NamesFromSubject(certificate);
+    if (rfc822Names) {
+        CFRange emailRange = { 0, CFArrayGetCount(rfc822Names) };
+        nc_san_match_context_t emailContext = { subtrees, &email_match, permit };
+        CFArrayApplyFunction(rfc822Names, emailRange, nc_compare_RFC822Name_to_subtrees, &emailContext);
+    }
+    CFReleaseNull(rfc822Names);
+    update_match(permit, &email_match, match);
 }
 
 static OSStatus nc_compare_subjectAltName_to_subtrees(void *context, SecCEGeneralNameType gnType, const DERItem *generalName) {
@@ -414,17 +513,8 @@ static OSStatus nc_compare_subjectAltName_to_subtrees(void *context, SecCEGenera
         match_t match = { false, false };
         nc_match_context_t match_context = {gnType, generalName, &match};
         CFArrayApplyFunction(subtrees, range, nc_decode_and_compare_subtree, &match_context);
-        
-        /*
-         * We set the SAN context match struct as follows:
-         * 'present' is true if there's any subtree of the same type as any SAN
-         * 'match' is false if the present type(s) is/are not supported or the subtree(s) and SAN(s) don't match.
-         * Note: the state of 'match' is meaningless without 'present' also being true.
-         */
-        if (match.present && san_context->match) {
-            san_context->match->present = true;
-            san_context->match->isMatch &= match.isMatch;
-        }
+
+        update_match(san_context->permit, &match, san_context->match);
         
         return errSecSuccess;
     }
@@ -435,7 +525,6 @@ static OSStatus nc_compare_subjectAltName_to_subtrees(void *context, SecCEGenera
 OSStatus SecNameContraintsMatchSubtrees(SecCertificateRef certificate, CFArrayRef subtrees, bool *matched, bool permit) {
     CFDataRef subject = NULL;
     OSStatus status = errSecSuccess;
-    CFArrayRef rfc822Names = NULL;
     
     require_action_quiet(subject = SecCertificateCopySubjectSequence(certificate),
                          out,
@@ -445,25 +534,20 @@ OSStatus SecNameContraintsMatchSubtrees(SecCertificateRef certificate, CFArrayRe
     /* Reject certificates with neither Subject Name nor SubjectAltName */
     require_action_quiet(!isEmptySubject(subject) || subjectAltNames, out, status = errSecInvalidCertificate);
     
-    /* Verify that the subject name is within any of the subtrees for X.500 distinguished names */
-    match_t subject_match = { false, false };
-    nc_compare_subject_to_subtrees(subject,subtrees,&subject_match);
-
-    match_t san_match = { false, true };
-    nc_san_match_context_t san_context = {subtrees, &san_match};
+    /* Verify that the subject name is within all of the subtrees */
+    match_t subject_match = { false, permit };
+    nc_compare_subject_to_subtrees(certificate, subtrees, permit, &subject_match);
+
+    /* permit tells us whether to start with true or false. If we are looking at permitted
+     * subtrees, we are going to "and" the matching results because all present types must match
+     * to permit. For excluded subtrees, we are going to "or" the matching results because
+     * any matching present types causes exclusion. */
+    match_t san_match = { false, permit };
+    nc_san_match_context_t san_context = {subtrees, &san_match, permit};
     
-    /* If there are no subjectAltNames, then determine if there's a matching emailAddress in the Subject */
-    if (!subjectAltNames) {
-        rfc822Names = SecCertificateCopyRFC822Names(certificate);
-        /* If there's also no emailAddress field then subject match is enough. */
-        if (rfc822Names) {
-            CFRange range = { 0 , CFArrayGetCount(rfc822Names) };
-            CFArrayApplyFunction(rfc822Names, range, nc_compare_RFC822Name_to_subtrees, &san_context);
-        }
-    }
-    else {
-        /* And verify that each of the alternative names in the subjectAltName extension (critical or non-critical)
-         * is within any of the subtrees for that name type. */
+    /* And verify that each of the alternative names in the subjectAltName extension (critical or non-critical)
+     * is within any of the subtrees for that name type. */
+    if (subjectAltNames) {
         status = SecCertificateParseGeneralNames(subjectAltNames,
                                                  &san_context,
                                                  nc_compare_subjectAltName_to_subtrees);
@@ -500,7 +584,6 @@ OSStatus SecNameContraintsMatchSubtrees(SecCertificateRef certificate, CFArrayRe
     
 out:
     CFReleaseNull(subject);
-    CFReleaseNull(rfc822Names);
     return status;
 }
 
index 1195965e5919c6fddaa9a8dc05505abe659e0f04..d86582c7b4b3e6cc65556dd44fcc343eac0e727b 100644 (file)
                DC3C7AB91D838C8D00F6A832 /* oids.h in Headers */ = {isa = PBXBuildFile; fileRef = DC1785421D778A7400B50D50 /* oids.h */; settings = {ATTRIBUTES = (Private, ); }; };
                DC3C7ABA1D838C9F00F6A832 /* sslTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = DC1786FB1D778F3C00B50D50 /* sslTypes.h */; settings = {ATTRIBUTES = (Private, ); }; };
                DC3C7C901D83957F00F6A832 /* NSFileHandle+Formatting.m in Sources */ = {isa = PBXBuildFile; fileRef = E78A9AD91D34959200006B5B /* NSFileHandle+Formatting.m */; };
+               DC3D748C1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3D748A1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h */; };
+               DC3D748D1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3D748A1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h */; };
+               DC3D748E1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3D748B1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m */; };
+               DC3D748F1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = DC3D748B1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m */; };
                DC4268F61E82036F002B7110 /* server_endpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6ACC401E81DF9400125DC5 /* server_endpoint.m */; };
                DC4268FC1E820370002B7110 /* server_endpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6ACC401E81DF9400125DC5 /* server_endpoint.m */; };
                DC4268FE1E820371002B7110 /* server_endpoint.m in Sources */ = {isa = PBXBuildFile; fileRef = DC6ACC401E81DF9400125DC5 /* server_endpoint.m */; };
                DCB344A51D8A35270054D16E /* si-20-sectrust-provisioning.h in Headers */ = {isa = PBXBuildFile; fileRef = DCB344701D8A35270054D16E /* si-20-sectrust-provisioning.h */; };
                DCB344A61D8A35270054D16E /* si-33-keychain-backup.c in Sources */ = {isa = PBXBuildFile; fileRef = DCB344711D8A35270054D16E /* si-33-keychain-backup.c */; };
                DCB344A71D8A35270054D16E /* si-34-one-true-keychain.c in Sources */ = {isa = PBXBuildFile; fileRef = DCB344721D8A35270054D16E /* si-34-one-true-keychain.c */; };
+               DCB502331FDA156B008F8E4F /* AutoreleaseTest.c in Sources */ = {isa = PBXBuildFile; fileRef = DCB5022C1FDA155D008F8E4F /* AutoreleaseTest.c */; };
                DCB515DE1ED3CF86001F1152 /* SecurityFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE4E7C01D7A463E00AFB96E /* SecurityFoundation.framework */; };
                DCB515DF1ED3CF95001F1152 /* SecurityFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE4E7C01D7A463E00AFB96E /* SecurityFoundation.framework */; };
                DCB515E01ED3D111001F1152 /* client.c in Sources */ = {isa = PBXBuildFile; fileRef = 7908507F0CA87CF00083CC4D /* client.c */; };
                DC3A4B601D91EAC500E46D4A /* com.apple.CodeSigningHelper.sb */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = com.apple.CodeSigningHelper.sb; sourceTree = "<group>"; };
                DC3A4B621D91EAC500E46D4A /* main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = main.cpp; sourceTree = "<group>"; };
                DC3A81D41D99D567000C7419 /* libcoretls_cfhelpers.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcoretls_cfhelpers.dylib; path = usr/lib/libcoretls_cfhelpers.dylib; sourceTree = SDKROOT; };
+               DC3D748A1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CKKSLocalSynchronizeOperation.h; sourceTree = "<group>"; };
+               DC3D748B1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CKKSLocalSynchronizeOperation.m; sourceTree = "<group>"; };
                DC4269031E82EDAC002B7110 /* SecItem.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SecItem.m; sourceTree = "<group>"; };
                DC4269061E82FBDF002B7110 /* server_security_helpers.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = server_security_helpers.c; sourceTree = "<group>"; };
                DC4269071E82FBDF002B7110 /* server_security_helpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = server_security_helpers.h; sourceTree = "<group>"; };
                DCB344701D8A35270054D16E /* si-20-sectrust-provisioning.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "si-20-sectrust-provisioning.h"; path = "regressions/si-20-sectrust-provisioning.h"; sourceTree = "<group>"; };
                DCB344711D8A35270054D16E /* si-33-keychain-backup.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "si-33-keychain-backup.c"; path = "regressions/si-33-keychain-backup.c"; sourceTree = "<group>"; };
                DCB344721D8A35270054D16E /* si-34-one-true-keychain.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "si-34-one-true-keychain.c"; path = "regressions/si-34-one-true-keychain.c"; sourceTree = "<group>"; };
+               DCB5022C1FDA155D008F8E4F /* AutoreleaseTest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = AutoreleaseTest.c; sourceTree = "<group>"; };
+               DCB502321FDA155E008F8E4F /* AutoreleaseTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoreleaseTest.h; sourceTree = "<group>"; };
                DCB5D9391E4A9A3400BE22AB /* CKKSSynchronizeOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKKSSynchronizeOperation.h; sourceTree = "<group>"; };
                DCB5D93A1E4A9A3400BE22AB /* CKKSSynchronizeOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKKSSynchronizeOperation.m; sourceTree = "<group>"; };
                DCBDB3B01E57C67500B61300 /* CKKSKeychainView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKKSKeychainView.h; sourceTree = "<group>"; };
                DC3502B61E0208BE00BC0587 /* Tests (Local) */ = {
                        isa = PBXGroup;
                        children = (
+                               DCB5022C1FDA155D008F8E4F /* AutoreleaseTest.c */,
+                               DCB502321FDA155E008F8E4F /* AutoreleaseTest.h */,
                                471024D91E79CB6D00844C09 /* CKKSTests.h */,
                                DC3502B71E0208BE00BC0587 /* CKKSTests.m */,
                                DC6593D21ED8DBCE00C19462 /* CKKSTests+API.h */,
                                DCFE1C501F1825F7007640C8 /* CKKSUpdateDeviceStateOperation.m */,
                                DCAD9B421F8D939C00C5E2AE /* CKKSFixups.h */,
                                DCAD9B431F8D939C00C5E2AE /* CKKSFixups.m */,
+                               DC3D748A1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h */,
+                               DC3D748B1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m */,
                        );
                        name = Operations;
                        sourceTree = "<group>";
                                DCAD9B451F8D939C00C5E2AE /* CKKSFixups.h in Headers */,
                                DC9C95B51F79CFD1000D19E5 /* CKKSControl.h in Headers */,
                                DC222C6F1E034D1F00B09171 /* SecKeybagSupport.h in Headers */,
+                               DC3D748D1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h in Headers */,
                                DC222C701E034D1F00B09171 /* iCloudTrace.h in Headers */,
                                DCEA5D861E2F14810089CF55 /* CKKSAPSReceiver.h in Headers */,
                                DC222C711E034D1F00B09171 /* CKKSOutgoingQueueEntry.h in Headers */,
                                DCAD9B441F8D939C00C5E2AE /* CKKSFixups.h in Headers */,
                                DC9C95B41F79CFD1000D19E5 /* CKKSControl.h in Headers */,
                                DC52E7EA1D80BE9500B0A59C /* SecItemSchema.h in Headers */,
+                               DC3D748C1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.h in Headers */,
                                DC52E7E91D80BE8D00B0A59C /* SecKeybagSupport.h in Headers */,
                                DCD662F51E329B6800188186 /* CKKSNewTLKOperation.h in Headers */,
                                DC52E7EB1D80BE9B00B0A59C /* iCloudTrace.h in Headers */,
                                DC222C431E034D1F00B09171 /* SecItemBackupServer.c in Sources */,
                                DCE278E01ED789EF0083B485 /* CKKSCurrentItemPointer.m in Sources */,
                                DC222C441E034D1F00B09171 /* SecItemDataSource.c in Sources */,
+                               DC3D748F1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m in Sources */,
                                526965D31E6E284500627F9D /* AsymKeybagBackup.m in Sources */,
                                DCFE1C541F1825F7007640C8 /* CKKSUpdateDeviceStateOperation.m in Sources */,
                                DCD6C4B51EC5302500414FEE /* CKKSNearFutureScheduler.m in Sources */,
                                4771ECD91F17CE5100840998 /* SFAnalyticsLogger.m in Sources */,
                                4771ECCE1F17CD2100840998 /* SFObjCType.m in Sources */,
                                4771ECCC1F17CD0E00840998 /* SFSQLite.m in Sources */,
+                               DCB502331FDA156B008F8E4F /* AutoreleaseTest.c in Sources */,
                                4771ECCD1F17CD0E00840998 /* SFSQLiteStatement.m in Sources */,
                                DCD6C4B71EC5319600414FEE /* CKKSNearFutureSchedulerTests.m in Sources */,
                                DC08D1C41E64FA8C006237DA /* CloudKitKeychainSyncingMockXCTest.m in Sources */,
                                6C588D801EAA20AB00D7E322 /* RateLimiter.m in Sources */,
                                DC15F7681E67A6F6003B9A40 /* CKKSHealKeyHierarchyOperation.m in Sources */,
                                DCE278DF1ED789EF0083B485 /* CKKSCurrentItemPointer.m in Sources */,
+                               DC3D748E1FD2217900AC57DA /* CKKSLocalSynchronizeOperation.m in Sources */,
                                DCA4D1FF1E552DD50056214F /* CKKSCurrentKeyPointer.m in Sources */,
                                DCFE1C531F1825F7007640C8 /* CKKSUpdateDeviceStateOperation.m in Sources */,
                                DCD6C4B41EC5302500414FEE /* CKKSNearFutureScheduler.m in Sources */,
index 95f1e88e3bc64d070cab1c32baac98cf8445e9c7..3b6a8545b9bfbc2f69f971a7d809894d7ca64434 100644 (file)
 #ifndef CKKS_h
 #define CKKS_h
 
+#include <dispatch/dispatch.h>
 #include <ipc/securityd_client.h>
-#include <utilities/SecDb.h>
 #include <utilities/SecCFWrappers.h>
-#include <dispatch/dispatch.h>
+#include <utilities/SecDb.h>
 #include <xpc/xpc.h>
 
 #ifdef __OBJC__
 #import <Foundation/Foundation.h>
+NS_ASSUME_NONNULL_BEGIN
+#else
+CF_ASSUME_NONNULL_BEGIN
+#endif
+
+#ifdef __OBJC__
 
 typedef NS_ENUM(NSUInteger, SecCKKSItemEncryptionVersion) {
     CKKSItemEncryptionVersionNone = 0,  // No encryption present
@@ -55,7 +61,7 @@ extern CKKSItemState* const SecCKKSStateUnauthenticated;
 extern CKKSItemState* const SecCKKSStateInFlight;
 extern CKKSItemState* const SecCKKSStateReencrypt;
 extern CKKSItemState* const SecCKKSStateError;
-extern CKKSItemState* const SecCKKSStateDeleted; // meta-state: please delete this item!
+extern CKKSItemState* const SecCKKSStateDeleted;  // meta-state: please delete this item!
 
 /* Processed States */
 @protocol SecCKKSProcessedState
@@ -94,8 +100,8 @@ extern NSString* const SecCKRecordServerWasCurrent;
 /* Intermediate Key CKRecord Keys */
 extern NSString* const SecCKRecordIntermediateKeyType;
 extern NSString* const SecCKRecordKeyClassKey;
-//extern NSString* const SecCKRecordWrappedKeyKey;
-//extern NSString* const SecCKRecordParentKeyRefKey;
+//extern NSString*  const SecCKRecordWrappedKeyKey;
+//extern NSString*  const SecCKRecordParentKeyRefKey;
 
 /* TLK Share CKRecord Keys */
 // These are a bit special; they can't use the record ID as information without parsing.
@@ -108,13 +114,13 @@ extern NSString* const SecCKRecordEpoch;
 extern NSString* const SecCKRecordPoisoned;
 extern NSString* const SecCKRecordSignature;
 extern NSString* const SecCKRecordVersion;
-//extern NSString* const SecCKRecordParentKeyRefKey; // reference to the key contained by this record
-//extern NSString* const SecCKRecordWrappedKeyKey;   // key material
+//extern NSString*  const SecCKRecordParentKeyRefKey; // reference to the key contained by this record
+//extern NSString*  const SecCKRecordWrappedKeyKey;   // key material
 
 /* Current Key CKRecord Keys */
 extern NSString* const SecCKRecordCurrentKeyType;
 // The key class will be the record name.
-//extern NSString* const SecCKRecordParentKeyRefKey; <-- represent the current key for this key class
+//extern NSString*  const SecCKRecordParentKeyRefKey; <-- represent the current key for this key class
 
 /* Current Item CKRecord Keys */
 extern NSString* const SecCKRecordCurrentItemType;
@@ -157,6 +163,9 @@ extern CKKSZoneKeyState* const SecCKKSZoneKeyStateInitializing;
 extern CKKSZoneKeyState* const SecCKKSZoneKeyStateInitialized;
 // Everything is ready and waiting for input.
 extern CKKSZoneKeyState* const SecCKKSZoneKeyStateReady;
+// We're presumably ready, but we'd like to do one or two more checks after we unlock.
+extern CKKSZoneKeyState* const SecCKKSZoneKeyStateReadyPendingUnlock;
+
 // A Fetch has just been completed which includes some new keys to process
 extern CKKSZoneKeyState* const SecCKKSZoneKeyStateFetchComplete;
 // We'd really like a full refetch.
@@ -207,7 +216,7 @@ extern NSString* const CKKSServerExtensionErrorDomain;
 #define SecCKKSOutgoingQueueItemsAtOnce 100
 #define SecCKKSIncomingQueueItemsAtOnce 10
 
-#endif // OBJ-C
+#endif  // OBJ-C
 
 /* C functions to interact with CKKS */
 void SecCKKSInitialize(SecDbRef db);
@@ -219,6 +228,9 @@ void SecCKKS24hrNotification(void);
 // Register this callback to receive a call when the item with this UUID next successfully (or unsuccessfully) exits the outgoing queue.
 void CKKSRegisterSyncStatusCallback(CFStringRef cfuuid, SecBoolCFErrorCallback callback);
 
+// Tells CKKS that the local keychain was reset, and that it should respond accordingly
+void SecCKKSPerformLocalResync(void);
+
 // Returns true if CloudKit keychain syncing should occur
 bool SecCKKSIsEnabled(void);
 
@@ -235,10 +247,6 @@ bool SecCKKSEnforceManifests(void);
 bool SecCKKSEnableEnforceManifests(void);
 bool SecCKKSSetEnforceManifests(bool value);
 
-bool SecCKKSShareTLKs(void);
-bool SecCKKSEnableShareTLKs(void);
-bool SecCKKSSetShareTLKs(bool value);
-
 // Testing support
 bool SecCKKSTestsEnabled(void);
 bool SecCKKSTestsEnable(void);
@@ -255,8 +263,7 @@ bool SecCKKSTestDisableKeyNotifications(void);
 void SecCKKSTestSetDisableKeyNotifications(bool set);
 
 
-XPC_RETURNS_RETAINED xpc_endpoint_t
-SecServerCreateCKKSEndpoint(void);
+XPC_RETURNS_RETAINED _Nullable xpc_endpoint_t SecServerCreateCKKSEndpoint(void);
 
 // TODO: handle errors better
 typedef CF_ENUM(CFIndex, CKKSErrorCode) {
@@ -305,40 +312,68 @@ typedef CF_ENUM(CFIndex, CKKSServerExtensionErrorCode) {
     //CKKSServerInvalidCurrentItem = 17,
 };
 
-#define SecTranslateError(nserrorptr, cferror) \
-    if(nserrorptr) { \
-       *nserrorptr = (__bridge_transfer NSError*) cferror; \
-    } else { \
-       CFReleaseNull(cferror); \
+#define SecTranslateError(nserrorptr, cferror)             \
+    if(nserrorptr) {                                       \
+        *nserrorptr = (__bridge_transfer NSError*)cferror; \
+    } else {                                               \
+        CFReleaseNull(cferror);                            \
     }
 
 // Very similar to the secerror, secnotice, and secinfo macros in debugging.h, but add zoneNames
-#define ckkserrorwithzonename(scope, zoneName, format, ...) { os_log(secLogObjForScope("SecError"), scope "-%@: " format, (zoneName ? zoneName : @"unknown"), ## __VA_ARGS__); }
-#define ckksnoticewithzonename(scope, zoneName, format, ...) { os_log(secLogObjForCFScope((__bridge CFStringRef)[@(scope "-") stringByAppendingString: (zoneName ? zoneName : @"unknown")]), format, ## __VA_ARGS__); }
-#define ckksinfowithzonename(scope, zoneName, format, ...) { os_log_debug(secLogObjForCFScope((__bridge CFStringRef)[@(scope "-") stringByAppendingString: (zoneName ? zoneName : @"unknown")]), format, ## __VA_ARGS__); }
-
-#define ckkserror(scope, zoneNameHaver, format, ...) \
-{ NSString* znh = zoneNameHaver.zoneName; \
-  ckkserrorwithzonename(scope, znh, format, ## __VA_ARGS__) \
-}
-#define ckksnotice(scope, zoneNameHaver, format, ...) \
-{ NSString* znh = zoneNameHaver.zoneName; \
-  ckksnoticewithzonename(scope, znh, format, ## __VA_ARGS__) \
-}
-#define ckksinfo(scope, zoneNameHaver, format, ...) \
-{ NSString* znh = zoneNameHaver.zoneName; \
-  ckksinfowithzonename(scope, znh, format, ## __VA_ARGS__) \
-}
+#define ckkserrorwithzonename(scope, zoneName, format, ...)                                                             \
+    {                                                                                                                   \
+        os_log(secLogObjForScope("SecError"), scope "-%@: " format, (zoneName ? zoneName : @"unknown"), ##__VA_ARGS__); \
+    }
+#define ckksnoticewithzonename(scope, zoneName, format, ...)                                                                         \
+    {                                                                                                                                \
+        os_log(secLogObjForCFScope((__bridge CFStringRef)[@(scope "-") stringByAppendingString:(zoneName ? zoneName : @"unknown")]), \
+               format,                                                                                                               \
+               ##__VA_ARGS__);                                                                                                       \
+    }
+#define ckksinfowithzonename(scope, zoneName, format, ...)                                                                                 \
+    {                                                                                                                                      \
+        os_log_debug(secLogObjForCFScope((__bridge CFStringRef)[@(scope "-") stringByAppendingString:(zoneName ? zoneName : @"unknown")]), \
+                     format,                                                                                                               \
+                     ##__VA_ARGS__);                                                                                                       \
+    }
+
+#define ckkserror(scope, zoneNameHaver, format, ...)             \
+    {                                                            \
+        NSString* znh = zoneNameHaver.zoneName;                  \
+        ckkserrorwithzonename(scope, znh, format, ##__VA_ARGS__) \
+    }
+#define ckksnotice(scope, zoneNameHaver, format, ...)             \
+    {                                                             \
+        NSString* znh = zoneNameHaver.zoneName;                   \
+        ckksnoticewithzonename(scope, znh, format, ##__VA_ARGS__) \
+    }
+#define ckksinfo(scope, zoneNameHaver, format, ...)             \
+    {                                                           \
+        NSString* znh = zoneNameHaver.zoneName;                 \
+        ckksinfowithzonename(scope, znh, format, ##__VA_ARGS__) \
+    }
 
 #undef ckksdebug
 #if !defined(NDEBUG)
-#define ckksdebugwithzonename(scope, zoneName, format, ...) { os_log_debug(secLogObjForCFScope((__bridge CFStringRef)[@(scope "-") stringByAppendingString: (zoneName ? zoneName : @"unknown")]), format, ## __VA_ARGS__); }
-#define ckksdebug(scope, zoneNameHaver, format, ...) \
-{ NSString* znh = zoneNameHaver.zoneName; \
-  ckksdebugwithzonename(scope, znh, format, ## __VA_ARGS__) \
-}
+#define ckksdebugwithzonename(scope, zoneName, format, ...)                                                                                \
+    {                                                                                                                                      \
+        os_log_debug(secLogObjForCFScope((__bridge CFStringRef)[@(scope "-") stringByAppendingString:(zoneName ? zoneName : @"unknown")]), \
+                     format,                                                                                                               \
+                     ##__VA_ARGS__);                                                                                                       \
+    }
+#define ckksdebug(scope, zoneNameHaver, format, ...)             \
+    {                                                            \
+        NSString* znh = zoneNameHaver.zoneName;                  \
+        ckksdebugwithzonename(scope, znh, format, ##__VA_ARGS__) \
+    }
+#else
+#define ckksdebug(scope, ...) /* nothing */
+#endif
+
+#ifdef __OBJC__
+NS_ASSUME_NONNULL_END
 #else
-#define ckksdebug(scope,...)   /* nothing */
+CF_ASSUME_NONNULL_END
 #endif
 
 #endif /* CKKS_h */
index d5da4e839cd175996302eed6ecca3bbb660f5e30..d0d130b6999702cc3e15edb6d131d65d3e791591 100644 (file)
@@ -117,6 +117,7 @@ NSString* const SecCKRecordManifestLeafDERKey = @"der";
 NSString* const SecCKRecordManifestLeafDigestKey = @"digest";
 
 CKKSZoneKeyState* const SecCKKSZoneKeyStateReady = (CKKSZoneKeyState*) @"ready";
+CKKSZoneKeyState* const SecCKKSZoneKeyStateReadyPendingUnlock = (CKKSZoneKeyState*) @"readypendingunlock";
 CKKSZoneKeyState* const SecCKKSZoneKeyStateError = (CKKSZoneKeyState*) @"error";
 CKKSZoneKeyState* const SecCKKSZoneKeyStateCancelled = (CKKSZoneKeyState*) @"cancelled";
 
@@ -154,6 +155,7 @@ NSDictionary<CKKSZoneKeyState*, NSNumber*>* CKKSZoneKeyStateMap(void) {
           SecCKKSZoneKeyStateHealTLKShares:      @12U,
           SecCKKSZoneKeyStateHealTLKSharesFailed:@13U,
           SecCKKSZoneKeyStateWaitForFixupOperation:@14U,
+          SecCKKSZoneKeyStateReadyPendingUnlock: @15U,
         };
     });
     return map;
@@ -279,8 +281,10 @@ bool SecCKKSSetEnforceManifests(bool value) {
     return CKKSEnforceManifests;
 }
 
-static bool CKKSShareTLKs = true;
+// Here's a mechanism for CKKS feature flags with default values from NSUserDefaults:
+/*static bool CKKSShareTLKs = true;
 bool SecCKKSShareTLKs(void) {
+    return true;
     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
         // Use the default value as above, or apply the preferences value if it exists
@@ -292,17 +296,7 @@ bool SecCKKSShareTLKs(void) {
     });
 
     return CKKSShareTLKs;
-}
-bool SecCKKSEnableShareTLKs(void) {
-    return SecCKKSSetShareTLKs(true);
-}
-bool SecCKKSSetShareTLKs(bool value) {
-    // Call this to do the dispatch_once first
-    SecCKKSShareTLKs();
-
-    CKKSShareTLKs = value;
-    return CKKSShareTLKs;
-}
+}*/
 
 // Feature flags to twiddle behavior for tests
 static bool CKKSDisableAutomaticUUID = false;
@@ -445,3 +439,16 @@ void CKKSRegisterSyncStatusCallback(CFStringRef cfuuid, SecBoolCFErrorCallback c
     [[CKKSViewManager manager] registerSyncStatusCallback: (__bridge NSString*) cfuuid callback:nscallback];
 #endif
 }
+
+void SecCKKSPerformLocalResync() {
+#if OCTAGON
+    secnotice("ckks", "Local keychain was reset; performing local resync");
+    [[CKKSViewManager manager] rpcResyncLocal:nil reply:^(NSError *result) {
+        if(result) {
+            secnotice("ckks", "Local keychain reset resync finished with an error: %@", result);
+        } else {
+            secnotice("ckks", "Local keychain reset resync finished successfully");
+        }
+    }];
+#endif
+}
index 9a876a91c7a9a3e7e8965d7c4789f28fdff75ce3..e9788bc954a67b187584b27a937a18b1d4d92d17 100644 (file)
 #import <Foundation/Foundation.h>
 
 #if OCTAGON
-#import <CloudKit/CloudKit.h>
 #import <ApplePushService/ApplePushService.h>
-#import "keychain/ckks/CloudKitDependencies.h"
+#import <CloudKit/CloudKit.h>
 #import "keychain/ckks/CKKSCondition.h"
+#import "keychain/ckks/CloudKitDependencies.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 @protocol CKKSZoneUpdateReceiver
-- (void)notifyZoneChange: (CKRecordZoneNotification*) notification;
+- (void)notifyZoneChange:(CKRecordZoneNotification* _Nullable)notification;
 @end
 
 @interface CKKSAPSReceiver : NSObject <APSConnectionDelegate>
 
 // class dependencies (for injection)
 @property (readonly) Class<CKKSAPSConnection> apsConnectionClass;
-
-@property id<CKKSAPSConnection> apsConnection;
+@property (nullable) id<CKKSAPSConnection> apsConnection;
 
 + (instancetype)receiverForEnvironment:(NSString*)environmentName
                      namedDelegatePort:(NSString*)namedDelegatePort
                     apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass;
-- (CKKSCondition*)register:(id<CKKSZoneUpdateReceiver>)receiver forZoneID:(CKRecordZoneID *)zoneID;
+- (CKKSCondition*)registerReceiver:(id<CKKSZoneUpdateReceiver>)receiver forZoneID:(CKRecordZoneID*)zoneID;
 
 // Test support:
 - (instancetype)initWithEnvironmentName:(NSString*)environmentName
@@ -56,4 +57,5 @@
 
 @end
 
-#endif // OCTAGON
+NS_ASSUME_NONNULL_END
+#endif  // OCTAGON
index 31bd8625fd67b88a161af5a20d51e64225dbe9d0..c8bc2158fe486b5636f2c7d9b2d6028d868048ce 100644 (file)
@@ -99,7 +99,7 @@
     return self;
 }
 
-- (CKKSCondition*)register:(id<CKKSZoneUpdateReceiver>)receiver forZoneID:(CKRecordZoneID *)zoneID {
+- (CKKSCondition*)registerReceiver:(id<CKKSZoneUpdateReceiver>)receiver forZoneID:(CKRecordZoneID *)zoneID {
     CKKSCondition* finished = [[CKKSCondition alloc] init];
 
     __weak __typeof(self) weakSelf = self;
index 49d8de91e97b68c507c5a1d00b95933de41bb437..19370999af6fba4ef8ace8ea3dc11ba3e9817723 100644 (file)
 #import <Foundation/Foundation.h>
 
 #if OCTAGON
-#import <CloudKit/CloudKit.h>
 #import <CloudKit/CKContainer_Private.h>
-#import "keychain/ckks/CloudKitDependencies.h"
-#import "keychain/ckks/CKKSCondition.h"
+#import <CloudKit/CloudKit.h>
 #include <Security/SecureObjectSync/SOSCloudCircle.h>
+#import "keychain/ckks/CKKSCondition.h"
+#import "keychain/ckks/CloudKitDependencies.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 /*
  * Implements a 'debouncer' to store the current CK account and circle state, and receive updates to it.
 
 typedef NS_ENUM(NSInteger, CKKSAccountStatus) {
     /* Set at initialization. This means we haven't figured out what the account state is. */
-    CKKSAccountStatusUnknown                             = 0,
+    CKKSAccountStatusUnknown = 0,
     /* We have an iCloud account and are in-circle */
-    CKKSAccountStatusAvailable                           = 1,
+    CKKSAccountStatusAvailable = 1,
     /* No iCloud account is logged in on this device, or we're out of circle */
-    CKKSAccountStatusNoAccount                           = 3,
+    CKKSAccountStatusNoAccount = 3,
 };
 
 @protocol CKKSAccountStateListener
--(void)ckAccountStatusChange: (CKKSAccountStatus)oldStatus to:(CKKSAccountStatus)currentStatus;
+- (void)ckAccountStatusChange:(CKKSAccountStatus)oldStatus to:(CKKSAccountStatus)currentStatus;
 @end
 
 @interface CKKSCKAccountStateTracker : NSObject
-@property CKKSCondition* finishedInitialCalls;
+@property CKKSCondition* finishedInitialDispatches;
 
 // If you use these, please be aware they could change out from under you at any time
-@property CKAccountInfo* currentCKAccountInfo;
+@property (nullable) CKAccountInfo* currentCKAccountInfo;
 @property SOSCCStatus currentCircleStatus;
 
 // Fetched and memoized from CloudKit; we can't afford deadlocks with their callbacks
-@property (copy) NSString* ckdeviceID;
-@property NSError*         ckdeviceIDError;
-@property CKKSCondition*   ckdeviceIDInitialized;
+@property (nullable, copy) NSString* ckdeviceID;
+@property (nullable) NSError* ckdeviceIDError;
+@property CKKSCondition* ckdeviceIDInitialized;
 
 // Fetched and memoized from the Account when we're in-circle; our threading model is strange
-@property NSString*      accountCirclePeerID;
-@property NSError*       accountCirclePeerIDError;
+@property (nullable) NSString* accountCirclePeerID;
+@property (nullable) NSError* accountCirclePeerIDError;
 @property CKKSCondition* accountCirclePeerIDInitialized;
 
--(instancetype)init: (CKContainer*) container nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass;
+- (instancetype)init:(CKContainer*)container nsnotificationCenterClass:(Class<CKKSNSNotificationCenter>)nsnotificationCenterClass;
 
--(CKKSAccountStatus)currentCKAccountStatusAndNotifyOnChange: (id<CKKSAccountStateListener>) listener;
+- (dispatch_semaphore_t)notifyOnAccountStatusChange:(id<CKKSAccountStateListener>)listener;
 
 // Methods useful for testing:
 
 // Call this to simulate a notification (and pause the calling thread until all notifications are delivered)
--(void)notifyCKAccountStatusChangeAndWaitForSignal;
--(void)notifyCircleStatusChangeAndWaitForSignal;
+- (void)notifyCKAccountStatusChangeAndWaitForSignal;
+- (void)notifyCircleStatusChangeAndWaitForSignal;
+
+- (dispatch_group_t _Nullable)checkForAllDeliveries;
 
-+(SOSCCStatus)getCircleStatus;
-+(void)fetchCirclePeerID:(void (^)(NSString* peerID, NSError* error))callback;
-+(NSString*)stringFromAccountStatus: (CKKSAccountStatus) status;
++ (SOSCCStatus)getCircleStatus;
++ (void)fetchCirclePeerID:(void (^)(NSString* _Nullable peerID, NSError* _Nullable error))callback;
++ (NSString*)stringFromAccountStatus:(CKKSAccountStatus)status;
 
 @end
 
-#endif // OCTAGON
+NS_ASSUME_NONNULL_END
+#endif  // OCTAGON
index e29784cc7375c2cf68a214193aad647739aa8ca5..9c340e19817a8de74d675f174b964cb5f477bdbe 100644 (file)
@@ -67,7 +67,7 @@
         _firstCKAccountFetch = false;
         _firstSOSCircleFetch = false;
 
-        _finishedInitialCalls = [[CKKSCondition alloc] init];
+        _finishedInitialDispatches = [[CKKSCondition alloc] init];
         _ckdeviceIDInitialized = [[CKKSCondition alloc] init];
 
         id<CKKSNSNotificationCenter> notificationCenter = [self.nsnotificationCenterClass defaultCenter];
@@ -92,7 +92,7 @@
             }
             [strongSelf notifyCKAccountStatusChange:nil];
             [strongSelf notifyCircleChange:nil];
-            [strongSelf.finishedInitialCalls fulfill];
+            [strongSelf.finishedInitialDispatches fulfill];
         });
     }
     return self;
     return [self descriptionInternal: [super description]];
 }
 
--(CKKSAccountStatus)currentCKAccountStatusAndNotifyOnChange: (id<CKKSAccountStateListener>) listener {
-
-    __block CKKSAccountStatus status = CKKSAccountStatusUnknown;
-
-    dispatch_sync(self.queue, ^{
-        status = self.currentComputedAccountStatus;
+-(dispatch_semaphore_t)notifyOnAccountStatusChange:(id<CKKSAccountStateListener>)listener {
+    // signals when we've successfully delivered the first account status
+    dispatch_semaphore_t finishedSema = dispatch_semaphore_create(0);
 
+    dispatch_async(self.queue, ^{
         bool alreadyRegisteredListener = false;
         NSEnumerator *enumerator = [self.changeListeners objectEnumerator];
         id<CKKSAccountStateListener> value;
 
             dispatch_queue_t objQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_SERIAL);
             [self.changeListeners setObject: listener forKey: objQueue];
+
+            // If we know the current account status, let this listener know
+            if(self.currentComputedAccountStatus != CKKSAccountStatusUnknown) {
+
+                dispatch_group_t g = dispatch_group_create();
+                if(!g) {
+                    secnotice("ckksaccount", "Unable to get dispatch group.");
+                    return;
+                }
+
+                [self _onqueueDeliverCurrentState:listener listenerQueue:objQueue oldStatus:CKKSAccountStatusUnknown group:g];
+
+                dispatch_group_notify(g, self.queue, ^{
+                    dispatch_semaphore_signal(finishedSema);
+                });
+            } else {
+                dispatch_semaphore_signal(finishedSema);
+            }
+        } else {
+            dispatch_semaphore_signal(finishedSema);
         }
     });
-    return status;
+
+    return finishedSema;
 }
 
 - (dispatch_semaphore_t)notifyCKAccountStatusChange:(__unused id)object {
     }
 }
 
--(void)_onqueueUpdateAccountState: (CKAccountInfo*) ckAccountInfo circle: (SOSCCStatus) sosccstatus deliveredSemaphore: (dispatch_semaphore_t) finishedSema {
+-(void)_onqueueUpdateAccountState:(CKAccountInfo*)ckAccountInfo circle:(SOSCCStatus)sosccstatus deliveredSemaphore:(dispatch_semaphore_t)finishedSema {
     dispatch_assert_queue(self.queue);
 
     if([self.currentCKAccountInfo isEqual: ckAccountInfo] && self.currentCircleStatus == sosccstatus) {
               self.currentCKAccountInfo,
               SOSCCGetStatusDescription(self.currentCircleStatus));
 
+    [self _onqueueDeliverStateChanges:oldComputedStatus deliveredSemaphore:finishedSema];
+}
+
+-(void)_onqueueDeliverStateChanges:(CKKSAccountStatus)oldStatus deliveredSemaphore:(dispatch_semaphore_t)finishedSema {
+    dispatch_assert_queue(self.queue);
+
     dispatch_group_t g = dispatch_group_create();
     if(!g) {
         secnotice("ckksaccount", "Unable to get dispatch group.");
     // Queue up the changes for each listener.
     while ((dq = [enumerator nextObject])) {
         id<CKKSAccountStateListener> listener = [self.changeListeners objectForKey: dq];
-        __weak __typeof(listener) weakListener = listener;
-
-        if(listener) {
-            dispatch_group_async(g, dq, ^{
-                [weakListener ckAccountStatusChange: oldComputedStatus to: self.currentComputedAccountStatus];
-            });
-        }
+        [self _onqueueDeliverCurrentState:listener listenerQueue:dq oldStatus:oldStatus group:g];
     }
 
     dispatch_group_notify(g, self.queue, ^{
     });
 }
 
+-(void)_onqueueDeliverCurrentState:(id<CKKSAccountStateListener>)listener listenerQueue:(dispatch_queue_t)listenerQueue oldStatus:(CKKSAccountStatus)oldStatus group:(dispatch_group_t)g {
+    dispatch_assert_queue(self.queue);
+
+    __weak __typeof(listener) weakListener = listener;
+
+    if(listener) {
+        dispatch_group_async(g, listenerQueue, ^{
+            [weakListener ckAccountStatusChange:oldStatus to:self.currentComputedAccountStatus];
+        });
+    }
+}
+
 -(void)notifyCKAccountStatusChangeAndWaitForSignal {
     dispatch_semaphore_wait([self notifyCKAccountStatusChange: nil], DISPATCH_TIME_FOREVER);
 }
     dispatch_semaphore_wait([self notifyCircleChange: nil], DISPATCH_TIME_FOREVER);
 }
 
+-(dispatch_group_t)checkForAllDeliveries {
+
+    dispatch_group_t g = dispatch_group_create();
+    if(!g) {
+        secnotice("ckksaccount", "Unable to get dispatch group.");
+        return nil;
+    }
+
+    dispatch_sync(self.queue, ^{
+        NSEnumerator *enumerator = [self.changeListeners keyEnumerator];
+        dispatch_queue_t dq;
+
+        // Queue up the changes for each listener.
+        while ((dq = [enumerator nextObject])) {
+            id<CKKSAccountStateListener> listener = [self.changeListeners objectForKey: dq];
+
+            secinfo("ckksaccountblock", "Starting blocking for listener %@", listener);
+            __weak __typeof(listener) weakListener = listener;
+            dispatch_group_async(g, dq, ^{
+                __strong __typeof(listener) strongListener = weakListener;
+                // Do nothing in particular. It's just important that this block runs.
+                secinfo("ckksaccountblock", "Done blocking for listener %@", strongListener);
+            });
+        }
+    });
+
+    return g;
+}
+
 // This is its own function to allow OCMock to swoop in and replace the result during testing.
 +(SOSCCStatus)getCircleStatus {
     CFErrorRef cferror = NULL;
index ff6cf694b08c2c87a000d9e09971457223d9d203..ebb780ac87b3f0c96befb2e7c974d6e2ecb94bfd 100644 (file)
@@ -24,6 +24,8 @@
 #import <Foundation/Foundation.h>
 #import <dispatch/dispatch.h>
 
+NS_ASSUME_NONNULL_BEGIN
+
 /*
  * CKKSCondition is for implementing one-shot condition variables,
  * based on libdispatch semaphores (which might use condition variables underneath).
 
 @interface CKKSCondition : NSObject
 
--(instancetype)init;
+- (instancetype)init;
+
+// Fulfilling this condition will also fulfill the chained condition.
+// Fulfilling the chained condition will do nothing to this condition.
+- (instancetype)initToChain:(CKKSCondition* _Nullable)chain;
 
 /* Fulfills the condition. Can only be called once per CKKSCondition. */
--(void)fulfill;
+- (void)fulfill;
 
 /* Wait for the the condition to be fulfilled. The returned result behaves exactly like
    libdispatch's dispatch_semaphore_wait. */
--(long)wait:(uint64_t)timeout;
+- (long)wait:(uint64_t)timeout;
 
 @end
 
+NS_ASSUME_NONNULL_END
index 998c34859c7cf3f3e87c823f2b130268b145f6ec..ebef4ef2ba4155a50bc3a5e7028dcc2c6718265a 100644 (file)
 
 @interface CKKSCondition ()
 @property dispatch_semaphore_t semaphore;
+@property CKKSCondition* chain;
 @end
 
 @implementation CKKSCondition
 
--(instancetype)init
+-(instancetype)init {
+    return [self initToChain:nil];
+}
+
+-(instancetype)initToChain:(CKKSCondition*)chain
 {
     if((self = [super init])) {
         _semaphore = dispatch_semaphore_create(0);
+        _chain = chain;
     }
     return self;
 }
 
 -(void)fulfill {
     dispatch_semaphore_signal(self.semaphore);
+    [self.chain fulfill];
+    self.chain = nil; // break the retain, since that condition is filled
 }
 
 -(long)wait:(uint64_t)timeout {
index 29172c6d5a4be0ba4eb5adeacc876a019326b77c..32aedf6398018dd15d4c4df85ca407f7cb371a80 100644 (file)
 
 #import <Foundation/Foundation.h>
 
+NS_ASSUME_NONNULL_BEGIN
+
 @interface CKKSControl : NSObject
 
 - (instancetype)init NS_UNAVAILABLE;
 - (instancetype)initWithConnection:(NSXPCConnection*)connection;
 
-- (void)rpcStatus:                          (NSString*)viewName reply:(void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply;
-- (void)rpcResetLocal:                      (NSString*)viewName reply:(void(^)(NSError* error))reply;
-- (void)rpcResetCloudKit:                   (NSString*)viewName reply:(void(^)(NSError* error))reply;
-- (void)rpcResync:                          (NSString*)viewName reply:(void(^)(NSError* error))reply;
-- (void)rpcFetchAndProcessChanges:          (NSString*)viewName reply:(void(^)(NSError* error))reply;
-- (void)rpcFetchAndProcessClassAChanges:    (NSString*)viewName reply:(void(^)(NSError* error))reply;
-- (void)rpcPushOutgoingChanges:             (NSString*)viewName reply:(void(^)(NSError* error))reply;
+- (void)rpcStatus:(NSString* _Nullable)viewName
+            reply:(void (^)(NSArray<NSDictionary*>* _Nullable result, NSError* _Nullable error))reply;
+- (void)rpcResetLocal:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply;
+- (void)rpcResetCloudKit:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply;
+- (void)rpcResync:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply;
+- (void)rpcFetchAndProcessChanges:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply;
+- (void)rpcFetchAndProcessClassAChanges:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply;
+- (void)rpcPushOutgoingChanges:(NSString* _Nullable)viewName reply:(void (^)(NSError* _Nullable error))reply;
 
 - (void)rpcPerformanceCounters:             (void(^)(NSDictionary <NSString *,NSNumber *> *,NSError*))reply;
 - (void)rpcGetAnalyticsSysdiagnoseWithReply:(void (^)(NSString* sysdiagnose, NSError* error))reply;
 - (void)rpcForceUploadAnalyticsWithReply:   (void (^)(BOOL success, NSError* error))reply;
 
 // convenience wrapper for rpcStatus:reply:
-- (void)rpcTLKMissing:                      (NSString*)viewName reply:(void(^)(bool missing))reply;
+- (void)rpcTLKMissing:(NSString* _Nullable)viewName reply:(void (^)(bool missing))reply;
 
-+ (CKKSControl*)controlObject:(NSError* __autoreleasing *)error;
++ (CKKSControl* _Nullable)controlObject:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
 @end
 
-#endif // __OBJC__
+NS_ASSUME_NONNULL_END
+#endif  // __OBJC__
index ce2411018f49b0effc453f244c7c7b2fb146d33d..2492a3c6e481748fdd79d77d71a9f5378a6a2446 100644 (file)
@@ -28,6 +28,7 @@
 - (void)rpcResetLocal:    (NSString*)viewName reply: (void(^)(NSError* result)) reply;
 - (void)rpcResetCloudKit: (NSString*)viewName reply: (void(^)(NSError* result)) reply;
 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply;
+- (void)rpcResyncLocal:(NSString*)viewName reply:(void(^)(NSError* result))reply;
 - (void)rpcStatus:(NSString*)viewName reply: (void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply;
 - (void)rpcFetchAndProcessChanges:(NSString*)viewName reply: (void(^)(NSError* result)) reply;
 - (void)rpcFetchAndProcessClassAChanges:(NSString*)viewName reply: (void(^)(NSError* result)) reply;
index 1008cf0e0b910a5067f5509eb188c04db479c9b2..afc5b61af7130014e4d4d5968f535b9e51523d56 100644 (file)
                   currentItemUUID:(NSString*)currentItemUUID
                             state:(CKKSProcessedState*)state
                            zoneID:(CKRecordZoneID*)zoneID
-                  encodedCKRecord:(NSData*) encodedrecord;
-
-+ (instancetype) fromDatabase:(NSString*)identifier state:(CKKSProcessedState*)state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase:(NSString*)identifier state:(CKKSProcessedState*)state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-
-+ (NSArray<CKKSCurrentItemPointer*>*)remoteItemPointers: (CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error;
-+ (bool) deleteAll:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *) error;
-+ (NSArray<CKKSCurrentItemPointer*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error;
+                  encodedCKRecord:(NSData*)encodedrecord;
+
++ (instancetype)fromDatabase:(NSString*)identifier
+                       state:(CKKSProcessedState*)state
+                      zoneID:(CKRecordZoneID*)zoneID
+                       error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)identifier
+                          state:(CKKSProcessedState*)state
+                         zoneID:(CKRecordZoneID*)zoneID
+                          error:(NSError* __autoreleasing*)error;
+
++ (NSArray<CKKSCurrentItemPointer*>*)remoteItemPointers:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (bool)deleteAll:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSCurrentItemPointer*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 @end
 
index b55efcfbe99e567147dc8e4934ed13cc0b25429d..99bfb5e848a0416517dc83a4f5b90b508dfec442 100644 (file)
 - (instancetype)initForClass:(CKKSKeyClass*)keyclass
               currentKeyUUID:(NSString*)currentKeyUUID
                       zoneID:(CKRecordZoneID*)zoneID
-             encodedCKRecord: (NSData*) encodedrecord;
+             encodedCKRecord:(NSData*)encodedrecord;
 
-+ (instancetype) fromDatabase: (CKKSKeyClass*) keyclass zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (CKKSKeyClass*) keyclass zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)fromDatabase:(CKKSKeyClass*)keyclass zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(CKKSKeyClass*)keyclass zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
-+ (instancetype) forKeyClass: (CKKSKeyClass*) keyclass withKeyUUID: (NSString*) keyUUID zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)forKeyClass:(CKKSKeyClass*)keyclass
+                withKeyUUID:(NSString*)keyUUID
+                     zoneID:(CKRecordZoneID*)zoneID
+                      error:(NSError* __autoreleasing*)error;
 
-+ (NSArray<CKKSCurrentKeyPointer*>*)all:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (bool) deleteAll:(CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error;
++ (NSArray<CKKSCurrentKeyPointer*>*)all:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (bool)deleteAll:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 @end
 
@@ -62,8 +65,8 @@
 
 @property NSArray<CKKSTLKShare*>* tlkShares;
 
--(instancetype)init;
--(instancetype)initForZone:(CKRecordZoneID*)zoneID;
+- (instancetype)init;
+- (instancetype)initForZone:(CKRecordZoneID*)zoneID;
 @end
 
 #endif
index 8634310f0ffd2d0b1c62d3ff69a7dce992b5f760..cd3ac7cc8aa107e86e1a03fdc070c2b038130794 100644 (file)
 
 #if OCTAGON
 
-#include <utilities/SecDb.h>
 #include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
 
 #import <CloudKit/CloudKit.h>
 #import "keychain/ckks/CKKS.h"
-#import "keychain/ckks/CKKSSQLDatabaseObject.h"
 #import "keychain/ckks/CKKSRecordHolder.h"
+#import "keychain/ckks/CKKSSQLDatabaseObject.h"
 
 /*
  * This is the backing class for "device state" records: each device in an iCloud account copies
 @property NSString* currentClassAUUID;
 @property NSString* currentClassCUUID;
 
-+ (instancetype)fromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error;
-+ (instancetype)tryFromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error;
-+ (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID error:(NSError * __autoreleasing *)error;
-+ (NSArray<CKKSDeviceStateEntry*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error;
++ (instancetype)fromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)device zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSDeviceStateEntry*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 - (instancetype)init NS_UNAVAILABLE;
 - (instancetype)initForDevice:(NSString*)device
@@ -71,6 +71,5 @@
               encodedCKRecord:(NSData*)encodedrecord;
 @end
 
-#endif // OCTAGON
-#endif /* CKKSDeviceStateEntry_h */
-
+#endif  // OCTAGON
+#endif  /* CKKSDeviceStateEntry_h */
index 1e8a356fc701baf86b2fc8fc0b2749b7c0eb17d0..f94594599b08fbaa019ffadb6591bc4efd74f4f2 100644 (file)
 #import <Foundation/Foundation.h>
 
 #if OCTAGON
-
 @class CKKSKeychainView;
 #import "keychain/ckks/CKKSGroupOperation.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 @interface CKKSFetchAllRecordZoneChangesOperation : CKKSGroupOperation
 
 // Set this to true before starting this operation if you'd like resync behavior:
 //  Fetching everything currently in CloudKit and comparing to local copy
 @property bool resync;
 
-@property (weak) CKKSKeychainView* ckks;
+@property (nullable, weak) CKKSKeychainView* ckks;
 @property CKRecordZoneID* zoneID;
 
 @property NSMutableDictionary<CKRecordID*, CKRecord*>* modifications;
 @property NSMutableDictionary<CKRecordID*, NSString*>* deletions;
 
-@property CKServerChangeToken* serverChangeToken;
+@property (nullable) CKServerChangeToken* serverChangeToken;
 
 - (instancetype)init NS_UNAVAILABLE;
 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
 
 @end
 
+NS_ASSUME_NONNULL_END
 
 #endif
index bf98cb51242996729afb95afad0fd90dd3524471..528ba34ea430269e65570da78e59c28c5313332b 100644 (file)
 #import "keychain/ckks/CKKSMirrorEntry.h"
 #import "keychain/ckks/CKKSManifest.h"
 #import "keychain/ckks/CKKSManifestLeafRecord.h"
+#import "CKKSPowerCollection.h"
 #include <securityd/SecItemServer.h>
 
 
 @interface CKKSFetchAllRecordZoneChangesOperation()
 @property CKDatabaseOperation<CKKSFetchRecordZoneChangesOperation>* fetchRecordZoneChangesOperation;
 @property CKOperationGroup* ckoperationGroup;
+@property (assign) NSUInteger fetchedItems;
 @end
 
 @implementation CKKSFetchAllRecordZoneChangesOperation
@@ -91,7 +93,7 @@
         }
     }
 
-    if (![ckks _onQueueUpdateLatestManifestWithError:&error]) {
+    if (![ckks _onqueueUpdateLatestManifestWithError:&error]) {
         self.error = error;
         ckkserror("ckksfetch", ckks, "failed to get latest manifest");
     }
     NSError* error = nil;
     if(self.resync) {
         ckksnotice("ckksresync", ckks, "Comparing local UUIDs against the CloudKit list");
-        NSMutableArray<NSString*>* uuids = [[CKKSMirrorEntry allUUIDs: &error] mutableCopy];
+        NSMutableArray<NSString*>* uuids = [[CKKSMirrorEntry allUUIDs:ckks.zoneID error:&error] mutableCopy];
 
         for(NSString* uuid in uuids) {
             if([self.modifications objectForKey: [[CKRecordID alloc] initWithRecordName: uuid zoneID: ckks.zoneID]]) {
-                ckksdebug("ckksresync", ckks, "UUID %@ is still in CloudKit; carry on.", uuid);
+                ckksnotice("ckksresync", ckks, "UUID %@ is still in CloudKit; carry on.", uuid);
             } else {
-                CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase: uuid zoneID:ckks.zoneID error: &error];
+                CKKSMirrorEntry* ckme = [CKKSMirrorEntry tryFromDatabase:uuid zoneID:ckks.zoneID error: &error];
                 if(error != nil) {
                     ckkserror("ckksresync", ckks, "Couldn't read an item from the database, but it used to be there: %@ %@", uuid, error);
                     self.error = error;
                     continue;
                 }
+                if(!ckme) {
+                    ckkserror("ckksresync", ckks, "Couldn't read ckme(%@) from database; continuing", uuid);
+                    continue;
+                }
 
                 ckkserror("ckksresync", ckks, "BUG: Local item %@ not found in CloudKit, deleting", uuid);
                 [ckks _onqueueCKRecordDeleted:ckme.item.storedCKRecord.recordID recordType:ckme.item.storedCKRecord.recordType resync:self.resync];
 - (void)groupStart {
     __weak __typeof(self) weakSelf = self;
 
-
     CKKSKeychainView* ckks = self.ckks;
     if(!ckks) {
         ckkserror("ckksresync", ckks, "no CKKS object");
             // Add this to the modifications, and remove it from the deletions
             [strongSelf.modifications setObject: record forKey: record.recordID];
             [strongSelf.deletions removeObjectForKey: record.recordID];
+            strongSelf.fetchedItems++;
         };
 
         self.fetchRecordZoneChangesOperation.recordWithIDWasDeletedBlock = ^(CKRecordID *recordID, NSString *recordType) {
             // Add to the deletions, and remove any pending modifications
             [strongSelf.modifications removeObjectForKey: recordID];
             [strongSelf.deletions setObject: recordType forKey: recordID];
+            strongSelf.fetchedItems++;
         };
 
         // This class only supports fetching from a single zone, so we can ignore recordZoneID
                 strongSelf.error = operationError;
             }
 
+            //[CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventFetchAllChanges zone:ckks.zoneName count:strongSelf.fetchedItems];
+
+
             // Trigger the fake 'we're done' operation.
             [strongSelf runBeforeGroupFinished: recordZoneChangesCompletedOperation];
         };
index 2346dbac8b5c7b2e35d3f1d2936967599cd6cfb0..3db29c4abf7be03f1ac03e0546e4d5d300ddf32f 100644 (file)
@@ -24,9 +24,9 @@
 #if OCTAGON
 
 #import <Foundation/Foundation.h>
-#import "keychain/ckks/CKKSResultOperation.h"
 #import "keychain/ckks/CKKSGroupOperation.h"
 #import "keychain/ckks/CKKSKeychainView.h"
+#import "keychain/ckks/CKKSResultOperation.h"
 
 // Sometimes things go wrong.
 // Sometimes you have to clean up after your past self.
@@ -36,8 +36,9 @@ typedef NS_ENUM(NSUInteger, CKKSFixup) {
     CKKSFixupNever,
     CKKSFixupRefetchCurrentItemPointers,
     CKKSFixupFetchTLKShares,
+    CKKSFixupLocalReload,
 };
-#define CKKSCurrentFixupNumber (SecCKKSShareTLKs() ? CKKSFixupFetchTLKShares : CKKSFixupRefetchCurrentItemPointers)
+#define CKKSCurrentFixupNumber (CKKSFixupLocalReload)
 
 @interface CKKSFixups : NSObject
 +(CKKSGroupOperation*)fixup:(CKKSFixup)lastfixup for:(CKKSKeychainView*)keychainView;
@@ -46,12 +47,18 @@ typedef NS_ENUM(NSUInteger, CKKSFixup) {
 // Fixup declarations. You probably don't need to look at these
 @interface CKKSFixupRefetchAllCurrentItemPointers : CKKSGroupOperation
 @property (weak) CKKSKeychainView* ckks;
-- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView ckoperationGroup:(CKOperationGroup *)ckoperationGroup;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
 @end
 
 @interface CKKSFixupFetchAllTLKShares : CKKSGroupOperation
 @property (weak) CKKSKeychainView* ckks;
-- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView ckoperationGroup:(CKOperationGroup *)ckoperationGroup;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
+@end
+
+@interface CKKSFixupLocalReloadOperation : CKKSGroupOperation
+@property (weak) CKKSKeychainView* ckks;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
+                        ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
 @end
 
-#endif // OCTAGON
+#endif  // OCTAGON
index ee99c72e7e6ef5f4d7df5ef379b9980cada305fe..97de7c4bf59e812c9f58c9b0269d887c7548b2c8 100644 (file)
     CKKSGroupOperation* fixups = [[CKKSGroupOperation alloc] init];
     fixups.name = @"ckks-fixups";
 
-    CKKSResultOperation* previousOp = nil;
+    CKKSResultOperation* previousOp = keychainView.holdFixupOperation;
 
     if(lastfixup < CKKSFixupRefetchCurrentItemPointers) {
         CKKSResultOperation* refetch = [[CKKSFixupRefetchAllCurrentItemPointers alloc] initWithCKKSKeychainView:keychainView
                                                                                                ckoperationGroup:fixupCKGroup];
-        [refetch addNullableSuccessDependency:previousOp];
+        [refetch addNullableDependency:previousOp];
         [fixups runBeforeGroupFinished:refetch];
         previousOp = refetch;
     }
 
-    if(SecCKKSShareTLKs() && lastfixup < CKKSFixupFetchTLKShares) {
+    if(lastfixup < CKKSFixupFetchTLKShares) {
         CKKSResultOperation* fetchShares = [[CKKSFixupFetchAllTLKShares alloc] initWithCKKSKeychainView:keychainView
                                                                                        ckoperationGroup:fixupCKGroup];
         [fetchShares addNullableSuccessDependency:previousOp];
         previousOp = fetchShares;
     }
 
+    if(lastfixup < CKKSFixupLocalReload) {
+        CKKSResultOperation* localSync = [[CKKSFixupLocalReloadOperation alloc] initWithCKKSKeychainView:keychainView
+                                                                                        ckoperationGroup:fixupCKGroup];
+        [localSync addNullableSuccessDependency:previousOp];
+        [fixups runBeforeGroupFinished:localSync];
+        previousOp = localSync;
+    }
+
+    (void)previousOp;
     return fixups;
 }
 @end
 }
 @end
 
+#pragma mark - CKKSFixupLocalReloadOperation
+
+@interface CKKSFixupLocalReloadOperation ()
+@property CKOperationGroup* group;
+@end
+
+// In <rdar://problem/35540228> Server Generated CloudKit "Manatee Identity Lost"
+// items could be deleted from the local keychain after CKKS believed they were already synced, and therefore wouldn't resync
+// Perform a local resync operation
+@implementation CKKSFixupLocalReloadOperation
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)keychainView
+                        ckoperationGroup:(CKOperationGroup *)ckoperationGroup
+{
+    if((self = [super init])) {
+        _ckks = keychainView;
+        _group = ckoperationGroup;
+    }
+    return self;
+}
+
+- (NSString*)description {
+    return [NSString stringWithFormat:@"<CKKSFixup:LocalReload (%@)>", self.ckks];
+}
+- (void)groupStart {
+    CKKSKeychainView* ckks = self.ckks;
+    __weak __typeof(self) weakSelf = self;
+    if(!ckks) {
+        ckkserror("ckksfixup", ckks, "no CKKS object");
+        self.error = [NSError errorWithDomain:CKKSErrorDomain code:errSecInternalError userInfo:@{NSLocalizedDescriptionKey: @"no CKKS object"}];
+        return;
+    }
+
+    CKKSResultOperation* reload = [[CKKSReloadAllItemsOperation alloc] initWithCKKSKeychainView:ckks];
+    [self runBeforeGroupFinished:reload];
+
+    CKKSResultOperation* cleanup = [CKKSResultOperation named:@"local-reload-cleanup" withBlock:^{
+        __strong __typeof(self) strongSelf = weakSelf;
+        __strong __typeof(self.ckks) strongCKKS = strongSelf.ckks;
+        [strongCKKS dispatchSync:^bool{
+            if(reload.error) {
+                ckkserror("ckksfixup", strongCKKS, "Couldn't perform a reload: %@", reload.error);
+                strongSelf.error = reload.error;
+                return false;
+            }
+
+            ckksnotice("ckksfixup", strongCKKS, "Successfully performed a reload fixup");
+
+            NSError* localerror = nil;
+            CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry fromDatabase:strongCKKS.zoneName error:&localerror];
+            ckse.lastFixup = CKKSFixupLocalReload;
+            [ckse saveToDatabase:&localerror];
+            if(localerror) {
+                ckkserror("ckksfixup", strongCKKS, "Couldn't save CKKSZoneStateEntry(%@): %@", ckse, localerror);
+            } else {
+                ckksnotice("ckksfixup", strongCKKS, "Updated zone fixup state to CKKSFixupLocalReload");
+            }
+            return true;
+        }];
+    }];
+    [cleanup addNullableDependency:reload];
+    [self runBeforeGroupFinished:cleanup];
+}
+@end
 
 #endif // OCTAGON
index f0caf654965194908f121ae7ec919887019c1852..d157762c857d189ad845629ba47ad31cd08e3673 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-
-#ifndef CKKSGroupOperation_h
-#define CKKSGroupOperation_h
+#if OCTAGON
 
 #import <Foundation/Foundation.h>
 #include <dispatch/dispatch.h>
 #import "keychain/ckks/CKKSResultOperation.h"
 
-@interface CKKSGroupOperation : CKKSResultOperation {
+@interface CKKSGroupOperation : CKKSResultOperation
+{
     BOOL executing;
     BOOL finished;
 }
@@ -41,8 +40,8 @@
 // For subclasses: override this to execute at Group operation start time
 - (void)groupStart;
 
-- (void)runBeforeGroupFinished: (NSOperation*) suboperation;
-- (void)dependOnBeforeGroupFinished: (NSOperation*) suboperation;
+- (void)runBeforeGroupFinished:(NSOperation*)suboperation;
+- (void)dependOnBeforeGroupFinished:(NSOperation*)suboperation;
 @end
 
-#endif // CKKSGroupOperation_h
+#endif  // OCTAGON
index 727a12f2939021eea3ed4cf2933ed7f04ca7e0b6..1ea9ea85c59876ad9d805d7ff870551798552671 100644 (file)
@@ -21,6 +21,8 @@
  * @APPLE_LICENSE_HEADER_END@
  */
 
+#if OCTAGON
+
 #import "CKKSGroupOperation.h"
 #include <utilities/debugging.h>
 
 }
 
 - (void)runBeforeGroupFinished: (NSOperation*) suboperation {
-    if([self isCancelled]) {
-        // Cancelled operations can't add anything.
-        secnotice("ckksgroup", "Not adding operation to cancelled group %@", self);
-        return;
-    }
 
     // op must wait for this operation to start
     [suboperation addDependency: self.startOperation];
 }
 
 @end
+
+#endif // OCTAGON
+
index 4f74072d2f265ea62608d4487d6f526b9280ed58..496f833bc7602f3b1096eea74a1aaf67db6c3d83 100644 (file)
@@ -36,5 +36,4 @@
 
 @end
 
-#endif // OCTAGON
-
+#endif  // OCTAGON
index 52df80c46b7070cd899392c029bf801bbfd276fd..130da74ec887c2ddf13cfa2254c8c4eda0fa1a61 100644 (file)
                     [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:[NSError errorWithDomain: @"securityd" code:0 userInfo:@{NSLocalizedDescriptionKey: @"couldn't create new classA key", NSUnderlyingErrorKey: error}]];
                 }
 
+                keyset.classA = newClassAKey;
                 keyset.currentClassAPointer.currentKeyUUID = newClassAKey.uuid;
                 changedCurrentClassA = true;
             }
                     [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:[NSError errorWithDomain: @"securityd" code:0 userInfo:@{NSLocalizedDescriptionKey: @"couldn't create new classC key", NSUnderlyingErrorKey: error}]];
                 }
 
+                keyset.classC = newClassCKey;
                 keyset.currentClassCPointer.currentKeyUUID = newClassCKey.uuid;
                 changedCurrentClassC = true;
             }
             return true;
         }
 
-        [keyset.tlk loadKeyMaterialFromKeychain:&error];
+        // Check if CKKS can recover this TLK.
+        [ckks _onqueueWithAccountKeysCheckTLK:keyset.tlk error:&error];
         if(error && [ckks.lockStateTracker isLockedError:error]) {
-            ckksnotice("ckkskey", ckks, "Failed to load TLK from keychain, keybag is locked. Entering WaitForUnlock: %@", error);
+            ckksnotice("ckkskey", ckks, "Failed to load TLK from keychain, keybag is locked. Entering waitforunlock: %@", error);
             [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
             return false;
         } else if(error) {
-            ckkserror("ckksheal", ckks, "No TLK in keychain, triggering move to bad state: %@", error);
+            ckkserror("ckksheal", ckks, "CKKS wasn't sure about TLK, triggering move to bad state: %@", error);
             [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateWaitForTLK withError: nil];
             return false;
         }
 
+        if(![self ensureKeyPresent:keyset.tlk]) {
+            return false;
+        }
+
         if(![self ensureKeyPresent:keyset.classA]) {
             return false;
         }
 
     [key loadKeyMaterialFromKeychain:&error];
     if(error) {
-        ckkserror("ckksheal", ckks, "Couldn't load classC key from keychain. Attempting recovery: %@", error);
+        ckkserror("ckksheal", ckks, "Couldn't load key(%@) from keychain. Attempting recovery: %@", key, error);
         error = nil;
         [key unwrapViaKeyHierarchy: &error];
         if(error) {
-            ckkserror("ckksheal", ckks, "Couldn't unwrap class C key using key hierarchy. Keys are broken, quitting: %@", error);
+            ckkserror("ckksheal", ckks, "Couldn't unwrap key(%@) using key hierarchy. Keys are broken, quitting: %@", key, error);
             [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
             self.error = error;
             return false;
         }
         [key saveKeyMaterialToKeychain:&error];
         if(error) {
-            ckkserror("ckksheal", ckks, "Couldn't save class C key to keychain: %@", error);
+            ckkserror("ckksheal", ckks, "Couldn't save key(%@) to keychain: %@", key, error);
             [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
             self.error = error;
             return false;
index 5d1e295504933f37b1d6ac9111a9a7132cced40f..4786d6ea0bca4608f362427c7b466a393e367e5e 100644 (file)
@@ -32,8 +32,7 @@
 @property (weak) CKKSKeychainView* ckks;
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks
-                        ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
 @end
 
-#endif // OCTAGON
+#endif  // OCTAGON
index 97d0cb2d2bc1ac68b81352c1fccde5d5711229a5..be883bd4cf7ad4c500c2e7413ae06c23547c4654 100644 (file)
@@ -30,6 +30,8 @@
 #import "keychain/ckks/CKKSGroupOperation.h"
 #import "keychain/ckks/CKKSTLKShare.h"
 
+#import "CKKSPowerCollection.h"
+
 @interface CKKSHealTLKSharesOperation ()
 @property NSBlockOperation* cloudkitModifyOperationFinished;
 @property CKOperationGroup* ckoperationGroup;
             ckksnotice("ckksshare", ckks, "Key set is %@", keyset);
         }
 
+        //[CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventTLKShareProcessing zone:ckks.zoneName];
+
         // Okay! Perform the checks.
         if(![keyset.tlk loadKeyMaterialFromKeychain:&error] || error) {
             // Well, that's no good. We can't share a TLK we don't have.
             if([ckks.lockStateTracker isLockedError: error]) {
                 ckkserror("ckksshare", ckks, "Keychain is locked: can't fix shares yet: %@", error);
-                [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
+                [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReadyPendingUnlock withError:nil];
             } else {
                 // TODO go to waitfortlk
                 ckkserror("ckksshare", ckks, "couldn't load current tlk from keychain: %@", error);
index e4787234da776346dbc5744ceca15105fd4f52a7..1b3971f895df4d9ffef59c17f8af33cbf6c1a1e1 100644 (file)
 
 #if OCTAGON
 
-#import "CKKSSQLDatabaseObject.h"
+#import <CloudKit/CloudKit.h>
+#include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
 #import "CKKSItem.h"
 #import "CKKSMirrorEntry.h"
-#include <utilities/SecDb.h>
-#include <securityd/SecDbItem.h>
-
-#ifndef CKKSIncomingQueueEntry_h
-#define CKKSIncomingQueueEntry_h
-
+#import "CKKSSQLDatabaseObject.h"
 
-#import <CloudKit/CloudKit.h>
+NS_ASSUME_NONNULL_BEGIN
 
 @interface CKKSIncomingQueueEntry : CKKSSQLDatabaseObject
 
 @property CKKSItem* item;
-@property NSString* uuid; // through-access to underlying item
+@property NSString* uuid;  // through-access to underlying item
 
 @property NSString* action;
 @property NSString* state;
 
-- (instancetype) initWithCKKSItem:(CKKSItem*) ckme
-                           action:(NSString*) action
-                            state:(NSString*) state;
+- (instancetype)initWithCKKSItem:(CKKSItem*)ckme action:(NSString*)action state:(NSString*)state;
 
-+ (instancetype) fromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype _Nullable)fromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype _Nullable)tryFromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
-+ (NSArray<CKKSIncomingQueueEntry*>*)fetch:(ssize_t)n
-                            startingAtUUID:(NSString*)uuid
-                                     state:(NSString*)state
-                                    zoneID:(CKRecordZoneID*)zoneID
-                                     error: (NSError * __autoreleasing *) error;
++ (NSArray<CKKSIncomingQueueEntry*>* _Nullable)fetch:(ssize_t)n
+                                      startingAtUUID:(NSString*)uuid
+                                               state:(NSString*)state
+                                              zoneID:(CKRecordZoneID*)zoneID
+                                               error:(NSError* __autoreleasing*)error;
 
-+ (NSDictionary<NSString*,NSNumber*>*)countsByState:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSDictionary<NSString*, NSNumber*>*)countsByState:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 @end
 
+NS_ASSUME_NONNULL_END
 #endif
-#endif /* CKKSIncomingQueueEntry_h */
index dd2a8a12b29e0fd1b0891288e870cebdba1e8d6a..ff8a6f09a7779d87024163b91f4aaeeb9470c53e 100644 (file)
 // should error if it can't process class A items due to the keychain being locked.
 @property bool errorOnClassAFailure;
 
+@property size_t successfulItemsProcessed;
+@property size_t errorItemsProcessed;
+
 - (instancetype)init NS_UNAVAILABLE;
 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks errorOnClassAFailure:(bool)errorOnClassAFailure;
 
 @end
 
-#endif // OCTAGON
-
+#endif  // OCTAGON
index dd192ba14203889464ce6456f0d501fadc073999..e2e7ecde4556ac3a7470f5b41a9823fc34249098 100644 (file)
@@ -29,6 +29,7 @@
 #import "CKKSKey.h"
 #import "CKKSManifest.h"
 #import "CKKSAnalyticsLogger.h"
+#import "CKKSPowerCollection.h"
 #import "keychain/ckks/CKKSCurrentItemPointer.h"
 
 #include <securityd/SecItemServer.h>
         if ([CKKSManifest shouldSyncManifests]) {
             if (![manifest validateCurrentItem:p withError:&error]) {
                 ckkserror("ckksincoming", ckks, "Unable to validate current item pointer (%@) against manifest (%@)", p, manifest);
-                [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"CKKSManifestCurrentItemPointerValidation" withAttributes:@{CKKSManifestZoneKey : ckks.zoneID.zoneName, CKKSManifestSignerIDKey : manifest.signerID ?: @"no signer", CKKSManifestGenCountKey : @(manifest.generationCount)}];
                 if ([CKKSManifest shouldEnforceManifests]) {
                     return false;
                 }
             }
-            else {
-                [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestCurrentItemPointerValidation"];
-            }
         }
 
         p.state = SecCKKSProcessedStateLocal;
 
     NSMutableArray* newOrChangedRecords = [[NSMutableArray alloc] init];
     NSMutableArray* deletedRecordIDs = [[NSMutableArray alloc] init];
-    NSInteger manifestGenerationCount = manifest.generationCount;
-    NSString* manifestSignerID = manifest.signerID ?: @"no signer";
 
     for(id entry in queueEntries) {
         if(self.cancelled) {
                 ckkserror("ckksincoming", ckks, "Couldn't decrypt IQE %@ for some reason: %@", iqe, error);
                 self.error = error;
             }
+            self.errorItemsProcessed += 1;
             continue;
         }
 
                                              code:errSecInternalError
                                          userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Item did not have a reasonable class: %@", classStr]}];
             ckkserror("ckksincoming", ckks, "Synced item seems wrong: %@", self.error);
+            self.errorItemsProcessed += 1;
             continue;
         }
 
                 ckkserror("ckksincoming", ckks, "Couldn't save errored IQE to database: %@", error);
                 self.error = error;
             }
+            self.errorItemsProcessed += 1;
             continue;
         }
 
         if([iqe.action isEqualToString: SecCKKSActionAdd] || [iqe.action isEqualToString: SecCKKSActionModify]) {
             BOOL requireManifestValidation = [CKKSManifest shouldEnforceManifests];
             BOOL manifestValidatesItem = [manifest validateItem:iqe.item withError:&error];
-            if (manifestValidatesItem) {
-                [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestValidateItemAdd"];
-            }
-            else {
-                [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"CKKSManifestValidateItemAdd" withAttributes:@{CKKSManifestZoneKey : ckks.zoneID.zoneName, CKKSManifestSignerIDKey : manifestSignerID, CKKSManifestGenCountKey : @(manifestGenerationCount)}];
-            }
-            
+
             if (!requireManifestValidation || manifestValidatesItem) {
                 [self _onqueueHandleIQEChange: iqe attributes:attributes class:classP];
                 [newOrChangedRecords addObject:[iqe.item CKRecordWithZoneID:ckks.zoneID]];
                     ckkserror("ckksincoming", ckks, "failed to save incoming item back to database in unauthenticated state with error: %@", error);
                     return false;
                 }
-                
+                self.errorItemsProcessed += 1;
                 continue;
             }
         } else if ([iqe.action isEqualToString: SecCKKSActionDelete]) {
             BOOL requireManifestValidation = [CKKSManifest shouldEnforceManifests];
             BOOL manifestValidatesDelete = ![manifest itemUUIDExistsInManifest:iqe.uuid];
-            if (manifestValidatesDelete) {
-                [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestValidateItemDelete"];
-            }
-            else {
-                [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"CKKSManifestValidateItemDelete" withAttributes:@{CKKSManifestZoneKey : ckks.zoneID.zoneName, CKKSManifestSignerIDKey : manifestSignerID, CKKSManifestGenCountKey : @(manifestGenerationCount)}];
-            }
             
             if (!requireManifestValidation || manifestValidatesDelete) {
                 // if the item does not exist in the latest manifest, we're good to delete it
                 ckkserror("ckksincoming", ckks, "could not validate incoming item deletion against manifest");
                 if (![self _onqueueUpdateIQE:iqe withState:SecCKKSStateUnauthenticated error:&error]) {
                     ckkserror("ckksincoming", ckks, "failed to save incoming item deletion back to database in unauthenticated state with error: %@", error);
+
+                    self.errorItemsProcessed += 1;
                     return false;
                 }
             }
         // Iterate through all incoming queue entries a chunk at a time (for peak memory concerns)
         NSArray<CKKSIncomingQueueEntry*> * queueEntries = nil;
         NSString* lastMaxUUID = nil;
-        NSUInteger processedItems = 0;
         while(queueEntries == nil || queueEntries.count == SecCKKSIncomingQueueItemsAtOnce) {
             if(self.cancelled) {
                 ckksnotice("ckksincoming", ckks, "CKKSIncomingQueueOperation cancelled, quitting");
                 break;
             }
 
+            //[CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventOutgoingQueue zone:ckks.zoneName count:[queueEntries count]];
+
             if (![self processQueueEntries:queueEntries withManifest:ckks.latestManifest egoManifest:ckks.egoManifest]) {
                 ckksnotice("ckksincoming", ckks, "processQueueEntries didn't complete successfully");
                 return false;
             }
-            processedItems += [queueEntries count];
 
             // Find the highest UUID for the next fetch.
             for(CKKSIncomingQueueEntry* iqe in queueEntries) {
         }
 
         // Process other queues: CKKSCurrentItemPointers
-        ckksnotice("ckksincoming", ckks, "Processed %lu items in incoming queue", processedItems);
+        ckksnotice("ckksincoming", ckks, "Processed %lu items in incoming queue (%lu errors)", self.successfulItemsProcessed, self.errorItemsProcessed);
 
         NSArray<CKKSCurrentItemPointer*>* newCIPs = [CKKSCurrentItemPointer remoteItemPointers:ckks.zoneID error:&error];
         if(error || !newCIPs) {
                 return;
             }
 
+            CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
+
             if (!strongSelf.error) {
-                CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
                 [logger logSuccessForEvent:CKKSEventProcessIncomingQueueClassC inView:ckks];
                 if (!strongSelf.pendingClassAEntries) {
                     [logger logSuccessForEvent:CKKSEventProcessIncomingQueueClassA inView:ckks];
                 }
+            } else {
+                [logger logRecoverableError:strongSelf.error
+                                   forEvent:CKKSEventProcessIncomingQueueClassA
+                                     inView:strongSelf.ckks
+                             withAttributes:NULL];
             }
         };
 
         if(error) {
             ckkserror("ckksincoming", ckks, "couldn't delete CKKSIncomingQueueEntry: %@", error);
             self.error = error;
+            self.errorItemsProcessed += 1;
+        } else {
+            self.successfulItemsProcessed += 1;
         }
 
         if(moddate) {
             ckkserror("ckksincoming", ckks, "Couldn't save errored IQE to database: %@", error);
             self.error = error;
         }
+
+        self.errorItemsProcessed += 1;
     }
 }
 
         if(error) {
             ckkserror("ckksincoming", ckks, "couldn't delete CKKSIncomingQueueEntry: %@", error);
             self.error = error;
+            self.errorItemsProcessed += 1;
+        } else {
+            self.successfulItemsProcessed += 1;
         }
     } else {
         ckkserror("ckksincoming", ckks, "IQE not correctly processed, but why? %@ %@", error, cferror);
         self.error = error;
+        self.errorItemsProcessed += 1;
     }
 }
 
index a0aeb0674e4a3e8a3c64fde6242b825e86dc57a7..6cd0088d120b1de70310beee14ea6b1d191cd1c3 100644 (file)
 
 #if OCTAGON
 
+#import <CloudKit/CloudKit.h>
+#include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
 #import "keychain/ckks/CKKS.h"
-#import "keychain/ckks/CKKSSQLDatabaseObject.h"
 #import "keychain/ckks/CKKSRecordHolder.h"
-#include <utilities/SecDb.h>
-#include <securityd/SecDbItem.h>
-
-#ifndef CKKSItem_h
-#define CKKSItem_h
+#import "keychain/ckks/CKKSSQLDatabaseObject.h"
 
-#import <CloudKit/CloudKit.h>
+NS_ASSUME_NONNULL_BEGIN
 
 @class CKKSWrappedAESSIVKey;
 
-
 // Helper base class that includes UUIDs and key information
-@interface CKKSItem : CKKSCKRecordHolder {
-
-}
+@interface CKKSItem : CKKSCKRecordHolder
 
 @property (copy) NSString* uuid;
 @property (copy) NSString* parentKeyUUID;
-@property (copy) NSData* encitem;
+@property (nullable, copy) NSData* encitem;
 
-@property (getter=base64Item, setter=setBase64Item:) NSString* base64encitem;
+@property (nullable, getter=base64Item, setter=setBase64Item:) NSString* base64encitem;
 
-@property (copy) CKKSWrappedAESSIVKey* wrappedkey;
+@property (nullable, copy) CKKSWrappedAESSIVKey* wrappedkey;
 @property NSUInteger generationCount;
 @property enum SecCKKSItemEncryptionVersion encver;
 
-@property NSNumber* plaintextPCSServiceIdentifier;
-@property NSData* plaintextPCSPublicKey;
-@property NSData* plaintextPCSPublicIdentity;
+@property (nullable) NSNumber* plaintextPCSServiceIdentifier;
+@property (nullable) NSData* plaintextPCSPublicKey;
+@property (nullable) NSData* plaintextPCSPublicIdentity;
 
-// Used for item encryption and decryption. Attempts to be future-compatible for new CloudKit record fields with an optional olditem field, which may contain a CK record. Any fields in that record that we don't understand will be added to the authenticated data dictionary.
-- (NSDictionary<NSString*, NSData*>*)makeAuthenticatedDataDictionaryUpdatingCKKSItem:(CKKSItem*) olditem encryptionVersion:(SecCKKSItemEncryptionVersion)encversion;
+// Used for item encryption and decryption. Attempts to be future-compatible for new CloudKit record fields with an optional
+// olditem field, which may contain a CK record. Any fields in that record that we don't understand will be added to the authenticated data dictionary.
+- (NSDictionary<NSString*, NSData*>*)makeAuthenticatedDataDictionaryUpdatingCKKSItem:(CKKSItem* _Nullable)olditem
+                                                                   encryptionVersion:(SecCKKSItemEncryptionVersion)encversion;
 
 
-- (instancetype) initWithCKRecord: (CKRecord*) record;
-- (instancetype) initCopyingCKKSItem: (CKKSItem*) item;
+- (instancetype)initWithCKRecord:(CKRecord*)record;
+- (instancetype)initCopyingCKKSItem:(CKKSItem*)item;
 
 // Use this one if you really don't have any more information
-- (instancetype) initWithUUID: (NSString*) uuid
-                parentKeyUUID: (NSString*) parentKeyUUID
-                       zoneID: (CKRecordZoneID*) zoneID;
+- (instancetype)initWithUUID:(NSString*)uuid parentKeyUUID:(NSString*)parentKeyUUID zoneID:(CKRecordZoneID*)zoneID;
 
 // Use this one if you don't have a CKRecord yet
-- (instancetype) initWithUUID: (NSString*) uuid
-                parentKeyUUID: (NSString*) parentKeyUUID
-                       zoneID: (CKRecordZoneID*) zoneID
-                      encItem: (NSData*) encitem
-                   wrappedkey: (CKKSWrappedAESSIVKey*) wrappedkey
-              generationCount: (NSUInteger) genCount
-                       encver: (NSUInteger) encver;
-
-- (instancetype) initWithUUID: (NSString*) uuid
-                parentKeyUUID: (NSString*) parentKeyUUID
-                       zoneID: (CKRecordZoneID*)zoneID
-              encodedCKRecord: (NSData*) encodedrecord
-                      encItem: (NSData*) encitem
-                   wrappedkey: (CKKSWrappedAESSIVKey*) wrappedkey
-              generationCount: (NSUInteger) genCount
-                       encver: (NSUInteger) encver;
-
-- (instancetype) initWithUUID: (NSString*) uuid
-                parentKeyUUID: (NSString*) parentKeyUUID
-                       zoneID: (CKRecordZoneID*)zoneID
-              encodedCKRecord: (NSData*) encodedrecord
-                      encItem: (NSData*) encitem
-                   wrappedkey: (CKKSWrappedAESSIVKey*) wrappedkey
-              generationCount: (NSUInteger) genCount
-                       encver: (NSUInteger) encver
-plaintextPCSServiceIdentifier: (NSNumber*) pcsServiceIdentifier
-        plaintextPCSPublicKey: (NSData*) pcsPublicKey
-   plaintextPCSPublicIdentity: (NSData*) pcsPublicIdentity;
+- (instancetype)initWithUUID:(NSString*)uuid
+               parentKeyUUID:(NSString*)parentKeyUUID
+                      zoneID:(CKRecordZoneID*)zoneID
+                     encItem:(NSData* _Nullable)encitem
+                  wrappedkey:(CKKSWrappedAESSIVKey* _Nullable)wrappedkey
+             generationCount:(NSUInteger)genCount
+                      encver:(NSUInteger)encver;
+
+- (instancetype)initWithUUID:(NSString*)uuid
+               parentKeyUUID:(NSString*)parentKeyUUID
+                      zoneID:(CKRecordZoneID*)zoneID
+             encodedCKRecord:(NSData* _Nullable)encodedrecord
+                     encItem:(NSData* _Nullable)encitem
+                  wrappedkey:(CKKSWrappedAESSIVKey* _Nullable)wrappedkey
+             generationCount:(NSUInteger)genCount
+                      encver:(NSUInteger)encver;
+
+- (instancetype)initWithUUID:(NSString*)uuid
+                    parentKeyUUID:(NSString*)parentKeyUUID
+                           zoneID:(CKRecordZoneID*)zoneID
+                  encodedCKRecord:(NSData* _Nullable)encodedrecord
+                          encItem:(NSData* _Nullable)encitem
+                       wrappedkey:(CKKSWrappedAESSIVKey* _Nullable)wrappedkey
+                  generationCount:(NSUInteger)genCount
+                           encver:(NSUInteger)encver
+    plaintextPCSServiceIdentifier:(NSNumber* _Nullable)pcsServiceIdentifier
+            plaintextPCSPublicKey:(NSData* _Nullable)pcsPublicKey
+       plaintextPCSPublicIdentity:(NSData* _Nullable)pcsPublicIdentity;
 
 // Convenience function: set the upload version for this record to be the current OS version
-+ (void)setOSVersionInRecord: (CKRecord*) record;
++ (void)setOSVersionInRecord:(CKRecord*)record;
 
 
 @end
 
 @interface CKKSSQLDatabaseObject (CKKSZoneExtras)
-// Convenience function: get all UUIDs of this type
-+ (NSArray<NSString*>*) allUUIDs:  (NSError * __autoreleasing *) error;
+// Convenience function: get all UUIDs of this type on this particular zone
++ (NSArray<NSString*>*)allUUIDs:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error;
 
 // Convenience function: get all objects in this particular zone
-+ (NSArray*) all:(CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error;
++ (NSArray*)all:(CKRecordZoneID*)zoneID error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
 // Convenience function: delete all records of this type with this zoneID
-+ (bool) deleteAll:(CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error;
++ (bool)deleteAll:(CKRecordZoneID*)zoneID error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 @end
 
+NS_ASSUME_NONNULL_END
 #endif
-#endif /* CKKSItem_H */
index c883c189f3d0f2e455cf7e1e50c382d731cd494f..2b9dde4e8e8fde0230c336f4f81ee412b01131ef 100644 (file)
@@ -33,6 +33,7 @@
 #include <securityd/SecDbItem.h>
 #include <securityd/SecItemSchema.h>
 
+#include <sys/sysctl.h>
 #import <CloudKit/CloudKit.h>
 #import <CloudKit/CloudKit_Private.h>
 
@@ -199,11 +200,35 @@ plaintextPCSServiceIdentifier: (NSNumber*) pcsServiceIdentifier
     NSString* platform = "unknown";
 #warning No PLATFORM defined; why?
 #endif
-    NSString* osversion = [[NSProcessInfo processInfo]operatingSystemVersionString];
 
-    // subtly improve osversion (but it's okay if that does nothing)
-    NSString* finalversion = [platform stringByAppendingString: [osversion stringByReplacingOccurrencesOfString:@"Version" withString:@""]];
-    record[SecCKRecordHostOSVersionKey] = finalversion;
+    NSString* osversion = nil;
+
+    // If we can get the build information from sysctl, use it.
+    char release[256];
+    size_t releasesize = sizeof(release);
+    bool haveSysctlInfo = true;
+    haveSysctlInfo &= (0 == sysctlbyname("kern.osrelease", release, &releasesize, NULL, 0));
+
+    char version[256];
+    size_t versionsize = sizeof(version);
+    haveSysctlInfo &= (0 == sysctlbyname("kern.osversion", version, &versionsize, NULL, 0));
+
+    if(haveSysctlInfo) {
+        // Null-terminate for extra safety
+        release[sizeof(release)-1] = '\0';
+        version[sizeof(version)-1] = '\0';
+        osversion = [NSString stringWithFormat:@"%s (%s)", release, version];
+    }
+
+    if(!osversion) {
+        //  Otherwise, use the not-really-supported fallback.
+        osversion = [[NSProcessInfo processInfo] operatingSystemVersionString];
+
+        // subtly improve osversion (but it's okay if that does nothing)
+        osversion = [osversion stringByReplacingOccurrencesOfString:@"Version" withString:@""];
+    }
+
+     record[SecCKRecordHostOSVersionKey] = [NSString stringWithFormat:@"%@ %@", platform, osversion];
 }
 
 - (CKRecord*) updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID {
@@ -478,11 +503,11 @@ plaintextPCSServiceIdentifier: (NSNumber*) pcsServiceIdentifier
 
 @implementation CKKSSQLDatabaseObject (CKKSZoneExtras)
 
-+ (NSArray<NSString*>*) allUUIDs:  (NSError * __autoreleasing *) error {
++ (NSArray<NSString*>*)allUUIDs:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error {
     __block NSMutableArray* uuids = [[NSMutableArray alloc] init];
 
     [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
-                                        where: nil
+                                        where:@{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)}
                                       columns: @[@"UUID"]
                                       groupBy: nil
                                       orderBy:nil
index dc103ef51ad56e67110ef1e4df35a1a1964e0328..aa47769886b5c524e6cd45f9ec11b8eb0f4810f4 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#ifndef CKKSItemEncrypter_h
-#define CKKSItemEncrypter_h
+#if OCTAGON
 
 #include <securityd/SecDbItem.h>
 
-#if OCTAGON
-
 @class CKKSItem;
 @class CKKSMirrorEntry;
 @class CKKSKey;
 @class CKKSAESSIVKey;
 @class CKRecordZoneID;
 
-#define CKKS_PADDING_MARK_BYTE 0x80
+NS_ASSUME_NONNULL_BEGIN
 
-@interface CKKSItemEncrypter : NSObject {
+#define CKKS_PADDING_MARK_BYTE 0x80
 
-}
+@interface CKKSItemEncrypter : NSObject
 
-+(CKKSItem*)encryptCKKSItem:(CKKSItem*)baseitem
-             dataDictionary:(NSDictionary *)dict
-           updatingCKKSItem:(CKKSItem*)olditem
-                  parentkey:(CKKSKey *)parentkey
-                      error:(NSError * __autoreleasing *) error;
++ (CKKSItem* _Nullable)encryptCKKSItem:(CKKSItem*)baseitem
+                        dataDictionary:(NSDictionary*)dict
+                      updatingCKKSItem:(CKKSItem* _Nullable)olditem
+                             parentkey:(CKKSKey*)parentkey
+                                 error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
-+ (NSDictionary*) decryptItemToDictionary: (CKKSItem*) item error: (NSError * __autoreleasing *) error;
++ (NSDictionary* _Nullable)decryptItemToDictionary:(CKKSItem*)item error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
-+ (NSData*) encryptDictionary: (NSDictionary*) dict key:  (CKKSAESSIVKey*) key authenticatedData: (NSDictionary<NSString*, NSData*>*) ad  error: (NSError * __autoreleasing *) error;
-+ (NSDictionary*) decryptDictionary: (NSData*) encitem key: (CKKSAESSIVKey*) key authenticatedData: (NSDictionary<NSString*, NSData*>*) ad  error: (NSError * __autoreleasing *) error;
++ (NSData* _Nullable)encryptDictionary:(NSDictionary*)dict
+                                   key:(CKKSAESSIVKey*)key
+                     authenticatedData:(NSDictionary<NSString*, NSData*>* _Nullable)ad
+                                 error:(NSError* _Nullable __autoreleasing* _Nullable)error;
++ (NSDictionary* _Nullable)decryptDictionary:(NSData*)encitem
+                                         key:(CKKSAESSIVKey*)key
+                           authenticatedData:(NSDictionary<NSString*, NSData*>* _Nullable)ad
+                                       error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
-+ (NSData *)padData:(NSData *)input blockSize:(NSUInteger)blockSize additionalBlock:(BOOL)extra;
-+ (NSData *)removePaddingFromData:(NSData *)input;
++ (NSData*)padData:(NSData*)input blockSize:(NSUInteger)blockSize additionalBlock:(BOOL)extra;
++ (NSData* _Nullable)removePaddingFromData:(NSData*)input;
 @end
 
-#endif // OCTAGON
-
-#endif /* CKKSItemEncrypter_h */
-
+NS_ASSUME_NONNULL_END
+#endif  // OCTAGON
index 5fe452bf1e3fb122f61800e69132799ed97c6903..ae5842fd287595a6d8889405a14ceddcd79f5e2b 100644 (file)
@@ -28,8 +28,8 @@
 #import "keychain/ckks/CKKSItem.h"
 #import "keychain/ckks/CKKSSIV.h"
 
-#import "keychain/ckks/proto/source/CKKSSerializedKey.h"
 #import "keychain/ckks/CKKSPeer.h"
+#import "keychain/ckks/proto/source/CKKSSerializedKey.h"
 
 @interface CKKSKey : CKKSItem
 
 @property bool currentkey;
 
 // Fetches and attempts to unwrap this key for use
-+ (instancetype) loadKeyWithUUID: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)loadKeyWithUUID:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 // Creates new random keys, in the parent's zone
-+ (instancetype) randomKeyWrappedByParent: (CKKSKey*) parentKey error: (NSError * __autoreleasing *) error;
-+ (instancetype) randomKeyWrappedByParent: (CKKSKey*) parentKey keyclass:(CKKSKeyClass*)keyclass error: (NSError * __autoreleasing *) error;
++ (instancetype)randomKeyWrappedByParent:(CKKSKey*)parentKey error:(NSError* __autoreleasing*)error;
++ (instancetype)randomKeyWrappedByParent:(CKKSKey*)parentKey
+                                keyclass:(CKKSKeyClass*)keyclass
+                                   error:(NSError* __autoreleasing*)error;
 
 // Creates a new random key that wraps itself
-+ (instancetype)randomKeyWrappedBySelf: (CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)randomKeyWrappedBySelf:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 /* Helper functions for persisting key material in the keychain */
-- (bool)saveKeyMaterialToKeychain: (NSError * __autoreleasing *) error;
-- (bool)saveKeyMaterialToKeychain: (bool)stashTLK error:(NSError * __autoreleasing *) error; // call this to not stash a non-syncable TLK, if that's what you want
-    
-- (bool)loadKeyMaterialFromKeychain: (NSError * __autoreleasing *) error;
-- (bool)deleteKeyMaterialFromKeychain: (NSError * __autoreleasing *) error;
-+ (NSString*)isItemKeyForKeychainView: (SecDbItemRef) item;
+- (bool)saveKeyMaterialToKeychain:(NSError* __autoreleasing*)error;
+- (bool)saveKeyMaterialToKeychain:(bool)stashTLK
+                            error:(NSError* __autoreleasing*)error;  // call this to not stash a non-syncable TLK, if that's what you want
+
+- (bool)loadKeyMaterialFromKeychain:(NSError* __autoreleasing*)error;
+- (bool)deleteKeyMaterialFromKeychain:(NSError* __autoreleasing*)error;
++ (NSString*)isItemKeyForKeychainView:(SecDbItemRef)item;
 
 // Class methods to help tests
-+ (bool)saveKeyMaterialToKeychain:(CKKSKey*)key stashTLK:(bool)stashTLK error:(NSError * __autoreleasing *) error;
-+ (NSData*)loadKeyMaterialFromKeychain:(CKKSKey*)key resave:(bool*)resavePtr error:(NSError* __autoreleasing *) error;
++ (NSDictionary*)setKeyMaterialInKeychain:(NSDictionary*)query error:(NSError* __autoreleasing*)error;
++ (NSDictionary*)queryKeyMaterialInKeychain:(NSDictionary*)query error:(NSError* __autoreleasing*)error;
 
-+ (instancetype)keyFromKeychain: (NSString*) uuid
-                  parentKeyUUID: (NSString*) parentKeyUUID
-                       keyclass: (CKKSKeyClass*)keyclass
-                          state: (CKKSProcessedState*) state
-                         zoneID: (CKRecordZoneID*) zoneID
-                encodedCKRecord: (NSData*) encodedrecord
-                     currentkey: (NSInteger) currentkey
-                          error: (NSError * __autoreleasing *) error;
++ (instancetype)keyFromKeychain:(NSString*)uuid
+                  parentKeyUUID:(NSString*)parentKeyUUID
+                       keyclass:(CKKSKeyClass*)keyclass
+                          state:(CKKSProcessedState*)state
+                         zoneID:(CKRecordZoneID*)zoneID
+                encodedCKRecord:(NSData*)encodedrecord
+                     currentkey:(NSInteger)currentkey
+                          error:(NSError* __autoreleasing*)error;
 
 
-+ (instancetype) fromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabaseAnyState: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)fromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabaseAnyState:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
-+ (NSArray<CKKSKey*>*) selfWrappedKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSArray<CKKSKey*>*)selfWrappedKeys:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
-+ (instancetype)currentKeyForClass: (CKKSKeyClass*) keyclass zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (NSArray<CKKSKey*>*)currentKeysForClass: (CKKSKeyClass*) keyclass state:(CKKSProcessedState*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)currentKeyForClass:(CKKSKeyClass*)keyclass zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSKey*>*)currentKeysForClass:(CKKSKeyClass*)keyclass
+                                    state:(CKKSProcessedState*)state
+                                   zoneID:(CKRecordZoneID*)zoneID
+                                    error:(NSError* __autoreleasing*)error;
 
-+ (NSArray<CKKSKey*>*)allKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (NSArray<CKKSKey*>*)remoteKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (NSArray<CKKSKey*>*)localKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSArray<CKKSKey*>*)allKeys:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSKey*>*)remoteKeys:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSKey*>*)localKeys:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
-- (bool)saveToDatabaseAsOnlyCurrentKeyForClassAndState: (NSError * __autoreleasing *) error;
+- (bool)saveToDatabaseAsOnlyCurrentKeyForClassAndState:(NSError* __autoreleasing*)error;
 
 - (instancetype)init NS_UNAVAILABLE;
 
-- (instancetype) initSelfWrappedWithAESKey: (CKKSAESSIVKey*) aeskey
-                                      uuid: (NSString*) uuid
-                                  keyclass: (CKKSKeyClass*)keyclass
-                                     state: (CKKSProcessedState*) state
-                                    zoneID: (CKRecordZoneID*) zoneID
-                           encodedCKRecord: (NSData*) encodedrecord
-                                currentkey: (NSInteger) currentkey;
-
-- (instancetype) initWrappedBy: (CKKSKey*) wrappingKey
-                        AESKey: (CKKSAESSIVKey*) aeskey
-                          uuid: (NSString*) uuid
-                      keyclass: (CKKSKeyClass*)keyclass
-                         state: (CKKSProcessedState*) state
-                        zoneID: (CKRecordZoneID*) zoneID
-               encodedCKRecord: (NSData*) encodedrecord
-                    currentkey: (NSInteger) currentkey;
-
-- (instancetype) initWithWrappedAESKey: (CKKSWrappedAESSIVKey*) wrappedaeskey
-                                  uuid: (NSString*) uuid
-                         parentKeyUUID: (NSString*) parentKeyUUID
-                              keyclass: (CKKSKeyClass*)keyclass
-                                 state: (CKKSProcessedState*) state
-                                zoneID: (CKRecordZoneID*) zoneID
-                       encodedCKRecord: (NSData*) encodedrecord
-                            currentkey: (NSInteger) currentkey;
+- (instancetype)initSelfWrappedWithAESKey:(CKKSAESSIVKey*)aeskey
+                                     uuid:(NSString*)uuid
+                                 keyclass:(CKKSKeyClass*)keyclass
+                                    state:(CKKSProcessedState*)state
+                                   zoneID:(CKRecordZoneID*)zoneID
+                          encodedCKRecord:(NSData*)encodedrecord
+                               currentkey:(NSInteger)currentkey;
+
+- (instancetype)initWrappedBy:(CKKSKey*)wrappingKey
+                       AESKey:(CKKSAESSIVKey*)aeskey
+                         uuid:(NSString*)uuid
+                     keyclass:(CKKSKeyClass*)keyclass
+                        state:(CKKSProcessedState*)state
+                       zoneID:(CKRecordZoneID*)zoneID
+              encodedCKRecord:(NSData*)encodedrecord
+                   currentkey:(NSInteger)currentkey;
+
+- (instancetype)initWithWrappedAESKey:(CKKSWrappedAESSIVKey*)wrappedaeskey
+                                 uuid:(NSString*)uuid
+                        parentKeyUUID:(NSString*)parentKeyUUID
+                             keyclass:(CKKSKeyClass*)keyclass
+                                state:(CKKSProcessedState*)state
+                               zoneID:(CKRecordZoneID*)zoneID
+                      encodedCKRecord:(NSData*)encodedrecord
+                           currentkey:(NSInteger)currentkey;
 
 /* Returns true if we believe this key wraps itself. */
 - (bool)wrapsSelf;
 
 - (void)zeroKeys;
 
-- (CKKSKey*)topKeyInAnyState: (NSError * __autoreleasing *) error;
+- (CKKSKey*)topKeyInAnyState:(NSError* __autoreleasing*)error;
 
 // Attempts checks if the AES key is already loaded, or attempts to load it from the keychain. Returns false if it fails.
-- (CKKSAESSIVKey*)ensureKeyLoaded: (NSError * __autoreleasing *) error;
+- (CKKSAESSIVKey*)ensureKeyLoaded:(NSError* __autoreleasing*)error;
 
 // Attempts to unwrap this key via unwrapping its wrapping keys via the key hierarchy.
-- (CKKSAESSIVKey*)unwrapViaKeyHierarchy: (NSError * __autoreleasing *) error;
+- (CKKSAESSIVKey*)unwrapViaKeyHierarchy:(NSError* __autoreleasing*)error;
 
 // On a self-wrapped key, determine if this AES-SIV key is the self-wrapped key.
 // If it is, save the key as this CKKSKey's unwrapped key.
-- (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate error:(NSError * __autoreleasing *) error;
+- (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate error:(NSError* __autoreleasing*)error;
 
-- (CKKSWrappedAESSIVKey*)wrapAESKey: (CKKSAESSIVKey*) keyToWrap error: (NSError * __autoreleasing *) error;
-- (CKKSAESSIVKey*)unwrapAESKey: (CKKSWrappedAESSIVKey*) keyToUnwrap error: (NSError * __autoreleasing *) error;
+- (CKKSWrappedAESSIVKey*)wrapAESKey:(CKKSAESSIVKey*)keyToWrap error:(NSError* __autoreleasing*)error;
+- (CKKSAESSIVKey*)unwrapAESKey:(CKKSWrappedAESSIVKey*)keyToUnwrap error:(NSError* __autoreleasing*)error;
 
-- (bool)wrapUnder: (CKKSKey*) wrappingKey error: (NSError * __autoreleasing *) error;
-- (bool)unwrapSelfWithAESKey: (CKKSAESSIVKey*) unwrappingKey error: (NSError * __autoreleasing *) error;
+- (bool)wrapUnder:(CKKSKey*)wrappingKey error:(NSError* __autoreleasing*)error;
+- (bool)unwrapSelfWithAESKey:(CKKSAESSIVKey*)unwrappingKey error:(NSError* __autoreleasing*)error;
 
-- (NSData*)encryptData: (NSData*) plaintext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error;
-- (NSData*)decryptData: (NSData*) ciphertext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error;
+- (NSData*)encryptData:(NSData*)plaintext
+     authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
+                 error:(NSError* __autoreleasing*)error;
+- (NSData*)decryptData:(NSData*)ciphertext
+     authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
+                 error:(NSError* __autoreleasing*)error;
 
-- (NSData*)serializeAsProtobuf:(NSError* __autoreleasing *)error;
-+ (CKKSKey*)loadFromProtobuf:(NSData*)data error:(NSError* __autoreleasing *)error;
+- (NSData*)serializeAsProtobuf:(NSError* __autoreleasing*)error;
++ (CKKSKey*)loadFromProtobuf:(NSData*)data error:(NSError* __autoreleasing*)error;
 
-+ (NSDictionary<NSString*,NSNumber*>*)countsByClass:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSDictionary<NSString*, NSNumber*>*)countsByClass:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 @end
 
 #endif
index 231f2c7c595d6693cf495fe2763b5a6d9fd23323..fc10d37a0bb9e9cc78619bbcd89390661815093b 100644 (file)
 }
 
 - (bool)saveKeyMaterialToKeychain: (bool)stashTLK error:(NSError * __autoreleasing *) error {
-    return [CKKSKey saveKeyMaterialToKeychain:self stashTLK:stashTLK error:error];
-}
-
-+(bool)saveKeyMaterialToKeychain:(CKKSKey*)key stashTLK:(bool)stashTLK error:(NSError * __autoreleasing *) error {
 
     // Note that we only store the key class, view, UUID, parentKeyUUID, and key material in the keychain
     // Any other metadata must be stored elsewhere and filled in at load time.
 
-    if(![key ensureKeyLoaded:error]) {
+    if(![self ensureKeyLoaded:error]) {
         // No key material, nothing to save to keychain.
         return false;
     }
 
     // iOS keychains can't store symmetric keys, so we're reduced to storing this key as a password
-    NSData* keydata = [[[NSData alloc] initWithBytes:key.aessivkey->key length:key.aessivkey->size] base64EncodedDataWithOptions:0];
+    NSData* keydata = [[[NSData alloc] initWithBytes:self.aessivkey->key length:self.aessivkey->size] base64EncodedDataWithOptions:0];
     NSMutableDictionary* query = [@{
             (id)kSecClass : (id)kSecClassInternetPassword,
             (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
             (id)kSecAttrNoLegacy : @YES,
             (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
-            (id)kSecAttrDescription: key.keyclass,
-            (id)kSecAttrServer: key.zoneID.zoneName,
-            (id)kSecAttrAccount: key.uuid,
-            (id)kSecAttrPath: key.parentKeyUUID,
+            (id)kSecAttrDescription: self.keyclass,
+            (id)kSecAttrServer: self.zoneID.zoneName,
+            (id)kSecAttrAccount: self.uuid,
+            (id)kSecAttrPath: self.parentKeyUUID,
             (id)kSecAttrIsInvisible: @YES,
             (id)kSecValueData : keydata,
         } mutableCopy];
 
     // Only TLKs are synchronizable. Other keyclasses must synchronize via key hierarchy.
-    if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
+    if([self.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
         // Use PCS-MasterKey view so they'll be initial-synced under SOS.
         query[(id)kSecAttrSyncViewHint] = (id)kSecAttrViewHintPCSMasterKey;
         query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
     }
 
     // Class C keys are accessible after first unlock; TLKs and Class A keys are accessible only when unlocked
-    if([key.keyclass isEqualToString: SecCKKSKeyClassC]) {
+    if([self.keyclass isEqualToString: SecCKKSKeyClassC]) {
         query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleAfterFirstUnlock;
     } else {
         query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleWhenUnlocked;
     }
 
-    OSStatus status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
-
-    if(status == errSecDuplicateItem) {
-        // Sure, okay, fine, we'll update.
-        error = nil;
-        NSMutableDictionary* update = [@{
-                                 (id)kSecValueData: keydata,
-                                 (id)kSecAttrPath: key.parentKeyUUID,
-                                 } mutableCopy];
-        query[(id)kSecValueData] = nil;
-        query[(id)kSecAttrPath] = nil;
-
-        // Udpate the view-hint, too
-        if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
-            update[(id)kSecAttrSyncViewHint] = (id)kSecAttrViewHintPCSMasterKey;
-            query[(id)kSecAttrSyncViewHint] = nil;
-        }
-
-        status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef)update);
-    }
+    NSError* localError = nil;
+    [CKKSKey setKeyMaterialInKeychain:query error:&localError];
 
-    if(status && error) {
+    if(localError && error) {
         *error = [NSError errorWithDomain:@"securityd"
-                                    code:status
-                                userInfo:@{NSLocalizedDescriptionKey:
-                [NSString stringWithFormat:@"Couldn't save %@ to keychain: %d", self, (int)status]}];
+                                    code:localError.code
+                                userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:@"Couldn't save %@ to keychain: %d", self, (int)localError.code],
+                                           NSUnderlyingErrorKey: localError,
+                                           }];
     }
 
     // TLKs are synchronizable. Stash them nonsyncably nearby.
     // Don't report errors here.
-    if(stashTLK && [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
+    if(stashTLK && [self.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
         query = [@{
                    (id)kSecClass : (id)kSecClassInternetPassword,
                    (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
                    (id)kSecAttrNoLegacy : @YES,
                    (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
-                   (id)kSecAttrDescription: [key.keyclass stringByAppendingString: @"-nonsync"],
-                   (id)kSecAttrServer: key.zoneID.zoneName,
-                   (id)kSecAttrAccount: key.uuid,
-                   (id)kSecAttrPath: key.parentKeyUUID,
+                   (id)kSecAttrDescription: [self.keyclass stringByAppendingString: @"-nonsync"],
+                   (id)kSecAttrServer: self.zoneID.zoneName,
+                   (id)kSecAttrAccount: self.uuid,
+                   (id)kSecAttrPath: self.parentKeyUUID,
                    (id)kSecAttrIsInvisible: @YES,
                    (id)kSecValueData : keydata,
                    } mutableCopy];
         query[(id)kSecAttrSynchronizable] = (id)kCFBooleanFalse;
 
-        OSStatus stashstatus = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
-        if(stashstatus != errSecSuccess) {
-            if(stashstatus == errSecDuplicateItem) {
-                // Sure, okay, fine, we'll update.
-                error = nil;
-                NSDictionary* update = @{
-                                         (id)kSecValueData: keydata,
-                                         (id)kSecAttrPath: key.parentKeyUUID,
-                                         };
-                query[(id)kSecValueData] = nil;
-                query[(id)kSecAttrPath] = nil;
-
-                stashstatus = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef)update);
-            }
+        NSError* stashError = nil;
+        [CKKSKey setKeyMaterialInKeychain:query error:&localError];
 
-            if(stashstatus != errSecSuccess) {
-                secerror("ckkskey: Couldn't stash %@ to keychain: %d", self, (int)stashstatus);
-            }
+        if(stashError) {
+            secerror("ckkskey: Couldn't stash %@ to keychain: %@", self, stashError);
+        }
+    }
+
+    return localError == nil;
+}
+
++ (NSDictionary*)setKeyMaterialInKeychain:(NSDictionary*)query error:(NSError* __autoreleasing *)error {
+    CFTypeRef result = NULL;
+    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, &result);
+
+    NSError* localerror = nil;
+
+    // Did SecItemAdd fall over due to an existing item?
+    if(status == errSecDuplicateItem) {
+        // Add every primary key attribute to this find dictionary
+        NSMutableDictionary* findQuery = [[NSMutableDictionary alloc] init];
+        findQuery[(id)kSecClass]              = query[(id)kSecClass];
+        findQuery[(id)kSecAttrSynchronizable] = query[(id)kSecAttrSynchronizable];
+        findQuery[(id)kSecAttrSyncViewHint]   = query[(id)kSecAttrSyncViewHint];
+        findQuery[(id)kSecAttrAccessGroup]    = query[(id)kSecAttrAccessGroup];
+        findQuery[(id)kSecAttrAccount]        = query[(id)kSecAttrAccount];
+        findQuery[(id)kSecAttrServer]         = query[(id)kSecAttrServer];
+        findQuery[(id)kSecAttrPath]           = query[(id)kSecAttrPath];
+
+        NSMutableDictionary* updateQuery = [query mutableCopy];
+        updateQuery[(id)kSecClass] = nil;
+
+        status = SecItemUpdate((__bridge CFDictionaryRef)findQuery, (__bridge CFDictionaryRef)updateQuery);
+
+        if(status) {
+            localerror = [NSError errorWithDomain:@"securityd"
+                                             code:status
+                                      description:[NSString stringWithFormat:@"SecItemUpdate: %d", (int)status]];
+        }
+    } else {
+        localerror = [NSError errorWithDomain:@"securityd"
+                                         code:status
+                                  description: [NSString stringWithFormat:@"SecItemAdd: %d", (int)status]];
+    }
+
+    if(status) {
+        CFReleaseNull(result);
+
+        if(error) {
+            *error = localerror;
         }
+        return false;
     }
 
-    return status == 0;
+    NSDictionary* resultDict = CFBridgingRelease(result);
+    return resultDict;
 }
 
-+ (NSData*)loadKeyMaterialFromKeychain:(CKKSKey*)key resave:(bool*)resavePtr error:(NSError* __autoreleasing *)error {
++ (NSDictionary*)queryKeyMaterialInKeychain:(NSDictionary*)query  error:(NSError* __autoreleasing *)error {
+    CFTypeRef result = NULL;
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
+
+    if(status) {
+        CFReleaseNull(result);
+
+        if(error) {
+            *error = [NSError errorWithDomain:@"securityd"
+                                         code:status
+                                     userInfo:@{NSLocalizedDescriptionKey:
+                                                    [NSString stringWithFormat:@"SecItemCopyMatching: %d", (int)status]}];
+        }
+        return false;
+    }
+
+    NSDictionary* resultDict = CFBridgingRelease(result);
+    return resultDict;
+}
+
++ (NSDictionary*)fetchKeyMaterialItemFromKeychain:(CKKSKey*)key resave:(bool*)resavePtr error:(NSError* __autoreleasing *)error {
     NSMutableDictionary* query = [@{
             (id)kSecClass : (id)kSecClassInternetPassword,
             (id)kSecAttrNoLegacy : @YES,
             (id)kSecAttrDescription: key.keyclass,
             (id)kSecAttrAccount: key.uuid,
             (id)kSecAttrServer: key.zoneID.zoneName,
+            (id)kSecAttrPath: key.parentKeyUUID,
             (id)kSecReturnAttributes: @YES,
             (id)kSecReturnData: @YES,
         } mutableCopy];
         query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
     }
 
-    CFTypeRef result = NULL;
-    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &result);
+    NSError* localError = nil;
+    NSDictionary* result = [self queryKeyMaterialInKeychain:query error:&localError];
+    NSError* originalError = localError;
 
-    if(status == errSecItemNotFound) {
-        CFReleaseNull(result);
+    // If we found the item or errored in some interesting way, return.
+    if(localError == nil) {
+        return result;
+    }
+    if(localError && localError.code != errSecItemNotFound) {
+        if(error) {
+            *error = [NSError errorWithDomain:@"securityd"
+                                         code:localError.code
+                                     userInfo:@{NSLocalizedDescriptionKey:
+                                                    [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d", key, (int)localError.code],
+                                                NSUnderlyingErrorKey: localError,
+                                                }];
+        }
+        return result;
+    }
+    localError = nil;
+
+    if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
         //didn't find a regular tlk?  how about a piggy?
-        if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
-            query = [@{
-                       (id)kSecClass : (id)kSecClassInternetPassword,
-                       (id)kSecAttrNoLegacy : @YES,
-                       (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
-                       (id)kSecAttrDescription: @"tlk-piggy",
-                       (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
-                       (id)kSecAttrAccount: [NSString stringWithFormat: @"%@-piggy", key.uuid],
-                       (id)kSecAttrServer: key.zoneID.zoneName,
-                       (id)kSecReturnAttributes: @YES,
-                       (id)kSecReturnData: @YES,
-                       (id)kSecMatchLimit: (id)kSecMatchLimitOne,
-                       } mutableCopy];
-            status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &result);
-            if(status == errSecSuccess){
-                secnotice("ckkskey", "loaded a piggy TLK (%@)", key.uuid);
-
-                if(resavePtr) {
-                    *resavePtr = true;
-                }
+        query = [@{
+                   (id)kSecClass : (id)kSecClassInternetPassword,
+                   (id)kSecAttrNoLegacy : @YES,
+                   (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
+                   (id)kSecAttrDescription: [key.keyclass stringByAppendingString: @"-piggy"],
+                   (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
+                   (id)kSecAttrAccount: [NSString stringWithFormat: @"%@-piggy", key.uuid],
+                   (id)kSecAttrServer: key.zoneID.zoneName,
+                   (id)kSecReturnAttributes: @YES,
+                   (id)kSecReturnData: @YES,
+                   (id)kSecMatchLimit: (id)kSecMatchLimitOne,
+                   } mutableCopy];
+
+        result = [self queryKeyMaterialInKeychain:query error:&localError];
+        if(localError == nil) {
+            secnotice("ckkskey", "loaded a piggy TLK (%@)", key.uuid);
+
+            if(resavePtr) {
+                *resavePtr = true;
             }
+
+            return result;
+        }
+
+        if(localError && localError.code != errSecItemNotFound) {
+            if(error) {
+                *error = [NSError errorWithDomain:@"securityd"
+                                             code:localError.code
+                                         userInfo:@{NSLocalizedDescriptionKey:
+                                                        [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d", key, (int)localError.code],
+                                                    NSUnderlyingErrorKey: localError,
+                                                    }];
+            }
+            return nil;
         }
     }
-    if(status == errSecItemNotFound && [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
-        CFReleaseNull(result);
+
+    localError = nil;
+
+    // Try to load a stashed TLK
+    if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
+        localError = nil;
 
         // Try to look for the non-syncable stashed tlk and resurrect it.
         query = [@{
             (id)kSecAttrSynchronizable: @NO,
         } mutableCopy];
 
-        status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &result);
-        if(status == errSecSuccess) {
+        result = [self queryKeyMaterialInKeychain:query error:&localError];
+        if(localError == nil) {
             secnotice("ckkskey", "loaded a stashed TLK (%@)", key.uuid);
 
             if(resavePtr) {
                 *resavePtr = true;
             }
-        }
-    }
 
-    if(status){ //still can't find it!
-        if(error) {
-            *error = [NSError errorWithDomain:@"securityd"
-                                         code:status
-                                     userInfo:@{NSLocalizedDescriptionKey:
-                                                    [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d", self, (int)status]}];
+            return result;
         }
-        return false;
-    }
-
-    // Determine if we should fix up any attributes on this item...
-    NSDictionary* resultDict = CFBridgingRelease(result);
 
-    // We created some TLKs with no ViewHint. Fix it.
-    if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
-        NSString* viewHint = resultDict[(id)kSecAttrSyncViewHint];
-        if(!viewHint) {
-            ckksnotice("ckkskey", key.zoneID, "Fixing up non-viewhinted TLK %@", self);
-            query[(id)kSecReturnAttributes] = nil;
-            query[(id)kSecReturnData] = nil;
-
-            NSDictionary* update = @{(id)kSecAttrSyncViewHint: (id)kSecAttrViewHintPCSMasterKey};
-
-            status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
-            if(status) {
-                // Don't report error upwards; this is an optimization fixup.
-                secerror("ckkskey: Couldn't update viewhint on existing TLK %@", self);
+        if(localError && localError.code != errSecItemNotFound) {
+            if(error) {
+                *error = [NSError errorWithDomain:@"securityd"
+                                             code:localError.code
+                                         userInfo:@{NSLocalizedDescriptionKey:
+                                                        [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d", key, (int)localError.code],
+                                                    NSUnderlyingErrorKey: localError,
+                                                    }];
             }
+            return nil;
         }
     }
 
-    // Okay, back to the real purpose of this function: extract the CFData currently in the results dictionary
-    NSData* b64keymaterial = resultDict[(id)kSecValueData];
-    NSData* keymaterial = [[NSData alloc] initWithBase64EncodedData:b64keymaterial options:0];
-    return keymaterial;
+    // We didn't early-return. Use whatever error the original fetch produced.
+    if(error && originalError) {
+        *error = [NSError errorWithDomain:@"securityd"
+                                     code:originalError.code
+                                 userInfo:@{NSLocalizedDescriptionKey:
+                                                [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d", key, (int)originalError.code],
+                                            NSUnderlyingErrorKey: originalError,
+                                            }];
+    }
+
+    return result;
 }
 
 - (bool)loadKeyMaterialFromKeychain: (NSError * __autoreleasing *) error {
     bool resave = false;
-    NSData* keymaterial = [CKKSKey loadKeyMaterialFromKeychain:self resave:&resave error:error];
+    NSDictionary* result = [CKKSKey fetchKeyMaterialItemFromKeychain:self resave:&resave error:error];
+    if(!result) {
+        return false;
+    }
+
+    NSData* b64keymaterial = result[(id)kSecValueData];
+    NSData* keymaterial = [[NSData alloc] initWithBase64EncodedData:b64keymaterial options:0];
     if(!keymaterial) {
         return false;
     }
     return record;
 }
 
+- (bool)matchesCKRecord:(CKRecord*)record {
+    if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) {
+        return false;
+    }
+
+    if(![record.recordID.recordName isEqualToString: self.uuid]) {
+        secinfo("ckkskey", "UUID does not match");
+        return false;
+    }
+
+    // For the parent key ref, ensure that if it's nil, we wrap ourself
+    if(record[SecCKRecordParentKeyRefKey] == nil) {
+        if(![self wrapsSelf]) {
+            secinfo("ckkskey", "wrapping key reference (self-wrapped) does not match");
+            return false;
+        }
+
+    } else {
+        if(![[[record[SecCKRecordParentKeyRefKey] recordID] recordName] isEqualToString: self.parentKeyUUID]) {
+            secinfo("ckkskey", "wrapping key reference (non-self-wrapped) does not match");
+            return false;
+        }
+    }
+
+    if(![record[SecCKRecordKeyClassKey] isEqual: self.keyclass]) {
+        secinfo("ckkskey", "key class does not match");
+        return false;
+    }
+
+    if(![record[SecCKRecordWrappedKeyKey] isEqual: [self.wrappedkey base64WrappedKey]]) {
+        secinfo("ckkskey", "wrapped key does not match");
+        return false;
+    }
+
+    return true;
+}
+
+
 #pragma mark - Utility
 
 - (NSString*)description {
index f10395e0c033b0c14a00e234bf2079c955449b37..b7fe760f1b39acac1c16d2f3b89dc7520271c4a8 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-
-#ifndef CKKSKeychainView_h
-#define CKKSKeychainView_h
-
+#if OCTAGON
 #import <Foundation/Foundation.h>
 #include <dispatch/dispatch.h>
 
-#if OCTAGON
-#import "keychain/ckks/CloudKitDependencies.h"
 #import "keychain/ckks/CKKSAPSReceiver.h"
 #import "keychain/ckks/CKKSLockStateTracker.h"
-#endif
+#import "keychain/ckks/CloudKitDependencies.h"
 
-#include <utilities/SecDb.h>
 #include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
 
 #import "keychain/ckks/CKKS.h"
+#import "keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.h"
+#import "keychain/ckks/CKKSGroupOperation.h"
 #import "keychain/ckks/CKKSIncomingQueueOperation.h"
-#import "keychain/ckks/CKKSOutgoingQueueOperation.h"
 #import "keychain/ckks/CKKSNearFutureScheduler.h"
 #import "keychain/ckks/CKKSNewTLKOperation.h"
+#import "keychain/ckks/CKKSNotifier.h"
+#import "keychain/ckks/CKKSOutgoingQueueOperation.h"
+#import "keychain/ckks/CKKSPeer.h"
 #import "keychain/ckks/CKKSProcessReceivedKeysOperation.h"
 #import "keychain/ckks/CKKSReencryptOutgoingItemsOperation.h"
-#import "keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.h"
 #import "keychain/ckks/CKKSScanLocalItemsOperation.h"
+#import "keychain/ckks/CKKSTLKShare.h"
 #import "keychain/ckks/CKKSUpdateDeviceStateOperation.h"
-#import "keychain/ckks/CKKSGroupOperation.h"
 #import "keychain/ckks/CKKSZone.h"
 #import "keychain/ckks/CKKSZoneChangeFetcher.h"
-#import "keychain/ckks/CKKSNotifier.h"
-#import "keychain/ckks/CKKSPeer.h"
-#import "keychain/ckks/CKKSTLKShare.h"
+#import "keychain/ckks/CKKSSynchronizeOperation.h"
+#import "keychain/ckks/CKKSLocalSynchronizeOperation.h"
 
 #include "CKKS.h"
 
-# if !OCTAGON
-@interface CKKSKeychainView : NSObject {
-    NSString* _containerName;
-}
-@end
-#else // OCTAGON
+NS_ASSUME_NONNULL_BEGIN
 
 @class CKKSKey;
 @class CKKSAESSIVKey;
 @class CKKSOutgoingQueueEntry;
 @class CKKSZoneChangeFetcher;
 
-@interface CKKSKeychainView : CKKSZone <CKKSZoneUpdateReceiver,
-                                        CKKSChangeFetcherErrorOracle,
-                                        CKKSPeerUpdateListener> {
+@interface CKKSKeychainView : CKKSZone <CKKSZoneUpdateReceiver, CKKSChangeFetcherErrorOracle, CKKSPeerUpdateListener>
+{
     CKKSZoneKeyState* _keyHierarchyState;
 }
 
+@property CKKSCondition* loggedIn;
+@property CKKSCondition* loggedOut;
+
 @property CKKSLockStateTracker* lockStateTracker;
 
 @property CKKSZoneKeyState* keyHierarchyState;
-@property NSError* keyHierarchyError;
-@property CKOperationGroup* keyHierarchyOperationGroup;
-@property NSOperation* keyStateMachineOperation;
+@property (nullable) NSError* keyHierarchyError;
+@property (nullable) CKOperationGroup* keyHierarchyOperationGroup;
+@property (nullable) NSOperation* keyStateMachineOperation;
 
 // If the key hierarchy isn't coming together, it might be because we're out of sync with cloudkit.
 // Use this to track if we've completed a full refetch, so fix-up operations can be done.
 @property bool keyStateMachineRefetched;
-@property CKKSEgoManifest* egoManifest;
-@property CKKSManifest* latestManifest;
-@property CKKSResultOperation* keyStateReadyDependency;
+@property (nullable) CKKSEgoManifest* egoManifest;
+@property (nullable) CKKSManifest* latestManifest;
+@property (nullable) CKKSResultOperation* keyStateReadyDependency;
 
-@property (readonly) NSString *lastActiveTLKUUID;
+// True if we believe there's any items in the keychain which haven't been brought up in CKKS yet
+@property bool droppedItems;
+
+@property (readonly) NSString* lastActiveTLKUUID;
 
 // Full of condition variables, if you'd like to try to wait until the key hierarchy is in some state
 @property NSMutableDictionary<CKKSZoneKeyState*, CKKSCondition*>* keyHierarchyConditions;
 @property (weak) CKKSNearFutureScheduler* savedTLKNotifier;
 
 // Differs from the zonesetupoperation: zoneSetup is only for CK modifications, viewSetup handles local db changes too
-@property CKKSGroupOperation* viewSetupOperation;
+@property CKKSResultOperation* viewSetupOperation;
 
 /* Used for debugging: just what happened last time we ran this? */
-@property CKKSIncomingQueueOperation*             lastIncomingQueueOperation;
-@property CKKSNewTLKOperation*                    lastNewTLKOperation;
-@property CKKSOutgoingQueueOperation*             lastOutgoingQueueOperation;
-@property CKKSProcessReceivedKeysOperation*       lastProcessReceivedKeysOperation;
+@property CKKSIncomingQueueOperation* lastIncomingQueueOperation;
+@property CKKSNewTLKOperation* lastNewTLKOperation;
+@property CKKSOutgoingQueueOperation* lastOutgoingQueueOperation;
+@property CKKSProcessReceivedKeysOperation* lastProcessReceivedKeysOperation;
 @property CKKSFetchAllRecordZoneChangesOperation* lastRecordZoneChangesOperation;
-@property CKKSReencryptOutgoingItemsOperation*    lastReencryptOutgoingItemsOperation;
-@property CKKSScanLocalItemsOperation*            lastScanLocalItemsOperation;
-@property CKKSSynchronizeOperation*               lastSynchronizeOperation;
-@property CKKSResultOperation*                    lastFixupOperation;
+@property CKKSReencryptOutgoingItemsOperation* lastReencryptOutgoingItemsOperation;
+@property CKKSScanLocalItemsOperation* lastScanLocalItemsOperation;
+@property CKKSSynchronizeOperation* lastSynchronizeOperation;
+@property CKKSResultOperation* lastFixupOperation;
 
 /* Used for testing: pause operation types by adding operations here */
 @property NSOperation* holdReencryptOutgoingItemsOperation;
 @property NSOperation* holdOutgoingQueueOperation;
+@property NSOperation* holdLocalSynchronizeOperation;
+@property CKKSResultOperation* holdFixupOperation;
 
 /* Trigger this to tell the whole machine that this view has changed */
 @property CKKSNearFutureScheduler* notifyViewChangedScheduler;
 @property (nonatomic, readonly) NSSet<id<CKKSPeer>>* currentTrustedPeers;
 @property (nonatomic, readonly) NSError* currentTrustedPeersError;
 
-- (instancetype)initWithContainer:     (CKContainer*) container
-                             zoneName: (NSString*) zoneName
-                       accountTracker:(CKKSCKAccountStateTracker*) accountTracker
-                     lockStateTracker:(CKKSLockStateTracker*) lockStateTracker
-                     savedTLKNotifier:(CKKSNearFutureScheduler*) savedTLKNotifier
-                         peerProvider:(id<CKKSPeerProvider>)peerProvider
fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
-           fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
-                  queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
-    modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
-      modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
-                   apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
-                        notifierClass: (Class<CKKSNotifier>) notifierClass;
+- (instancetype)initWithContainer:(CKContainer*)container
+                                zoneName:(NSString*)zoneName
+                          accountTracker:(CKKSCKAccountStateTracker*)accountTracker
+                        lockStateTracker:(CKKSLockStateTracker*)lockStateTracker
+                        savedTLKNotifier:(CKKSNearFutureScheduler*)savedTLKNotifier
+                            peerProvider:(id<CKKSPeerProvider>)peerProvider
   fetchRecordZoneChangesOperationClass:(Class<CKKSFetchRecordZoneChangesOperation>)fetchRecordZoneChangesOperationClass
+              fetchRecordsOperationClass:(Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
+                     queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
+       modifySubscriptionsOperationClass:(Class<CKKSModifySubscriptionsOperation>)modifySubscriptionsOperationClass
+         modifyRecordZonesOperationClass:(Class<CKKSModifyRecordZonesOperation>)modifyRecordZonesOperationClass
+                      apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass
+                           notifierClass:(Class<CKKSNotifier>)notifierClass;
 
 /* Synchronous operations */
 
-- (void) handleKeychainEventDbConnection:(SecDbConnectionRef) dbconn
-                                   added:(SecDbItemRef) added
-                                 deleted:(SecDbItemRef) deleted
-                             rateLimiter:(CKKSRateLimiter*) rateLimiter
-                            syncCallback:(SecBoolNSErrorCallback) syncCallback;
+- (void)handleKeychainEventDbConnection:(SecDbConnectionRef)dbconn
+                                  added:(SecDbItemRef _Nullable)added
+                                deleted:(SecDbItemRef _Nullable)deleted
+                            rateLimiter:(CKKSRateLimiter*)rateLimiter
+                           syncCallback:(SecBoolNSErrorCallback)syncCallback;
 
--(void)setCurrentItemForAccessGroup:(SecDbItemRef)newItem
-                               hash:(NSData*)newItemSHA1
-                        accessGroup:(NSString*)accessGroup
-                         identifier:(NSString*)identifier
-                          replacing:(SecDbItemRef)oldItem
-                               hash:(NSData*)oldItemSHA1
-                           complete:(void (^) (NSError* operror)) complete;
+- (void)setCurrentItemForAccessGroup:(SecDbItemRef)newItem
+                                hash:(NSData*)newItemSHA1
+                         accessGroup:(NSString*)accessGroup
+                          identifier:(NSString*)identifier
+                           replacing:(SecDbItemRef _Nullable)oldItem
+                                hash:(NSData* _Nullable)oldItemSHA1
+                            complete:(void (^)(NSError* operror))complete;
 
--(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
-                         identifier:(NSString*)identifier
-                    fetchCloudValue:(bool)fetchCloudValue
-                           complete:(void (^) (NSString* uuid, NSError* operror)) complete;
+- (void)getCurrentItemForAccessGroup:(NSString*)accessGroup
+                          identifier:(NSString*)identifier
+                     fetchCloudValue:(bool)fetchCloudValue
+                            complete:(void (^)(NSString* uuid, NSError* operror))complete;
 
-- (bool) outgoingQueueEmpty: (NSError * __autoreleasing *) error;
+- (bool)outgoingQueueEmpty:(NSError* __autoreleasing*)error;
 
 - (CKKSResultOperation*)waitForFetchAndIncomingQueueProcessing;
-- (void) waitForKeyHierarchyReadiness;
-- (void) cancelAllOperations;
+- (void)waitForKeyHierarchyReadiness;
+- (void)cancelAllOperations;
 
-- (CKKSKey*) keyForItem: (SecDbItemRef) item error: (NSError * __autoreleasing *) error;
+- (CKKSKey* _Nullable)keyForItem:(SecDbItemRef)item error:(NSError* __autoreleasing*)error;
 
-- (bool)_onqueueWithAccountKeysCheckTLK: (CKKSKey*) proposedTLK error: (NSError * __autoreleasing *) error;
+- (bool)_onqueueWithAccountKeysCheckTLK:(CKKSKey*)proposedTLK error:(NSError* __autoreleasing*)error;
 
 /* Asynchronous kickoffs */
 
-- (void) initializeZone;
-
-- (CKKSOutgoingQueueOperation*)processOutgoingQueue:(CKOperationGroup*)ckoperationGroup;
-- (CKKSOutgoingQueueOperation*)processOutgoingQueueAfter:(CKKSResultOperation*)after ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
+- (CKKSOutgoingQueueOperation*)processOutgoingQueue:(CKOperationGroup* _Nullable)ckoperationGroup;
+- (CKKSOutgoingQueueOperation*)processOutgoingQueueAfter:(CKKSResultOperation* _Nullable)after
+                                        ckoperationGroup:(CKOperationGroup* _Nullable)ckoperationGroup;
 
-- (CKKSIncomingQueueOperation*) processIncomingQueue:(bool)failOnClassA;
-- (CKKSIncomingQueueOperation*) processIncomingQueue:(bool)failOnClassA after: (CKKSResultOperation*) after;
+- (CKKSIncomingQueueOperation*)processIncomingQueue:(bool)failOnClassA;
+- (CKKSIncomingQueueOperation*)processIncomingQueue:(bool)failOnClassA after:(CKKSResultOperation* _Nullable)after;
 
 // Schedules a process queueoperation to happen after the next device unlock. This may be Immediately, if the device is unlocked.
 - (void)processIncomingQueueAfterNextUnlock;
 // If rateLimit is true, the operation will abort if it's updated the record in the past 3 days
 - (CKKSUpdateDeviceStateOperation*)updateDeviceState:(bool)rateLimit
                    waitForKeyHierarchyInitialization:(uint64_t)timeout
-                                    ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
+                                    ckoperationGroup:(CKOperationGroup* _Nullable)ckoperationGroup;
 
-- (CKKSSynchronizeOperation*) resyncWithCloud;
+- (CKKSSynchronizeOperation*)resyncWithCloud;
+- (CKKSLocalSynchronizeOperation*)resyncLocal;
 
 - (CKKSResultOperation*)fetchAndProcessCKChanges:(CKKSFetchBecause*)because;
 
 // For our serial queue to work with how handleKeychainEventDbConnection is called from the main thread,
 // every block on our queue must have a SecDBConnectionRef available to it before it begins on the queue.
 // Use these helper methods to make sure those exist.
-- (void) dispatchAsync: (bool (^)(void)) block;
-- (void) dispatchSync: (bool (^)(void)) block;
+- (void)dispatchAsync:(bool (^)(void))block;
+- (void)dispatchSync:(bool (^)(void))block;
 - (void)dispatchSyncWithAccountKeys:(bool (^)(void))block;
 
 /* Synchronous operations which must be called from inside a dispatchAsync or dispatchSync block */
 - (void)_onqueueKeyStateMachineRequestProcess;
 
 // Call this from a key hierarchy operation to move the state machine, and record the results of the last move.
-- (void)_onqueueAdvanceKeyStateMachineToState: (CKKSZoneKeyState*) state withError: (NSError*) error;
+- (void)_onqueueAdvanceKeyStateMachineToState:(CKKSZoneKeyState* _Nullable)state withError:(NSError* _Nullable)error;
 
 // Since we might have people interested in the state transitions of objects, please do those transitions via these methods
-- (bool)_onqueueChangeOutgoingQueueEntry: (CKKSOutgoingQueueEntry*) oqe toState: (NSString*) state error: (NSError* __autoreleasing*) error;
-- (bool)_onqueueErrorOutgoingQueueEntry: (CKKSOutgoingQueueEntry*) oqe itemError: (NSError*) itemError error: (NSError* __autoreleasing*) error;
+- (bool)_onqueueChangeOutgoingQueueEntry:(CKKSOutgoingQueueEntry*)oqe
+                                 toState:(NSString*)state
+                                   error:(NSError* __autoreleasing*)error;
+- (bool)_onqueueErrorOutgoingQueueEntry:(CKKSOutgoingQueueEntry*)oqe
+                              itemError:(NSError*)itemError
+                                  error:(NSError* __autoreleasing*)error;
 
 // Call this if you've done a write and received an error. It'll pull out any new records returned as CKErrorServerRecordChanged and pretend we received them in a fetch
 //
 // Note that you need to tell this function the records you wanted to save, so it can determine which record failed from its CKRecordID.
 // I don't know why CKRecordIDs don't have record types, either.
-- (bool)_onqueueCKWriteFailed:(NSError*)ckerror attemptedRecordsChanged:(NSDictionary<CKRecordID*,CKRecord*>*)savedRecords;
+- (bool)_onqueueCKWriteFailed:(NSError*)ckerror attemptedRecordsChanged:(NSDictionary<CKRecordID*, CKRecord*>*)savedRecords;
 
-- (bool) _onqueueCKRecordChanged:(CKRecord*)record resync:(bool)resync;
-- (bool) _onqueueCKRecordDeleted:(CKRecordID*)recordID recordType:(NSString*)recordType resync:(bool)resync;
+- (bool)_onqueueCKRecordChanged:(CKRecord*)record resync:(bool)resync;
+- (bool)_onqueueCKRecordDeleted:(CKRecordID*)recordID recordType:(NSString*)recordType resync:(bool)resync;
 
 // For this key, who doesn't yet have a CKKSTLKShare for it?
 // Note that we really want a record sharing the TLK to ourselves, so this function might return
 // a non-empty set even if all peers have the TLK: it wants us to make a record for ourself.
-- (NSSet<id<CKKSPeer>>*)_onqueueFindPeersMissingShare:(CKKSKey*)key error:(NSError* __autoreleasing*)error;
+- (NSSet<id<CKKSPeer>>* _Nullable)_onqueueFindPeersMissingShare:(CKKSKey*)key error:(NSError* __autoreleasing*)error;
 
 // For this key, share it to all trusted peers who don't have it yet
-- (NSSet<CKKSTLKShare*>*)_onqueueCreateMissingKeyShares:(CKKSKey*)key error:(NSError* __autoreleasing*)error;
+- (NSSet<CKKSTLKShare*>* _Nullable)_onqueueCreateMissingKeyShares:(CKKSKey*)key error:(NSError* __autoreleasing*)error;
 
-- (bool)_onQueueUpdateLatestManifestWithError:(NSError**)error;
+- (bool)_onqueueUpdateLatestManifestWithError:(NSError**)error;
 
-- (CKKSDeviceStateEntry*)_onqueueCurrentDeviceStateEntry: (NSError* __autoreleasing*)error;
+- (CKKSDeviceStateEntry* _Nullable)_onqueueCurrentDeviceStateEntry:(NSError* __autoreleasing*)error;
 
 // Called by the CKKSZoneChangeFetcher
-- (bool) isFatalCKFetchError: (NSError*) error;
+- (bool)isFatalCKFetchError:(NSError*)error;
 
 // Please don't use these unless you're an Operation in this package
 @property NSHashTable<CKKSIncomingQueueOperation*>* incomingQueueOperations;
 @property CKKSScanLocalItemsOperation* initialScanOperation;
 
 // Returns the current state of this view
--(NSDictionary<NSString*, NSString*>*)status;
+- (NSDictionary<NSString*, NSString*>*)status;
 @end
-#endif // OCTAGON
-
-
-#define SecTranslateError(nserrorptr, cferror) \
-    if(nserrorptr) { \
-       *nserrorptr = (__bridge_transfer NSError*) cferror; \
-    } else { \
-       CFReleaseNull(cferror); \
-    }
 
-#endif /* CKKSKeychainView_h */
+NS_ASSUME_NONNULL_END
+#else   // !OCTAGON
+#import <Foundation/Foundation.h>
+@interface CKKSKeychainView : NSObject
+{
+    NSString* _containerName;
+}
+@end
+#endif  // OCTAGON
index 1906df763fab652d315fbe9105dbe6472a7e4660..2b9260ffb0bd1acb00ec8b1f483cd4efcbefb2d3 100644 (file)
@@ -53,7 +53,7 @@
 #import "CKKSManifest.h"
 #import "CKKSManifestLeafRecord.h"
 #import "CKKSZoneChangeFetcher.h"
-#import "CKKSAnalyticsLogger.h"
+#import  "CKKSAnalyticsLogger.h"
 #import "keychain/ckks/CKKSDeviceStateEntry.h"
 #import "keychain/ckks/CKKSNearFutureScheduler.h"
 #import "keychain/ckks/CKKSCurrentItemPointer.h"
@@ -64,6 +64,7 @@
 #import "keychain/ckks/CloudKitCategories.h"
 #import "keychain/ckks/CKKSTLKShare.h"
 #import "keychain/ckks/CKKSHealTLKSharesOperation.h"
+#import "keychain/ckks/CKKSLocalSynchronizeOperation.h"
 
 #include <utilities/SecCFWrappers.h>
 #include <utilities/SecDb.h>
 
 @property CKKSNearFutureScheduler* initializeScheduler;
 
+// Slows down all outgoing queue operations
+@property CKKSNearFutureScheduler* outgoingQueueOperationScheduler;
+
 @property CKKSResultOperation* processIncomingQueueAfterNextUnlockOperation;
 
 @property NSMutableDictionary<NSString*, SecBoolNSErrorCallback>* pendingSyncCallbacks;
 
 @property id<CKKSPeerProvider> currentPeerProvider;
 
+// An extra queue for semaphore-waiting-based NSOperations
+@property NSOperationQueue* waitingQueue;
+
 // Make these readwrite
 @property (nonatomic, readwrite) CKKSSelves* currentSelfPeers;
 @property (nonatomic, readwrite) NSError* currentSelfPeersError;
                     apsConnectionClass:apsConnectionClass]) {
         __weak __typeof(self) weakSelf = self;
 
+        _loggedIn = [[CKKSCondition alloc] init];
+        _loggedOut = [[CKKSCondition alloc] init];
+
         _incomingQueueOperations = [NSHashTable weakObjectsHashTable];
         _outgoingQueueOperations = [NSHashTable weakObjectsHashTable];
         _zoneChangeFetcher = [[CKKSZoneChangeFetcher alloc] initWithCKKSKeychainView: self];
         _keyStateFetchRequested = false;
         _keyStateProcessRequested = false;
 
+        _waitingQueue = [[NSOperationQueue alloc] init];
+        _waitingQueue.maxConcurrentOperationCount = 5;
+
         _keyStateReadyDependency = [self createKeyStateReadyDependency: @"Key state has become ready for the first time." ckoperationGroup:[CKOperationGroup CKKSGroupWithName:@"initial-key-state-ready-scan"]];
 
-        dispatch_time_t initializeDelay = SecCKKSTestsEnabled() ? NSEC_PER_MSEC * 500 : NSEC_PER_SEC * 30;
+        dispatch_time_t initializeDelay = SecCKKSTestsEnabled() ? NSEC_PER_MSEC * 600 : NSEC_PER_SEC * 30;
         _initializeScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat: @"%@-zone-initializer", self.zoneName]
                                                                 initialDelay:0
                                                              continuingDelay:initializeDelay
                                                                            [strongSelf maybeRestartSetup];
                                                                        }];
 
+        dispatch_time_t initialOutgoingQueueDelay = SecCKKSTestsEnabled() ? NSEC_PER_MSEC * 200 : NSEC_PER_SEC * 1;
+        dispatch_time_t continuingOutgoingQueueDelay = SecCKKSTestsEnabled() ? NSEC_PER_MSEC * 500 : NSEC_PER_SEC * 30;
+        _outgoingQueueOperationScheduler = [[CKKSNearFutureScheduler alloc] initWithName:[NSString stringWithFormat: @"%@-outgoing-queue-scheduler", self.zoneName]
+                                                                            initialDelay:initialOutgoingQueueDelay
+                                                                         continuingDelay:continuingOutgoingQueueDelay
+                                                                        keepProcessAlive:false
+                                                                                   block:^{}];
     }
     return self;
 }
 
 - (NSString*)description {
-    return [NSString stringWithFormat:@"<%@: %@>", NSStringFromClass([self class]), self.zoneName];
+    return [NSString stringWithFormat:@"<%@: %@ (%@)>", NSStringFromClass([self class]), self.zoneName, self.keyHierarchyState];
 }
 
 - (NSString*)debugDescription {
-    return [NSString stringWithFormat:@"<%@: %@ %p>", NSStringFromClass([self class]), self.zoneName, self];
+    return [NSString stringWithFormat:@"<%@: %@ (%@) %p>", NSStringFromClass([self class]), self.zoneName, self.keyHierarchyState, self];
 }
 
 - (CKKSZoneKeyState*)keyHierarchyState {
     return self.activeTLK;
 }
 
-- (void) initializeZone {
-    // Unfortunate, but makes retriggering easy.
-    [self.initializeScheduler trigger];
-}
-
 - (void)maybeRestartSetup {
     [self dispatchSync: ^bool{
-        if(self.setupStarted && !self.setupComplete) {
-            ckksdebug("ckks", self, "setup has restarted. Ignoring timer fire");
-            return false;
-        }
-
-        if(self.setupSuccessful) {
-            ckksdebug("ckks", self, "setup has completed successfully. Ignoring timer fire");
+        if([self.viewSetupOperation isPending] || [self.viewSetupOperation isExecuting]) {
+            ckksinfo("ckks", self, "setup is in-flight. Ignoring timer fire");
             return false;
         }
 
         [self resetSetup];
-        [self _onqueueInitializeZone];
+        [self restartCurrentAccountStateOperation];
         return true;
     }];
 }
     _keyHierarchyError = nil;
 }
 
- - (void)_onqueueInitializeZone {
+ - (void)_onqueueHandleCKLogin {
     if(!SecCKKSIsEnabled()) {
         ckksnotice("ckks", self, "Skipping CloudKit initialization due to disabled CKKS");
         return;
 
     __weak __typeof(self) weakSelf = self;
 
-    NSBlockOperation* afterZoneSetup = [NSBlockOperation blockOperationWithBlock: ^{
+    CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
+    [self handleCKLogin:ckse.ckzonecreated zoneSubscribed:ckse.ckzonesubscribed];
+
+    self.viewSetupOperation = [CKKSResultOperation operationWithBlock: ^{
         __strong __typeof(weakSelf) strongSelf = weakSelf;
         if(!strongSelf) {
             ckkserror("ckks", strongSelf, "received callback for released object");
 
                 // Note that CKKSZone has probably called [handleLogout]; which means we have a key hierarchy reset queued up. Error here anyway.
                 NSError* realReason = strongSelf.zoneCreatedError ? strongSelf.zoneCreatedError : strongSelf.zoneSubscribedError;
+                strongSelf.viewSetupOperation.error = realReason;
                 [strongSelf _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: realReason];
 
                 // We're supposed to be up, but something has gone wrong. Blindly retry until it works.
 
         // We can't enter the account queue until an account exists. Before this point, we don't know if one does.
         [strongSelf dispatchSyncWithAccountKeys: ^bool{
+            // Change our condition variables to reflect that we think we're logged in
+            strongSelf.loggedOut = [[CKKSCondition alloc] initToChain: strongSelf.loggedOut];
+            [strongSelf.loggedIn fulfill];
+
             CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: strongSelf.zoneName];
 
             // Check if we believe we've synced this zone before.
             } else {
                 // Likely a restart of securityd!
 
+                // First off, are there any in-flight queue entries? If so, put them back into New.
+                // If they're truly in-flight, we'll "conflict" with ourselves, but that should be fine.
+                NSError* error = nil;
+                [self _onqueueResetAllInflightOQE:&error];
+                if(error) {
+                    ckkserror("ckks", self, "Couldn't reset in-flight OQEs, bad behavior ahead: %@", error);
+                }
+
                 // Are there any fixups to run first?
                 strongSelf.lastFixupOperation = [CKKSFixups fixup:ckse.lastFixup for:strongSelf];
                 if(strongSelf.lastFixupOperation) {
                     initialProcess = [strongSelf processIncomingQueue:false after:strongSelf.lastFixupOperation];
                 }
 
-                if(!strongSelf.egoManifest) {
-                    ckksnotice("ckksmanifest", strongSelf, "No ego manifest on restart; rescanning");
-                    strongSelf.initialScanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:strongSelf ckoperationGroup:strongSelf.keyHierarchyOperationGroup];
-                    strongSelf.initialScanOperation.name = @"initial-scan-operation";
-                    [strongSelf.initialScanOperation addNullableDependency:strongSelf.lastFixupOperation];
-                    [strongSelf.initialScanOperation addNullableDependency:strongSelf.lockStateTracker.unlockDependency];
-                    [strongSelf.initialScanOperation addDependency: initialProcess];
-                    [strongSelf scheduleOperation: strongSelf.initialScanOperation];
+                if([CKKSManifest shouldSyncManifests]) {
+                    if (!strongSelf.egoManifest) {
+                        ckksnotice("ckksmanifest", strongSelf, "No ego manifest on restart; rescanning");
+                        strongSelf.initialScanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:strongSelf ckoperationGroup:strongSelf.keyHierarchyOperationGroup];
+                        strongSelf.initialScanOperation.name = @"initial-scan-operation";
+                        [strongSelf.initialScanOperation addNullableDependency:strongSelf.lastFixupOperation];
+                        [strongSelf.initialScanOperation addNullableDependency:strongSelf.lockStateTracker.unlockDependency];
+                        [strongSelf.initialScanOperation addDependency: initialProcess];
+                        [strongSelf scheduleOperation: strongSelf.initialScanOperation];
+                    }
                 }
 
+                // Process outgoing queue after re-start
                 [strongSelf processOutgoingQueueAfter:strongSelf.lastFixupOperation ckoperationGroup:strongSelf.keyHierarchyOperationGroup];
             }
 
             return true;
         }];
     }];
-    afterZoneSetup.name = @"view-setup";
-
-    CKKSZoneStateEntry* ckse = [CKKSZoneStateEntry state: self.zoneName];
-    NSOperation* zoneSetupOperation = [self createSetupOperation: ckse.ckzonecreated zoneSubscribed: ckse.ckzonesubscribed];
-
-    self.viewSetupOperation = [[CKKSGroupOperation alloc] init];
-    self.viewSetupOperation.name = @"view-setup-group";
-    if(!zoneSetupOperation.isFinished) {
-        [self.viewSetupOperation runBeforeGroupFinished: zoneSetupOperation];
-    }
-
-    [afterZoneSetup addDependency: zoneSetupOperation];
-    [self.viewSetupOperation runBeforeGroupFinished: afterZoneSetup];
+    self.viewSetupOperation.name = @"view-setup";
 
+    [self.viewSetupOperation addNullableDependency: self.zoneSetupOperation];
     [self scheduleAccountStatusOperation: self.viewSetupOperation];
 }
 
             return true;
         }];
 
-        [strongSelf resetSetup];
-
         if(error) {
             ckksnotice("ckksreset", strongSelf, "Local reset finished with error %@", error);
             strongOp.error = error;
             if(strongSelf.accountStatus == CKKSAccountStatusAvailable) {
                 // Since we're logged in, we expect a reset to fix up the key hierarchy
                 ckksnotice("ckksreset", strongSelf, "logged in; re-initializing zone");
-                [strongSelf initializeZone];
+                [self.initializeScheduler trigger];
 
                 ckksnotice("ckksreset", strongSelf, "waiting for key hierarchy to become ready");
                 CKKSResultOperation* waitOp = [CKKSResultOperation named:@"waiting-for-key-hierarchy" withBlock:^{}];
 }
 
 - (CKKSResultOperation*)resetCloudKitZone {
-    if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckks", self, "Skipping CloudKit reset due to disabled CKKS");
-        return nil;
-    }
-
     // On a reset, we should cancel all existing operations
     [self cancelAllOperations];
     CKKSResultOperation* reset = [super beginResetCloudKitZoneOperation];
             if(strongSelf.accountStatus == CKKSAccountStatusAvailable) {
                 // Since we're logged in, we expect a reset to fix up the key hierarchy
                 ckksnotice("ckksreset", strongSelf, "re-initializing zone");
-                [strongSelf initializeZone];
+                [self.initializeScheduler trigger];
 
                 ckksnotice("ckksreset", strongSelf, "waiting for key hierarchy to become ready");
                 CKKSResultOperation* waitOp = [CKKSResultOperation named:@"waiting-for-reset" withBlock:^{}];
         }
         ckksnotice("ckkskey", strongSelf, "%@", message);
 
-        // While we weren't in 'ready', keychain modifications might have come in and were dropped on the floor. Find them!
-        CKKSScanLocalItemsOperation* scanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView: strongSelf ckoperationGroup:group];
-        [strongSelf scheduleOperation: scanOperation];
+        [strongSelf dispatchSync:^bool {
+            if(strongSelf.droppedItems) {
+                // While we weren't in 'ready', keychain modifications might have come in and were dropped on the floor. Find them!
+                ckksnotice("ckkskey", strongSelf, "Launching scan operation for missed items");
+                CKKSScanLocalItemsOperation* scanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView: strongSelf ckoperationGroup:group];
+                [strongSelf scheduleOperation: scanOperation];
+            }
+            return true;
+        }];
     }];
     keyStateReadyDependency.name = [NSString stringWithFormat: @"%@-key-state-ready", self.zoneName];
     return keyStateReadyDependency;
         self.keyStateProcessRequested = false;
 
         self.keyHierarchyOperationGroup = [CKOperationGroup CKKSGroupWithName:@"key-state-reset"];
+        NSOperation* oldKSRD = self.keyStateReadyDependency;
         self.keyStateReadyDependency = [self createKeyStateReadyDependency:@"Key state has become ready for the first time (after reset)." ckoperationGroup:self.keyHierarchyOperationGroup];
+        if(oldKSRD) {
+            [oldKSRD addDependency:self.keyStateReadyDependency];
+            [self.waitingQueue addOperation:oldKSRD];
+        }
         return;
     }
 
     }
 
     if(state) {
+        ckksnotice("ckkskey", self, "Preparing to advance key hierarchy state machine from %@ to %@", self.keyHierarchyState, state);
         self.keyStateMachineOperation = nil;
-
-        ckksnotice("ckkskey", self, "Advancing key hierarchy state machine from %@ to %@", self.keyHierarchyState, state);
-        self.keyHierarchyState = state;
+    } else {
+        ckksnotice("ckkskey", self, "Key hierarchy state machine is being poked; currently %@", self.keyHierarchyState);
+        state = self.keyHierarchyState;
     }
 
     // Many of our decisions below will be based on what keys exist. Help them out.
 
     NSError* hierarchyError = nil;
 
-    if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateInitializing]) {
+    if([state isEqualToString: SecCKKSZoneKeyStateInitializing]) {
         if(state != nil) {
             // Wait for CKKSZone to finish initialization.
             ckkserror("ckkskey", self, "Asked to advance state machine (to %@) while CK zone still initializing.", state);
         }
         return;
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateReady]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateReady]) {
         if(self.keyStateProcessRequested || [remoteKeys count] > 0) {
             // We've either received some remote keys from the last fetch, or someone has requested a reprocess.
             ckksnotice("ckkskey", self, "Kicking off a key reprocess based on request:%d and remote key count %lu", self.keyStateProcessRequested, (unsigned long)[remoteKeys count]);
             [self _onqueueKeyHierarchyProcess];
+            // Stay in state 'ready': this reprocess might not change anything. If it does, cleanup code elsewhere will
+            // reencode items that arrive during this ready
 
         } else if(self.keyStateFullRefetchRequested) {
             // In ready, but someone has requested a full fetch. Kick it off.
             ckksnotice("ckkskey", self, "Kicking off a key refetch based on request:%d", self.keyStateFetchRequested);
             [self _onqueueKeyHierarchyRefetch];
+            state = SecCKKSZoneKeyStateNeedFullRefetch;
 
         } else if(self.keyStateFetchRequested) {
             // In ready, but someone has requested a fetch. Kick it off.
             ckksnotice("ckkskey", self, "Kicking off a key refetch based on request:%d", self.keyStateFetchRequested);
             [self _onqueueKeyHierarchyFetch];
+            state = SecCKKSZoneKeyStateInitialized; // Don't go to 'ready', go to 'initialized', since we want to fetch again
         }
         // TODO: kick off a key roll if one has been requested
 
             if(![checkedstate isEqualToString:SecCKKSZoneKeyStateReady] || hierarchyError) {
                 // Things is bad. Kick off a heal to fix things up.
                 ckksnotice("ckkskey", self, "Thought we were ready, but the key hierarchy is %@: %@", checkedstate, hierarchyError);
-                self.keyHierarchyState = checkedstate;
+                state = checkedstate;
 
-            } else {
-                // In ready, nothing to do. Notify waiters and quit.
-                self.keyHierarchyOperationGroup = nil;
-                if(self.keyStateReadyDependency) {
-                    [self scheduleOperation: self.keyStateReadyDependency];
-                    self.keyStateReadyDependency = nil;
-                }
-
-                // If there are any OQEs waiting to be encrypted, launch an op to fix them
-                if([outdatedOQEs count] > 0u) {
-                    ckksnotice("ckksreencrypt", self, "Reencrypting outgoing items as the key hierarchy is ready");
-                    CKKSReencryptOutgoingItemsOperation* op = [[CKKSReencryptOutgoingItemsOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:self.keyHierarchyOperationGroup];
-                    [self scheduleOperation:op];
-                }
-
-                return;
             }
         }
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateInitialized]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateInitialized]) {
         // We're initialized and CloudKit is ready. See what needs done...
 
         // Check if we have an existing key hierarchy
             CKKSZoneKeyState* checkedstate = [self _onqueueEnsureKeyHierarchyHealth:keyset error:&hierarchyError];
             if([checkedstate isEqualToString:SecCKKSZoneKeyStateReady] && !hierarchyError) {
                 ckksnotice("ckkskey", self, "Already have existing key hierarchy for %@; using it.", self.zoneID.zoneName);
-            } else if(hierarchyError && [self.lockStateTracker isLockedError:hierarchyError]) {
-                ckksnotice("ckkskey", self, "Initial scan shows key hierarchy is unavailable since keychain is locked: %@", hierarchyError);
-                self.keyHierarchyState = SecCKKSZoneKeyStateWaitForUnlock;
             } else {
                 ckksnotice("ckkskey", self, "Initial scan shows key hierarchy is %@: %@", checkedstate, hierarchyError);
-                self.keyHierarchyState = checkedstate;
+                state = checkedstate;
             }
 
         } else {
             [self _onqueueKeyHierarchyFetch];
         }
 
-    } else if([self.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateWaitForFixupOperation]) {
+    } else if([state isEqualToString:SecCKKSZoneKeyStateWaitForFixupOperation]) {
         // We should enter 'initialized' when the fixup operation completes
         ckksnotice("ckkskey", self, "Waiting for the fixup operation: %@", self.lastFixupOperation);
 
         self.keyStateMachineOperation = [NSBlockOperation named:@"key-state-after-fixup" withBlock:^{
             __strong __typeof(self) strongSelf = weakSelf;
-            [strongSelf dispatchSync:^bool{
+            [strongSelf dispatchSyncWithAccountKeys:^bool{
+                ckksnotice("ckkskey", self, "Fixup operation complete! Restarting key hierarchy machinery");
                 [strongSelf _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateInitialized withError:nil];
                 return true;
             }];
         }];
         [self.keyStateMachineOperation addNullableDependency:self.lastFixupOperation];
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateFetchComplete]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateFetchComplete]) {
         // We've just completed a fetch of everything. Are there any remote keys?
         if(remoteKeys.count > 0u) {
             // Process the keys we received.
             // Huh. We appear to have current key pointers, but the keys themselves don't exist. That's weird.
             // Transfer to the "unhealthy" state to request a fix
             ckksnotice("ckkskey", self, "We appear to have current key pointers but no keys to match them. Moving to 'unhealthy'");
-            self.keyHierarchyState = SecCKKSZoneKeyStateUnhealthy;
+            state = SecCKKSZoneKeyStateUnhealthy;
         } else {
             // No remote keys, and the pointers look sane? Do we have an existing key hierarchy?
             CKKSZoneKeyState* checkedstate = [self _onqueueEnsureKeyHierarchyHealth:keyset error:&hierarchyError];
             if([checkedstate isEqualToString:SecCKKSZoneKeyStateReady] && !hierarchyError) {
                 ckksnotice("ckkskey", self, "After fetch, everything looks good.");
-            } else if(hierarchyError && [self.lockStateTracker isLockedError:hierarchyError]) {
-                ckksnotice("ckkskey", self, "After fetch, we're locked. Key hierarchy is unavailable since keychain is locked: %@", hierarchyError);
-                self.keyHierarchyState = SecCKKSZoneKeyStateWaitForUnlock;
             } else if(localKeys.count == 0 && remoteKeys.count == 0) {
                 ckksnotice("ckkskey", self, "After fetch, we don't have any key hierarchy. Making a new one: %@", hierarchyError);
                 self.keyStateMachineOperation = [[CKKSNewTLKOperation alloc] initWithCKKSKeychainView: self ckoperationGroup:self.keyHierarchyOperationGroup];
             } else {
-                ckksnotice("ckkskey", self, "After fetch, we have an unhealthy key hierarchy. Moving to %@: %@", checkedstate, hierarchyError);
-                self.keyHierarchyState = checkedstate;
+                ckksnotice("ckkskey", self, "After fetch, we have a possibly unhealthy key hierarchy. Moving to %@: %@", checkedstate, hierarchyError);
+                state = checkedstate;
             }
         }
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateWaitForTLK]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateWaitForTLK]) {
         // We're in a hold state: waiting for the TLK bytes to arrive.
 
         if(self.keyStateProcessRequested) {
             [self _onqueueKeyHierarchyProcess];
         }
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateWaitForUnlock]) {
-        // We're in a hold state: waiting for the keybag to unlock so we can process the keys again.
+    } else if([state isEqualToString: SecCKKSZoneKeyStateWaitForUnlock]) {
+        // will be handled later.
+        ckksnotice("ckkskey", self, "Requested to enter waitforunlock");
 
-        [self _onqueueKeyHierarchyProcess];
-        [self.keyStateMachineOperation addNullableDependency: self.lockStateTracker.unlockDependency];
+    } else if([state isEqualToString: SecCKKSZoneKeyStateReadyPendingUnlock]) {
+        // will be handled later.
+        ckksnotice("ckkskey", self, "Believe we're ready, but recheck after unlock");
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateBadCurrentPointers]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateBadCurrentPointers]) {
         // The current key pointers are broken, but we're not sure why.
         ckksnotice("ckkskey", self, "Our current key pointers are reported broken. Attempting a fix!");
         self.keyStateMachineOperation = [[CKKSHealKeyHierarchyOperation alloc] initWithCKKSKeychainView: self ckoperationGroup:self.keyHierarchyOperationGroup];
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateNewTLKsFailed]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateNewTLKsFailed]) {
         ckksnotice("ckkskey", self, "Creating new TLKs didn't work. Attempting to refetch!");
         [self _onqueueKeyHierarchyFetch];
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateHealTLKSharesFailed]) {
-        if(!SecCKKSShareTLKs()) {
-             ckkserror("ckkskey", self, "In SecCKKSZoneKeyStateHealTLKSharesFailed, but TLK sharing is disabled.");
-        }
+    } else if([state isEqualToString: SecCKKSZoneKeyStateHealTLKSharesFailed]) {
         ckksnotice("ckkskey", self, "Creating new TLK shares didn't work. Attempting to refetch!");
         [self _onqueueKeyHierarchyFetch];
 
-    } else if([self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateNeedFullRefetch]) {
+    } else if([state isEqualToString: SecCKKSZoneKeyStateNeedFullRefetch]) {
         ckksnotice("ckkskey", self, "Informed of request for full refetch");
         [self _onqueueKeyHierarchyRefetch];
 
     } else {
-        ckkserror("ckks", self, "asked to advance state machine to unknown state: %@", self.keyHierarchyState);
+        ckkserror("ckks", self, "asked to advance state machine to unknown state: %@", state);
+        self.keyHierarchyState = state;
         return;
     }
 
-    if(self.keyStateMachineOperation) {
-        if(self.keyStateReadyDependency == nil || [self.keyStateReadyDependency isFinished]) {
-            ckksnotice("ckkskey", self, "reloading keyStateReadyDependency due to operation %@", self.keyStateMachineOperation);
+    // Check our other states: did the above code ask for a fix up? Are we in ready?
+    if([state isEqualToString:SecCKKSZoneKeyStateUnhealthy]) {
+        ckksnotice("ckkskey", self, "Looks like the key hierarchy is unhealthy. Launching fix.");
+        self.keyStateMachineOperation = [[CKKSHealKeyHierarchyOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:self.keyHierarchyOperationGroup];
 
-            self.keyHierarchyOperationGroup = [CKOperationGroup CKKSGroupWithName:@"key-state-broken"];
-            self.keyStateReadyDependency = [self createKeyStateReadyDependency:@"Key state has become ready again." ckoperationGroup:self.keyHierarchyOperationGroup];
-        }
+    } else if([state isEqualToString:SecCKKSZoneKeyStateHealTLKShares]) {
+        ckksnotice("ckksshare", self, "Key hierarchy is okay, but not shared appropriately. Launching fix.");
+        self.keyStateMachineOperation = [[CKKSHealTLKSharesOperation alloc] initWithCKKSKeychainView:self
+                                                                                    ckoperationGroup:self.keyHierarchyOperationGroup];
 
-        [self scheduleOperation: self.keyStateMachineOperation];
-    } else if([self.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateWaitForTLK]) {
-        ckksnotice("ckkskey", self, "Entering %@", self.keyHierarchyState);
+    } else if([state isEqualToString: SecCKKSZoneKeyStateWaitForUnlock] || [state isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock]) {
+        // We're in a hold state: waiting for the keybag to unlock so we can reenter the key hierarchy state machine
+        // After the next unlock, poke ourselves
+        self.keyStateMachineOperation = [NSBlockOperation named:@"key-state-after-unlock" withBlock:^{
+            __strong __typeof(self) strongSelf = weakSelf;
+            if(!strongSelf) {
+                return;
+            }
+            [strongSelf dispatchSyncWithAccountKeys:^bool{
+                [strongSelf _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateInitialized withError:nil];
+                return true;
+            }];
+        }];
+        [self.keyStateMachineOperation addNullableDependency: self.lockStateTracker.unlockDependency];
 
-    } else if([self.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateUnhealthy]) {
-        ckksnotice("ckkskey", self, "Looks like the key hierarchy is unhealthy. Launching fix.");
-        self.keyStateMachineOperation = [[CKKSHealKeyHierarchyOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:self.keyHierarchyOperationGroup];
-        [self scheduleOperation: self.keyStateMachineOperation];
+    }
 
-    } else if([self.keyHierarchyState isEqualToString:SecCKKSZoneKeyStateHealTLKShares]) {
 
-        if(!SecCKKSShareTLKs()) {
-            // This is an invalid state, since we haven't enabled TLK fixing. Set ourselves to ready!
-            ckkserror("ckksshare", self, "In SecCKKSZoneKeyStateHealTLKShares, but TLK sharing is disabled.");
-            self.keyHierarchyState = SecCKKSZoneKeyStateReady;
-        } else {
-            ckksnotice("ckksshare", self, "Key hierarchy is okay, but not shared appropriately. Launching fix.");
-            self.keyStateMachineOperation = [[CKKSHealTLKSharesOperation alloc] initWithCKKSKeychainView:self
-                                                                                        ckoperationGroup:self.keyHierarchyOperationGroup];
-            [self scheduleOperation: self.keyStateMachineOperation];
+    // Handle the key state ready dependency
+    if([state isEqualToString:SecCKKSZoneKeyStateReady] || [state isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock]) {
+        // Ready enough!
+        if(self.keyStateReadyDependency) {
+            [self scheduleOperation: self.keyStateReadyDependency];
+            self.keyStateReadyDependency = nil;
         }
 
+        // If there are any OQEs waiting to be encrypted, launch an op to fix them
+        if([outdatedOQEs count] > 0u) {
+            ckksnotice("ckksreencrypt", self, "Reencrypting outgoing items as the key hierarchy is ready");
+            CKKSReencryptOutgoingItemsOperation* op = [[CKKSReencryptOutgoingItemsOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:self.keyHierarchyOperationGroup];
+            [self scheduleOperation:op];
+        }
+    } else {
+        // Not in ready: we need a key state ready dependency
+        if(self.keyStateReadyDependency == nil || [self.keyStateReadyDependency isFinished]) {
+            self.keyHierarchyOperationGroup = [CKOperationGroup CKKSGroupWithName:@"key-state-broken"];
+            self.keyStateReadyDependency = [self createKeyStateReadyDependency:@"Key state has become ready again." ckoperationGroup:self.keyHierarchyOperationGroup];
+        }
+    }
+
+    // Start any operations, or log that we aren't
+    if(self.keyStateMachineOperation) {
+        [self scheduleOperation: self.keyStateMachineOperation];
+
+    } else if([state isEqualToString:SecCKKSZoneKeyStateWaitForTLK]) {
+        ckksnotice("ckkskey", self, "Entering key state %@", state);
+    } else if([state isEqualToString:SecCKKSZoneKeyStateError]) {
+        ckksnotice("ckkskey", self, "Entering key state 'error'");
     } else {
         // Nothing to do and not in a waiting state? Awesome; we must be in the ready state.
-        if(![self.keyHierarchyState isEqual: SecCKKSZoneKeyStateReady]) {
-            ckksnotice("ckkskey", self, "No action to take in state %@; we must be ready.", self.keyHierarchyState);
-            self.keyHierarchyState = SecCKKSZoneKeyStateReady;
+        if(!([state isEqualToString:SecCKKSZoneKeyStateReady] || [state isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock])) {
+            ckksnotice("ckkskey", self, "No action to take in state %@; we must be ready.", state);
+            state = SecCKKSZoneKeyStateReady;
 
             self.keyHierarchyOperationGroup = nil;
             if(self.keyStateReadyDependency) {
             }
         }
     }
+    ckksnotice("ckkskey", self, "Advancing to key state: %@", state);
+    self.keyHierarchyState = state;
 }
 
 // For this key, who doesn't yet have a CKKSTLKShare for it?
 - (NSSet<id<CKKSPeer>>*)_onqueueFindPeersMissingShare:(CKKSKey*)key error:(NSError* __autoreleasing*)error {
     dispatch_assert_queue(self.queue);
 
-    if(!SecCKKSShareTLKs()) {
+    if(!key) {
+        ckkserror("ckksshare", self, "Attempting to find missing shares for nil key");
         return [NSSet set];
     }
 
         if(error) {
             *error = self.currentTrustedPeersError;
         }
-        return nil;
+        return [NSSet set];
     }
     if(self.currentSelfPeersError) {
         ckkserror("ckksshare", self, "Couldn't find missing shares because self peers aren't available: %@", self.currentSelfPeersError);
         if(error) {
             *error = self.currentSelfPeersError;
         }
-        return nil;
+        return [NSSet set];
     }
 
     NSMutableSet<id<CKKSPeer>>* peersMissingShares = [NSMutableSet set];
 - (NSSet<CKKSTLKShare*>*)_onqueueCreateMissingKeyShares:(CKKSKey*)key error:(NSError* __autoreleasing*)error {
     dispatch_assert_queue(self.queue);
 
-    if(!SecCKKSShareTLKs()) {
-        return [NSSet set];
-    }
-
     if(self.currentTrustedPeersError) {
         ckkserror("ckksshare", self, "Couldn't create missing shares because trusted peers aren't available: %@", self.currentTrustedPeersError);
         if(error) {
     }
 
     NSError* localerror = nil;
+    bool probablyOkIfUnlocked = false;
 
     // keychain being locked is not a fatal error here
     [set.tlk loadKeyMaterialFromKeychain:&localerror];
         return SecCKKSZoneKeyStateUnhealthy;
     } else if(localerror) {
         ckkserror("ckkskey", self, "Soft error loading TLK(%@), maybe locked: %@", set.tlk, localerror);
+        probablyOkIfUnlocked = true;
     }
     localerror = nil;
 
         return SecCKKSZoneKeyStateUnhealthy;
     } else if(localerror) {
         ckkserror("ckkskey", self, "Soft error loading classA key(%@), maybe locked: %@", set.classA, localerror);
+        probablyOkIfUnlocked = true;
     }
     localerror = nil;
 
     self.activeTLK = [set.tlk uuid];
 
     // Now that we're pretty sure we have the keys, are they shared appropriately?
-    if(SecCKKSShareTLKs()) {
-        // Check that every trusted peer has at least one TLK share
-        NSSet<id<CKKSPeer>>* missingShares = [self _onqueueFindPeersMissingShare:set.tlk error:&localerror];
-        if(localerror) {
-            if(error) {
-                *error = localerror;
-            }
-            return SecCKKSZoneKeyStateError;
+    // Check that every trusted peer has at least one TLK share
+    NSSet<id<CKKSPeer>>* missingShares = [self _onqueueFindPeersMissingShare:set.tlk error:&localerror];
+    if(localerror && [self.lockStateTracker isLockedError: localerror]) {
+        ckkserror("ckkskey", self, "Couldn't find missing TLK shares due to lock state: %@", localerror);
+        probablyOkIfUnlocked = true;
+
+    } else if(localerror) {
+        if(error) {
+            *error = localerror;
         }
+        ckkserror("ckkskey", self, "Error finding missing TLK shares: %@", localerror);
+        return SecCKKSZoneKeyStateError;
+    }
 
-        if(!missingShares || missingShares.count != 0u) {
-            localerror = [NSError errorWithDomain:CKKSErrorDomain code:CKKSMissingTLKShare
-                                      description:[NSString stringWithFormat:@"Missing shares for %lu peers", (unsigned long)missingShares.count]];
-            if(error) {
-                *error = localerror;
-            }
-            return SecCKKSZoneKeyStateHealTLKShares;
-        } else {
-            ckksnotice("ckksshare", self, "TLK (%@) is shared correctly", set.tlk);
+    if(!missingShares || missingShares.count != 0u) {
+        localerror = [NSError errorWithDomain:CKKSErrorDomain code:CKKSMissingTLKShare
+                                  description:[NSString stringWithFormat:@"Missing shares for %lu peers", (unsigned long)missingShares.count]];
+        if(error) {
+            *error = localerror;
         }
+        return SecCKKSZoneKeyStateHealTLKShares;
+    } else {
+        ckksnotice("ckksshare", self, "TLK (%@) is shared correctly", set.tlk);
     }
 
     // Got to the bottom? Cool! All keys are present and accounted for.
-    return SecCKKSZoneKeyStateReady;
+    return probablyOkIfUnlocked ? SecCKKSZoneKeyStateReadyPendingUnlock : SecCKKSZoneKeyStateReady;
 }
 
 - (void)_onqueueKeyHierarchyFetch {
 
     __block NSError* error = nil;
 
-    if(self.accountStatus != CKKSAccountStatusAvailable && syncCallback) {
-        // We're not logged into CloudKit, and therefore don't expect this item to be synced anytime particularly soon.
-        CKKSAccountStatus accountStatus = self.accountStatus;
-        dispatch_async(self.queue, ^{
-            syncCallback(false, [NSError errorWithDomain:@"securityd"
-                                                    code:errSecNotLoggedIn
-                                                userInfo:@{NSLocalizedDescriptionKey:
-                                                               [NSString stringWithFormat: @"No iCloud account available(%d); item is not expected to sync", (int)accountStatus]}]);
-        });
-
+    if(self.accountStatus == CKKSAccountStatusNoAccount && syncCallback) {
+        // We're positively not logged into CloudKit, and therefore don't expect this item to be synced anytime particularly soon.
+        [self callSyncCallbackWithErrorNoAccount: syncCallback];
         syncCallback = nil;
     }
 
     NSString* protection = (__bridge NSString*)SecDbItemGetCachedValueWithName(added ? added : deleted, kSecAttrAccessible);
     if(! ([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleWhenUnlocked] ||
           [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAfterFirstUnlock] ||
-          [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAlways])) {
+          [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAlwaysPrivate])) {
         ckksnotice("ckks", self, "skipping sync of device-bound(%@) item", protection);
         return;
     }
             CFReleaseNull(cferror);
         }
 
-        if(![self.keyHierarchyState isEqualToString: SecCKKSZoneKeyStateReady]) {
-            ckksnotice("ckks", self, "Key state not ready for new items; skipping");
-            return true;
-        }
-
         CKKSOutgoingQueueEntry* oqe = nil;
         if       (isAdd) {
             oqe = [CKKSOutgoingQueueEntry withItem: added   action: SecCKKSActionAdd    ckks:self error: &error];
 
         if(error) {
             ckkserror("ckks", self, "Couldn't create outgoing queue entry: %@", error);
+            self.droppedItems = true;
 
             // If the problem is 'no UUID', launch a scan operation to find and fix it
             // We don't want to fix it up here, in the closing moments of a transaction
             }
 
             // If the problem is 'couldn't load key', tell the key hierarchy state machine to fix it
-            // Then, launch a scan operation to find this item and upload it
             if([error.domain isEqualToString:CKKSErrorDomain] && error.code == errSecItemNotFound) {
                 [self _onqueueAdvanceKeyStateMachineToState: nil withError: nil];
-
-                ckksnotice("ckks", self, "Launching scan operation to refind %@", added);
-                CKKSScanLocalItemsOperation* scanOperation = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView: self ckoperationGroup:operationGroup];
-                [scanOperation addNullableDependency:self.keyStateReadyDependency];
-                [self scheduleOperation: scanOperation];
             }
 
             return true;
         }
 
-        if(!oqe) {
-            ckkserror("ckks", self, "Didn't create an outgoing queue entry, but no error given.");
-            return true;
-        }
-
         if(rateLimiter) {
             NSDate* limit = nil;
             NSInteger value = [rateLimiter judge:oqe at:[NSDate date] limitTime:&limit];
                 return false;
             }
 
-            ckksnotice("ckkscurrent", strongSelf, "Retrieved current item pointer: %@", cip);
+            ckksinfo("ckkscurrent", strongSelf, "Retrieved current item pointer: %@", cip);
             complete(cip.currentItemUUID, NULL);
             return true;
         }];
     NSString* protection = (__bridge NSString*)SecDbItemGetCachedValueWithName(item, kSecAttrAccessible);
     if([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleWhenUnlocked]) {
         class = SecCKKSKeyClassA;
-    } else if([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAlways] ||
+    } else if([protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAlwaysPrivate] ||
               [protection isEqualToString: (__bridge NSString*)kSecAttrAccessibleAfterFirstUnlock]) {
         class = SecCKKSKeyClassC;
     } else {
 }
 
 - (CKKSOutgoingQueueOperation*)processOutgoingQueueAfter:(CKKSResultOperation*)after ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
-    if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckks", self, "Skipping processOutgoingQueue due to disabled CKKS");
-        return nil;
-    }
-
     CKKSOutgoingQueueOperation* outgoingop =
             (CKKSOutgoingQueueOperation*) [self findFirstPendingOperation:self.outgoingQueueOperations
                                                                   ofClass:[CKKSOutgoingQueueOperation class]];
 
             // Will log any pending dependencies as well
             ckksnotice("ckksoutgoing", self, "Returning existing %@", outgoingop);
+
+            // Shouldn't be necessary, but can't hurt
+            [self.outgoingQueueOperationScheduler trigger];
             return outgoingop;
         }
     }
     CKKSOutgoingQueueOperation* op = [[CKKSOutgoingQueueOperation alloc] initWithCKKSKeychainView:self ckoperationGroup:ckoperationGroup];
     op.name = @"outgoing-queue-operation";
     [op addNullableDependency:after];
+    [op addNullableDependency:self.outgoingQueueOperationScheduler.operationDependency];
+    [self.outgoingQueueOperationScheduler trigger];
 
     [op addNullableDependency: self.initialScanOperation];
 
 }
 
 - (CKKSIncomingQueueOperation*) processIncomingQueue:(bool)failOnClassA after: (CKKSResultOperation*) after {
-    if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckks", self, "Skipping processIncomingQueue due to disabled CKKS");
-        return nil;
-    }
-
     CKKSIncomingQueueOperation* incomingop = (CKKSIncomingQueueOperation*) [self findFirstPendingOperation:self.incomingQueueOperations];
     if(incomingop) {
         ckksinfo("ckks", self, "Skipping processIncomingQueue due to at least one pending instance");
 - (CKKSUpdateDeviceStateOperation*)updateDeviceState:(bool)rateLimit
                    waitForKeyHierarchyInitialization:(uint64_t)timeout
                                     ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
-    if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckks", self, "Skipping updateDeviceState due to disabled CKKS");
-        return nil;
-    }
     __weak __typeof(self) weakSelf = self;
 
     // If securityd just started, the key state might be in some transient early state. Wait a bit.
         }
         ckksnotice("ckksdevice", strongSelf, "Finished waiting for key hierarchy state, currently %@", strongSelf.keyHierarchyState);
     }];
+    [self.waitingQueue addOperation:waitForKeyReady];
 
     CKKSUpdateDeviceStateOperation* op = [[CKKSUpdateDeviceStateOperation alloc] initWithCKKSKeychainView:self rateLimit:rateLimit ckoperationGroup:ckoperationGroup];
     op.name = @"device-state-operation";
     [op linearDependenciesWithSelfFirst:self.outgoingQueueOperations];
 
     // CKKSUpdateDeviceStateOperations are special: they should fire even if we don't believe we're in an iCloud account.
-    [self scheduleAccountStatusOperation:waitForKeyReady];
-    [self scheduleAccountStatusOperation:op];
+    // They also shouldn't block or be blocked by any other operation; our wait operation above will handle that
+    [self scheduleOperationWithoutDependencies:op];
     return op;
 }
 
 }
 
 - (CKKSSynchronizeOperation*) resyncWithCloud {
-    if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckks", self, "Skipping resyncWithCloud due to disabled CKKS");
-        return nil;
-    }
-
     CKKSSynchronizeOperation* op = [[CKKSSynchronizeOperation alloc] initWithCKKSKeychainView: self];
     [self scheduleOperation: op];
     return op;
 }
 
+- (CKKSLocalSynchronizeOperation*)resyncLocal {
+    CKKSLocalSynchronizeOperation* op = [[CKKSLocalSynchronizeOperation alloc] initWithCKKSKeychainView:self];
+    [self scheduleOperation: op];
+    return op;
+}
+
 - (CKKSResultOperation*)fetchAndProcessCKChanges:(CKKSFetchBecause*)because {
     return [self fetchAndProcessCKChanges:because after:nil];
 }
         } else if(![ckme matchesCKRecord:record]) {
             ckkserror("ckksresync", self, "BUG: Local item doesn't match resynced CloudKit record: %@ %@", ckme, record);
         } else {
-            ckksnotice("ckksresync", self, "Already know about this item record, skipping update: %@", record);
-            return;
+            ckksnotice("ckksresync", self, "Already know about this item record, updating anyway: %@", record.recordID);
         }
     }
 
 
     // If we found an old version in the database; this might be an update
     if(ckme) {
-        if([ckme matchesCKRecord:record]) {
+        if([ckme matchesCKRecord:record] && !resync) {
             // This is almost certainly a record we uploaded; CKFetchChanges sends them back as new records
             ckksnotice("ckks", self, "CloudKit has told us of record we already know about; skipping update");
             return;
         }
     }
 
-    // For now, drop into the synckeys table as a 'remote' key, then ask for a rekey operation.
     CKKSKey* remotekey = [[CKKSKey alloc] initWithCKRecord: record];
 
-    // We received this from an update. Don't use, yet.
+    // Do we already know about this key?
+    CKKSKey* possibleLocalKey = [CKKSKey tryFromDatabase:remotekey.uuid zoneID:self.zoneID error:&error];
+    if(error) {
+        ckkserror("ckkskey", self, "Error findibg exsiting local key for %@: %@", remotekey, error);
+        // Go on, assuming there isn't a local key
+    } else if(possibleLocalKey && [possibleLocalKey matchesCKRecord:record]) {
+        // Okay, nothing new here. Update the CKRecord and move on.
+        // Note: If the new record doesn't match the local copy, we have to go through the whole dance below
+        possibleLocalKey.storedCKRecord = record;
+        [possibleLocalKey saveToDatabase:&error];
+
+        if(error) {
+            ckkserror("ckkskey", self, "Couldn't update existing key: %@: %@", possibleLocalKey, error);
+        }
+        return;
+    }
+
+    // Drop into the synckeys table as a 'remote' key, then ask for a rekey operation.
     remotekey.state = SecCKKSProcessedStateRemote;
     remotekey.currentkey = false;
 
     }
 }
 
+- (bool)_onqueueResetAllInflightOQE:(NSError**)error {
+    NSError* localError = nil;
+
+    while(true) {
+        NSArray<CKKSOutgoingQueueEntry*> * inflightQueueEntries = [CKKSOutgoingQueueEntry fetch:SecCKKSOutgoingQueueItemsAtOnce
+                                                                                          state:SecCKKSStateInFlight
+                                                                                         zoneID:self.zoneID
+                                                                                          error:&localError];
+
+        if(localError != nil) {
+            ckkserror("ckks", self, "Error finding inflight outgoing queue records: %@", localError);
+            if(error) {
+                *error = localError;
+            }
+            return false;
+        }
+
+        if([inflightQueueEntries count] == 0u) {
+            break;
+        }
+
+        for(CKKSOutgoingQueueEntry* oqe in inflightQueueEntries) {
+            [self _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateNew error:&localError];
+
+            if(localError) {
+                ckkserror("ckks", self, "Error fixing up inflight OQE(%@): %@", oqe, localError);
+                if(error) {
+                    *error = localError;
+                }
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
 - (bool)_onqueueChangeOutgoingQueueEntry: (CKKSOutgoingQueueEntry*) oqe toState: (NSString*) state error: (NSError* __autoreleasing*) error {
     dispatch_assert_queue(self.queue);
 
             ckkserror("ckks", self, "Couldn't delete %@: %@", oqe, localerror);
         }
 
+    } else if([oqe.state isEqualToString:SecCKKSStateInFlight] && [state isEqualToString:SecCKKSStateNew]) {
+        // An in-flight OQE is moving to new? See if it's been superceded
+        CKKSOutgoingQueueEntry* newOQE = [CKKSOutgoingQueueEntry tryFromDatabase:oqe.uuid state:SecCKKSStateNew zoneID:self.zoneID error:&localerror];
+        if(localerror) {
+            ckkserror("ckksoutgoing", self, "Couldn't fetch an overwriting OQE, assuming one doesn't exist: %@", localerror);
+            newOQE = nil;
+        }
+
+        if(newOQE) {
+            ckksnotice("ckksoutgoing", self, "New modification has come in behind inflight %@; dropping failed change", oqe);
+            // recurse for that lovely code reuse
+            [self _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateDeleted error:&localerror];
+            if(localerror) {
+                ckkserror("ckksoutgoing", self, "Couldn't delete in-flight OQE: %@", localerror);
+                if(error) {
+                    *error = localerror;
+                }
+            }
+        } else {
+            oqe.state = state;
+            [oqe saveToDatabase: &localerror];
+            if(localerror) {
+                ckkserror("ckks", self, "Couldn't save %@ as %@: %@", oqe, state, localerror);
+            }
+        }
+
     } else {
         oqe.state = state;
         [oqe saveToDatabase: &localerror];
     }
     NSError* localerror = nil;
 
-    oqe.state = SecCKKSStateError;
-    [oqe saveToDatabase: &localerror];
+    // Now, delete the OQE: it's never coming back
+    [oqe deleteFromDatabase:&localerror];
     if(localerror) {
-        ckkserror("ckks", self, "Couldn't set %@ as error: %@", oqe, localerror);
+        ckkserror("ckks", self, "Couldn't delete %@ (due to error %@): %@", oqe, itemError, localerror);
     }
 
     if(error && localerror) {
     return localerror == nil;
 }
 
-- (bool)_onQueueUpdateLatestManifestWithError:(NSError**)error
+- (bool)_onqueueUpdateLatestManifestWithError:(NSError**)error
 {
     dispatch_assert_queue(self.queue);
     CKKSManifest* manifest = [CKKSManifest latestTrustedManifestForZone:self.zoneName error:error];
     // First, if we have a local identity, check for any TLK shares
     NSError* localerror = nil;
 
-    if(SecCKKSShareTLKs()) {
-        if(![proposedTLK wrapsSelf]) {
-            ckkserror("ckksshare", self, "Potential TLK %@ does not wrap self; skipping TLK share checking", proposedTLK);
-        } else {
-            if(!self.currentSelfPeers.currentSelf || self.currentSelfPeersError) {
-                ckkserror("ckksshare", self, "Couldn't fetch self peers: %@", self.currentSelfPeersError);
-                if(error) {
-                    *error = self.currentSelfPeersError;
-                }
-                return false;
+    if(![proposedTLK wrapsSelf]) {
+        ckkserror("ckksshare", self, "Potential TLK %@ does not wrap self; skipping TLK share checking", proposedTLK);
+    } else {
+        if(!self.currentSelfPeers.currentSelf || self.currentSelfPeersError) {
+            ckkserror("ckksshare", self, "Couldn't fetch self peers: %@", self.currentSelfPeersError);
+            if(error) {
+                *error = self.currentSelfPeersError;
             }
+            return false;
+        }
 
-            if(!self.currentTrustedPeers || self.currentTrustedPeersError) {
-                ckkserror("ckksshare", self, "Couldn't fetch trusted peers: %@", self.currentTrustedPeersError);
-                if(error) {
-                    *error = self.currentTrustedPeersError;
-                }
-                return false;
+        if(!self.currentTrustedPeers || self.currentTrustedPeersError) {
+            ckkserror("ckksshare", self, "Couldn't fetch trusted peers: %@", self.currentTrustedPeersError);
+            if(error) {
+                *error = self.currentTrustedPeersError;
             }
+            return false;
+        }
 
-            NSArray<CKKSTLKShare*>* possibleShares = [CKKSTLKShare allFor:self.currentSelfPeers.currentSelf.peerID
-                                                                  keyUUID:proposedTLK.uuid
-                                                                   zoneID:self.zoneID
-                                                                    error:&localerror];
-            if(localerror) {
-                ckkserror("ckksshare", self, "Error fetching CKKSTLKShares: %@", localerror);
-            }
+        NSArray<CKKSTLKShare*>* possibleShares = [CKKSTLKShare allFor:self.currentSelfPeers.currentSelf.peerID
+                                                              keyUUID:proposedTLK.uuid
+                                                               zoneID:self.zoneID
+                                                                error:&localerror];
+        if(localerror) {
+            ckkserror("ckksshare", self, "Error fetching CKKSTLKShares: %@", localerror);
+        }
 
-            if(possibleShares.count == 0) {
-                ckksnotice("ckksshare", self, "No CKKSTLKShares for %@", proposedTLK);
-            }
+        if(possibleShares.count == 0) {
+            ckksnotice("ckksshare", self, "No CKKSTLKShares for %@", proposedTLK);
+        }
 
-            for(CKKSTLKShare* possibleShare in possibleShares) {
-                NSError* possibleShareError = nil;
-                ckksnotice("ckksshare", self, "Checking possible TLK share %@ as %@",
-                           possibleShare, self.currentSelfPeers.currentSelf);
+        for(CKKSTLKShare* possibleShare in possibleShares) {
+            NSError* possibleShareError = nil;
+            ckksnotice("ckksshare", self, "Checking possible TLK share %@ as %@",
+                       possibleShare, self.currentSelfPeers.currentSelf);
 
-                CKKSKey* possibleKey = [possibleShare recoverTLK:self.currentSelfPeers.currentSelf
-                                                    trustedPeers:self.currentTrustedPeers
-                                                           error:&possibleShareError];
+            CKKSKey* possibleKey = [possibleShare recoverTLK:self.currentSelfPeers.currentSelf
+                                                trustedPeers:self.currentTrustedPeers
+                                                       error:&possibleShareError];
 
-                if(possibleShareError) {
-                    ckkserror("ckksshare", self, "Unable to unwrap TLKShare(%@) as %@: %@",
-                              possibleShare, self.currentSelfPeers.currentSelf, possibleShareError);
-                    ckkserror("ckksshare", self, "Current trust set: %@", self.currentTrustedPeers);
-                    // TODO: save error
-                    continue;
-                }
+            if(possibleShareError) {
+                ckkserror("ckksshare", self, "Unable to unwrap TLKShare(%@) as %@: %@",
+                          possibleShare, self.currentSelfPeers.currentSelf, possibleShareError);
+                ckkserror("ckksshare", self, "Current trust set: %@", self.currentTrustedPeers);
+                // TODO: save error
+                continue;
+            }
 
-                bool result = [proposedTLK trySelfWrappedKeyCandidate:possibleKey.aessivkey error:&possibleShareError];
-                if(possibleShareError) {
-                    ckkserror("ckksshare", self, "Unwrapped TLKShare(%@) does not unwrap proposed TLK(%@) as %@: %@",
-                              possibleShare, proposedTLK, self.currentSelfPeers.currentSelf, possibleShareError);
-                    // TODO save error
-                    continue;
-                }
+            bool result = [proposedTLK trySelfWrappedKeyCandidate:possibleKey.aessivkey error:&possibleShareError];
+            if(possibleShareError) {
+                ckkserror("ckksshare", self, "Unwrapped TLKShare(%@) does not unwrap proposed TLK(%@) as %@: %@",
+                          possibleShare, proposedTLK, self.currentSelfPeers.currentSelf, possibleShareError);
+                // TODO save error
+                continue;
+            }
 
-                if(result) {
-                    ckksnotice("ckksshare", self, "TLKShare(%@) unlocked TLK(%@) as %@",
-                               possibleShare, proposedTLK, self.currentSelfPeers.currentSelf);
-
-                    // The proposed TLK is trusted key material. Persist it as a "trusted" key.
-                    [proposedTLK saveKeyMaterialToKeychain:true error: &possibleShareError];
-                    if(possibleShareError) {
-                        ckkserror("ckksshare", self, "Couldn't store the new TLK(%@) to the keychain: %@", proposedTLK, possibleShareError);
-                        if(error) {
-                            *error = possibleShareError;
-                        }
-                        return false;
-                    }
+            if(result) {
+                ckksnotice("ckksshare", self, "TLKShare(%@) unlocked TLK(%@) as %@",
+                           possibleShare, proposedTLK, self.currentSelfPeers.currentSelf);
 
-                    return true;
+                // The proposed TLK is trusted key material. Persist it as a "trusted" key.
+                [proposedTLK saveKeyMaterialToKeychain:true error: &possibleShareError];
+                if(possibleShareError) {
+                    ckkserror("ckksshare", self, "Couldn't store the new TLK(%@) to the keychain: %@", proposedTLK, possibleShareError);
+                    if(error) {
+                        *error = possibleShareError;
+                    }
+                    return false;
                 }
+
+                return true;
             }
         }
-
-    } else {
-        ckksnotice("ckks", self, "No current self identity. Skipping TLK shares.")
     }
 
     if([proposedTLK loadKeyMaterialFromKeychain:error]) {
     });
 }
 
-// Use this if you have a potential database connection already
-- (void) dispatchSyncWithConnection: (SecDbConnectionRef) dbconn block: (bool (^)(void)) block {
-    if(dbconn) {
-        dispatch_sync(self.queue, ^{
-            CFErrorRef cferror = NULL;
-            kc_transaction_type(dbconn, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, block);
+- (bool)dispatchSyncWithConnection:(SecDbConnectionRef _Nonnull)dbconn block:(bool (^)(void))block {
+    CFErrorRef cferror = NULL;
 
-            if(cferror) {
-                ckkserror("ckks", self, "error doing database transaction (sync), major problems ahead: %@", cferror);
-            }
+    // Take the DB transaction, then get on the local queue.
+    // In the case of exclusive DB transactions, we don't really _need_ the local queue, but, it's here for future use.
+    bool ret = kc_transaction_type(dbconn, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, ^bool{
+        __block bool ok = false;
+
+        dispatch_sync(self.queue, ^{
+            ok = block();
         });
-    } else {
-        [self dispatchSync: block];
+
+        return ok;
+    });
+
+    if(cferror) {
+        ckkserror("ckks", self, "error doing database transaction, major problems ahead: %@", cferror);
     }
+    return ret;
 }
 
-- (void) dispatchSync: (bool (^)(void)) block {
+- (void)dispatchSync: (bool (^)(void)) block {
     // important enough to block this thread. Must get a connection first, though!
-    __weak __typeof(self) weakSelf = self;
-
     CFErrorRef cferror = NULL;
     kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
-        __strong __typeof(weakSelf) strongSelf = weakSelf;
-        if(!strongSelf) {
-            ckkserror("ckks", strongSelf, "received callback for released object");
-            return false;
-        }
-
-        __block bool ok = false;
-        __block CFErrorRef cferror = NULL;
-
-        dispatch_sync(strongSelf.queue, ^{
-            ok = kc_transaction_type(dbt, kSecDbExclusiveRemoteCKKSTransactionType, &cferror, block);
-        });
-        return ok;
+        return [self dispatchSyncWithConnection:dbt block:block];
     });
     if(cferror) {
-        ckkserror("ckks", self, "error getting database connection (sync), major problems ahead: %@", cferror);
+        ckkserror("ckks", self, "error getting database connection, major problems ahead: %@", cferror);
     }
 }
 
 #pragma mark - CKKSZoneUpdateReceiver
 
 - (void)notifyZoneChange: (CKRecordZoneNotification*) notification {
-    ckksinfo("ckks", self, "hurray, got a zone change for %@ %@", self, notification);
+    ckksnotice("ckks", self, "received a zone change notification for %@ %@", self, notification);
 
     [self fetchAndProcessCKChanges:CKKSFetchBecauseAPNS];
 }
 
-// Must be on the queue when this is called
 - (void)handleCKLogin {
-    dispatch_assert_queue(self.queue);
+    ckksnotice("ckks", self, "received a notification of CK login");
 
-    if(!self.setupStarted) {
-        [self _onqueueInitializeZone];
-    } else {
-        ckksinfo("ckks", self, "ignoring login as setup has already started");
-    }
+    __weak __typeof(self) weakSelf = self;
+    CKKSResultOperation* login = [CKKSResultOperation named:@"ckks-login" withBlock:^{
+        __strong __typeof(self) strongSelf = weakSelf;
+
+        [strongSelf dispatchSync:^bool{
+            strongSelf.accountStatus = CKKSAccountStatusAvailable;
+            [strongSelf _onqueueHandleCKLogin];
+            return true;
+        }];
+    }];
+
+    [self scheduleAccountStatusOperation:login];
 }
 
-- (void)handleCKLogout {
-    NSBlockOperation* logout = [NSBlockOperation blockOperationWithBlock: ^{
-        [self dispatchSync:^bool {
-            ckksnotice("ckks", self, "received a notification of CK logout for %@", self.zoneName);
-            NSError* error = nil;
+- (void)superHandleCKLogout {
+    [super handleCKLogout];
+}
 
-            [self _onqueueResetLocalData: &error];
+- (void)handleCKLogout {
+    __weak __typeof(self) weakSelf = self;
+    CKKSResultOperation* logout = [CKKSResultOperation named:@"ckks-logout" withBlock: ^{
+        __strong __typeof(self) strongSelf = weakSelf;
+        if(!strongSelf) {
+            return;
+        }
+        [strongSelf dispatchSync:^bool {
+            ckksnotice("ckks", strongSelf, "received a notification of CK logout");
+            [strongSelf superHandleCKLogout];
 
+            NSError* error = nil;
+            [strongSelf _onqueueResetLocalData: &error];
             if(error) {
-                ckkserror("ckks", self, "error while resetting local data: %@", error);
+                ckkserror("ckks", strongSelf, "error while resetting local data: %@", error);
+            }
+
+            // Tell all pending sync clients that we don't expect to ever sync
+            for(NSString* callbackUUID in strongSelf.pendingSyncCallbacks.allKeys) {
+                [strongSelf callSyncCallbackWithErrorNoAccount:strongSelf.pendingSyncCallbacks[callbackUUID]];
+                strongSelf.pendingSyncCallbacks[callbackUUID] = nil;
             }
+
+            strongSelf.loggedIn = [[CKKSCondition alloc] initToChain: strongSelf.loggedIn];
+            [strongSelf.loggedOut fulfill];
+
             return true;
         }];
     }];
 
-    logout.name = @"cloudkit-logout";
     [self scheduleAccountStatusOperation: logout];
 }
 
+- (void)callSyncCallbackWithErrorNoAccount:(SecBoolNSErrorCallback)syncCallback {
+    CKKSAccountStatus accountStatus = self.accountStatus;
+    dispatch_async(self.queue, ^{
+        syncCallback(false, [NSError errorWithDomain:@"securityd"
+                                                code:errSecNotLoggedIn
+                                            userInfo:@{NSLocalizedDescriptionKey:
+                                                           [NSString stringWithFormat: @"No iCloud account available(%d); item is not expected to sync", (int)accountStatus]}]);
+    });
+}
+
 #pragma mark - CKKSChangeFetcherErrorOracle
 
 - (bool) isFatalCKFetchError: (NSError*) error {
                 ckksnotice("ckks", strongSelf, "CloudKit-inspired local reset of %@ ended with error: %@", strongSelf.zoneID, error);
             } else {
                 ckksnotice("ckksreset", strongSelf, "re-initializing zone %@", strongSelf.zoneID);
-                [strongSelf initializeZone];
+                [self.initializeScheduler trigger];
             }
         }];
 
         CKKSResultOperation* resetHandler = [CKKSResultOperation named:@"reset-handler" withBlock:^{
             __strong __typeof(self) strongSelf = weakSelf;
             if(!strongSelf) {
-                ckkserror("ckks", strongSelf, "received callback for released object");
+                ckkserror("ckksreset", strongSelf, "received callback for released object");
                 return;
             }
 
             if(resetOp.error) {
-                ckksnotice("ckks", strongSelf, "CloudKit-inspired zone reset of %@ ended with error: %@", strongSelf.zoneID, resetOp.error);
-            } else {
-                ckksnotice("ckksreset", strongSelf, "re-initializing zone %@", strongSelf.zoneID);
-                [strongSelf initializeZone];
+                ckksnotice("ckksreset", strongSelf, "CloudKit-inspired zone reset of %@ ended with error: %@", strongSelf.zoneID, resetOp.error);
             }
         }];
 
 }
 
 - (CKKSResultOperation*)waitForFetchAndIncomingQueueProcessing {
-    if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckks", self, "Due to disabled CKKS, returning fast from waitForFetchAndIncomingQueueProcessing");
-        return nil;
-    }
-
     CKKSResultOperation* op = [self fetchAndProcessCKChanges:CKKSFetchBecauseTesting];
     [op waitUntilFinished];
     return op;
     }
     [self.incomingQueueOperations removeAllObjects];
 
-    // Don't send any more notifications, either
-    _notifierClass = nil;
-
     [super cancelAllOperations];
 
     [self dispatchSync:^bool{
     }];
 }
 
+- (void)halt {
+    [super halt];
+
+    // Don't send any more notifications, either
+    _notifierClass = nil;
+}
+
 - (NSDictionary*)status {
 #define stringify(obj) CKKSNilToNSNull([obj description])
 #define boolstr(obj) (!!(obj) ? @"yes" : @"no")
                  @"lockstatetracker":    stringify(self.lockStateTracker),
                  @"accounttracker":      stringify(self.accountTracker),
                  @"fetcher":             stringify(self.zoneChangeFetcher),
-                 @"setup":               boolstr(self.setupComplete),
+                 @"setup":               boolstr([self.viewSetupOperation isFinished]),
                  @"zoneCreated":         boolstr(self.zoneCreated),
                  @"zoneCreatedError":    stringify(self.zoneCreatedError),
                  @"zoneSubscribed":      boolstr(self.zoneSubscribed),
diff --git a/keychain/ckks/CKKSLocalSynchronizeOperation.h b/keychain/ckks/CKKSLocalSynchronizeOperation.h
new file mode 100644 (file)
index 0000000..65e3d55
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2017 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#import <Foundation/Foundation.h>
+#import "keychain/ckks/CKKSGroupOperation.h"
+
+#if OCTAGON
+
+@class CKKSKeychainView;
+
+@interface CKKSLocalSynchronizeOperation : CKKSGroupOperation
+@property (weak) CKKSKeychainView* ckks;
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks;
+@end
+
+// Reload everything from the mirror table into the incoming queue
+// Does not process these items
+@interface CKKSReloadAllItemsOperation : CKKSResultOperation
+@property (weak) CKKSKeychainView* ckks;
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks;
+@end
+
+#endif  // OCTAGON
+
diff --git a/keychain/ckks/CKKSLocalSynchronizeOperation.m b/keychain/ckks/CKKSLocalSynchronizeOperation.m
new file mode 100644 (file)
index 0000000..5d14ba1
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#import "keychain/ckks/CKKSKeychainView.h"
+#import "keychain/ckks/CKKSGroupOperation.h"
+#import "keychain/ckks/CKKSLocalSynchronizeOperation.h"
+#import "keychain/ckks/CKKSFetchAllRecordZoneChangesOperation.h"
+#import "keychain/ckks/CKKSScanLocalItemsOperation.h"
+#import "keychain/ckks/CKKSMirrorEntry.h"
+#import "keychain/ckks/CloudKitCategories.h"
+
+#if OCTAGON
+
+@interface CKKSLocalSynchronizeOperation ()
+@property int32_t restartCount;
+@end
+
+@implementation CKKSLocalSynchronizeOperation
+
+- (instancetype)init {
+    return nil;
+}
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks
+{
+    if(self = [super init]) {
+        _ckks = ckks;
+        _restartCount = 0;
+
+        [self addNullableDependency:ckks.holdLocalSynchronizeOperation];
+    }
+    return self;
+}
+
+- (void)groupStart {
+    __weak __typeof(self) weakSelf = self;
+
+    /*
+     * A local synchronize is very similar to a CloudKit synchronize, but it won't cause any (non-essential)
+     * CloudKit operations to occur.
+     *
+     *  1. Finish processing the outgoing queue. You can't be in-sync with cloudkit if you have an update that hasn't propagated.
+     *  2. Process anything in the incoming queue as normal.
+     *          (Note that this might require the keybag to be unlocked.)
+     *
+     *  3. Take every item in the CKMirror, and check for its existence in the local keychain. If not present, add to the incoming queue.
+     *  4. Process the incoming queue again.
+     *  5. Scan the local keychain for items which exist locally but are not in CloudKit. Upload them.
+     *  6. If there are any such items in 4, restart the sync.
+     */
+
+    CKKSKeychainView* ckks = self.ckks;
+
+    // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety.
+    [ckks dispatchSync: ^bool{
+        if(self.cancelled) {
+            ckksnotice("ckksresync", ckks, "CKKSSynchronizeOperation cancelled, quitting");
+            return false;
+        }
+
+        //ckks.lastLocalSynchronizeOperation = self;
+
+        uint32_t steps = 5;
+
+        ckksinfo("ckksresync", ckks, "Beginning local resynchronize (attempt %u)", self.restartCount);
+
+        CKOperationGroup* operationGroup = [CKOperationGroup CKKSGroupWithName:@"ckks-resync-local"];
+
+        // Step 1
+        CKKSOutgoingQueueOperation* outgoingOp = [ckks processOutgoingQueue: operationGroup];
+        outgoingOp.name = [NSString stringWithFormat: @"resync-step%u-outgoing", self.restartCount * steps + 1];
+        [self dependOnBeforeGroupFinished:outgoingOp];
+
+        // Step 2
+        CKKSIncomingQueueOperation* incomingOp = [[CKKSIncomingQueueOperation alloc] initWithCKKSKeychainView:ckks errorOnClassAFailure:true];
+        incomingOp.name = [NSString stringWithFormat: @"resync-step%u-incoming", self.restartCount * steps + 2];
+        [incomingOp addSuccessDependency:outgoingOp];
+        [self runBeforeGroupFinished:incomingOp];
+
+        // Step 3:
+        CKKSResultOperation* reloadOp = [[CKKSReloadAllItemsOperation alloc] initWithCKKSKeychainView:ckks];
+        reloadOp.name = [NSString stringWithFormat: @"resync-step%u-reload", self.restartCount * steps + 3];
+        [self runBeforeGroupFinished:reloadOp];
+
+        // Step 4
+        CKKSIncomingQueueOperation* incomingResyncOp = [[CKKSIncomingQueueOperation alloc] initWithCKKSKeychainView:ckks errorOnClassAFailure:true];
+        incomingResyncOp.name = [NSString stringWithFormat: @"resync-step%u-incoming-again", self.restartCount * steps + 4];
+        [incomingResyncOp addSuccessDependency: reloadOp];
+        [self runBeforeGroupFinished:incomingResyncOp];
+
+        // Step 5
+        CKKSScanLocalItemsOperation* scan = [[CKKSScanLocalItemsOperation alloc] initWithCKKSKeychainView:ckks ckoperationGroup:operationGroup];
+        scan.name = [NSString stringWithFormat: @"resync-step%u-scan", self.restartCount * steps + 5];
+        [scan addSuccessDependency: incomingResyncOp];
+        [self runBeforeGroupFinished: scan];
+
+        // Step 6
+        CKKSResultOperation* restart = [[CKKSResultOperation alloc] init];
+        restart.name = [NSString stringWithFormat: @"resync-step%u-consider-restart", self.restartCount * steps + 6];
+        [restart addExecutionBlock:^{
+            __strong __typeof(weakSelf) strongSelf = weakSelf;
+            if(!strongSelf) {
+                secerror("ckksresync: received callback for released object");
+                return;
+            }
+
+            if(scan.recordsFound > 0) {
+                if(strongSelf.restartCount >= 3) {
+                    // we've restarted too many times. Fail and stop.
+                    ckkserror("ckksresync", ckks, "restarted synchronization too often; Failing");
+                    strongSelf.error = [NSError errorWithDomain:@"securityd"
+                                                           code:2
+                                                       userInfo:@{NSLocalizedDescriptionKey: @"resynchronization restarted too many times; churn in database?"}];
+                } else {
+                    // restart the sync operation.
+                    strongSelf.restartCount += 1;
+                    ckkserror("ckksresync", ckks, "restarting synchronization operation due to new local items");
+                    [strongSelf groupStart];
+                }
+            }
+        }];
+
+        [restart addSuccessDependency: scan];
+        [self runBeforeGroupFinished: restart];
+
+        return true;
+    }];
+}
+
+@end;
+
+#pragma mark - CKKSReloadAllItemsOperation
+
+@implementation CKKSReloadAllItemsOperation
+
+- (instancetype)init {
+    return nil;
+}
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks
+{
+    if(self = [super init]) {
+        _ckks = ckks;
+    }
+    return self;
+}
+
+- (void)main {
+    CKKSKeychainView* strongCKKS = self.ckks;
+
+    [strongCKKS dispatchSync: ^bool{
+       NSError* error = nil;
+       NSArray<CKKSMirrorEntry*>* mirrorItems = [CKKSMirrorEntry all:strongCKKS.zoneID error:&error];
+
+       if(error) {
+           ckkserror("ckksresync", strongCKKS, "Couldn't fetch mirror items: %@", error);
+           self.error = error;
+           return false;
+       }
+
+       // Reload all entries back into the local keychain
+       // We _could_ scan for entries, but that'd be expensive
+       // In 36044942, we used to store only the CKRecord system fields in the ckrecord. To work around this, make a whole new CKRecord from the item.
+       for(CKKSMirrorEntry* ckme in mirrorItems) {
+           CKRecord* ckmeRecord = [ckme.item CKRecordWithZoneID:strongCKKS.zoneID];
+           if(!ckmeRecord) {
+               ckkserror("ckksresync", strongCKKS, "Couldn't make CKRecord for item: %@", ckme);
+               continue;
+           }
+
+           [strongCKKS _onqueueCKRecordChanged:ckmeRecord resync:true];
+       }
+
+       return true;
+    }];
+}
+@end
+#endif
+
index f2d62ddeade25892c193e4940debf87657c6afda..0780a2a7521b620520a3253b0cacdccd5123ab91 100644 (file)
 @interface CKKSLockStateTracker : NSObject
 @property NSOperation* unlockDependency;
 
--(instancetype)init;
+- (instancetype)init;
 
 // Force a recheck of the keybag lock state
--(void)recheck;
+- (void)recheck;
 
 // Check if this error code is related to keybag is locked and we should retry later
--(bool)isLockedError:(NSError *)error;
+- (bool)isLockedError:(NSError*)error;
 
 // Ask AKS if the user's keybag is locked
-+(bool)queryAKSLocked;
++ (bool)queryAKSLocked;
 @end
 
-#endif // OCTAGON
+#endif  // OCTAGON
index 249f722f45f2b0acad74f7fd75f51ad0d5803c1c..139614b6127e186e70eb7ac76ecd99330c97d054 100644 (file)
@@ -23,9 +23,9 @@
 
 #if OCTAGON
 
-#import "CKKSRecordHolder.h"
 #import <Foundation/Foundation.h>
 #import <SecurityFoundation/SFKey.h>
+#import "CKKSRecordHolder.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -76,9 +76,14 @@ extern NSString* const CKKSManifestGenCountKey;
 @interface CKKSEgoManifest : CKKSManifest
 
 + (nullable CKKSEgoManifest*)tryCurrentEgoManifestForZone:(NSString*)zone;
-+ (nullable instancetype)newManifestForZone:(NSString*)zone withItems:(NSArray<CKKSItem*>*)items peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs currentItems:(NSDictionary*)currentItems error:(NSError**)error;
-
-- (void)updateWithNewOrChangedRecords:(NSArray<CKRecord*>*)newOrChangedRecords deletedRecordIDs:(NSArray<CKRecordID*>*)deletedRecordIDs;
++ (nullable instancetype)newManifestForZone:(NSString*)zone
+                                  withItems:(NSArray<CKKSItem*>*)items
+                            peerManifestIDs:(NSArray<NSString*>*)peerManifestIDs
+                               currentItems:(NSDictionary*)currentItems
+                                      error:(NSError**)error;
+
+- (void)updateWithNewOrChangedRecords:(NSArray<CKRecord*>*)newOrChangedRecords
+                     deletedRecordIDs:(NSArray<CKRecordID*>*)deletedRecordIDs;
 - (void)setCurrentItemUUID:(NSString*)newCurrentItemUUID forIdentifier:(NSString*)currentPointerIdentifier;
 
 - (NSArray<CKRecord*>*)allCKRecordsWithZoneID:(CKRecordZoneID*)zoneID;
@@ -98,13 +103,18 @@ extern NSString* const CKKSManifestGenCountKey;
 
 @interface CKKSEgoManifest (UnitTesting)
 
-+ (nullable instancetype)newFakeManifestForZone:(NSString*)zone withItemRecords:(NSArray<CKRecord*>*)itemRecords currentItems:(NSDictionary*)currentItems signerID:(NSString*)signerID keyPair:(SFECKeyPair*)keyPair error:(NSError**)error;
++ (nullable instancetype)newFakeManifestForZone:(NSString*)zone
+                                withItemRecords:(NSArray<CKRecord*>*)itemRecords
+                                   currentItems:(NSDictionary*)currentItems
+                                       signerID:(NSString*)signerID
+                                        keyPair:(SFECKeyPair*)keyPair
+                                          error:(NSError**)error;
 
 @end
 
 @interface CKKSManifestInjectionPointHelper : NSObject
 
-@property (class) BOOL ignoreChanges; // turn to YES to have changes to the database get ignored by CKKSManifest to support negative testing
+@property (class) BOOL ignoreChanges;  // turn to YES to have changes to the database get ignored by CKKSManifest to support negative testing
 
 + (void)registerEgoPeerID:(NSString*)egoPeerID keyPair:(SFECKeyPair*)keyPair;
 
index ada18403df6666817165450ef393d5eab04e9f0e..66587a72f6d87f7b936c25af1193e6cca0b7deba 100644 (file)
@@ -28,7 +28,6 @@
 #import "CKKS.h"
 #import "CKKSItem.h"
 #import "CKKSCurrentItemPointer.h"
-#import "CKKSAnalyticsLogger.h"
 #import "utilities/der_plist.h"
 #import <securityd/SOSCloudCircleServer.h>
 #import <securityd/SecItemServer.h>
@@ -282,7 +281,7 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
 {
     CKKSAccountInfo* accountInfo = [[CKKSAccountInfo alloc] init];
 
-    [[CKKSEgoManifest egoHelper] performWithSigningKey:^(_SFECKeyPair* signingKey, NSError* error) {
+    [[CKKSEgoManifest egoHelper] performWithSigningKey:^(SFECKeyPair* signingKey, NSError* error) {
         accountInfo.signingKey = signingKey;
         if(error) {
             secerror("ckksmanifest: cannot get signing key from account: %@", error);
@@ -522,14 +521,12 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
     NSString* signatureBase64String = record[SecCKRecordManifestSignaturesKey];
     if (!signatureBase64String) {
         ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have signatures attached: %@", record);
-        [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
         return nil;
     }
     NSData* signatureDERData = [[NSData alloc] initWithBase64EncodedString:signatureBase64String options:0];
     NSDictionary* signaturesDict = [self signatureDictFromDERData:signatureDERData error:&error];
     if (error) {
         ckkserror("ckksmanifest", record.recordID.zoneID, "failed to initialize CKKSManifest from CKRecord because we could not form a signature dict from the record: %@", record);
-        [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
         return nil;
     }
 
@@ -546,7 +543,6 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
     NSString* digestBase64String = record[SecCKRecordManifestDigestValueKey];
     if (!digestBase64String) {
         ckkserror("ckksmanifest", record.recordID.zoneID, "attempt to create manifest from CKRecord that does not have a digest attached: %@", record);
-        [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
         return nil;
     }
     NSData* digestData = [[NSData alloc] initWithBase64EncodedString:digestBase64String options:0];
@@ -562,12 +558,8 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
                                 signerID:record[SecCKRecordManifestSignerIDKey]
                                   schema:schemaDict]) {
         self.storedCKRecord = record;
-        [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestCreateFromCKRecord"];
     }
-    else {
-        [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestCreateFromCKRecord" withAttributes:@{CKKSManifestZoneKey : record.recordID.zoneID.zoneName}];
-    }
-    
+
     return self;
 }
 
@@ -739,7 +731,6 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
     
     NSData* signatureDERData = [self derDataFromSignatureDict:self.signatures error:nil];
     if (!signatureDERData) {
-        [[CKKSAnalyticsLogger logger] logHardFailureForEventNamed:@"CKKSManifestUpdateRecord" withAttributes:@{CKKSManifestZoneKey : zoneID.zoneName, CKKSManifestSignerIDKey : _signerID, CKKSManifestGenCountKey : @(_generationCount)}];
         return record;
     }
     
@@ -756,7 +747,6 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
         record[key] = futureField;
     }];
     
-    [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestUpdateRecord"];
     return record;
 }
 
@@ -834,13 +824,6 @@ static NSUInteger LeafBucketIndexForUUID(NSString* uuid)
         }
     }
 
-    if (verified) {
-        [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"CKKSManifestValidateSelf"];
-    }
-    else {
-        [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"CKKSManifestValidateSelf" withAttributes:@{CKKSManifestZoneKey : _zoneName, CKKSManifestSignerIDKey : _signerID, CKKSManifestGenerationCountKey : @(_generationCount)}];
-    }
-    
     return verified;
 }
 
index 3e69d18b57bca5ddfe34bc2c43f67d22bbebe2a4..4a10f75f5df4b79845dff3350fc9947d549d26d8 100644 (file)
@@ -23,8 +23,8 @@
 
 #if OCTAGON
 
-#import "CKKSRecordHolder.h"
 #import <Foundation/Foundation.h>
+#import "CKKSRecordHolder.h"
 
 @class CKRecord;
 @class CKKSItem;
@@ -34,13 +34,13 @@ NS_ASSUME_NONNULL_BEGIN
 @interface CKKSManifestLeafRecord : CKKSCKRecordHolder
 
 + (BOOL)recordExistsForID:(NSString*)recordID;
-+ (instancetype)leafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error;
-+ (instancetype)tryLeafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing *)error;
++ (instancetype)leafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryLeafRecordForID:(NSString*)recordID error:(NSError* __autoreleasing*)error;
 + (NSString*)leafUUIDForRecordID:(NSString*)recordID;
 
 @property (nonatomic, readonly) NSString* uuid;
 @property (nonatomic, readonly) NSData* digestValue;
-@property (nonatomic, readonly) NSDictionary<NSString*, NSData*>* recordDigestDict; // keyed by record UUID
+@property (nonatomic, readonly) NSDictionary<NSString*, NSData*>* recordDigestDict;  // keyed by record UUID
 
 @end
 
index 9b1238dc54007416f3772bde9ac1f55256cf5fc7..3b2a8dc1874c96002ae94d23668d848ba08031ec 100644 (file)
 
 #if OCTAGON
 
-#import "CKKSSQLDatabaseObject.h"
-#import "CKKSItem.h"
-#include <utilities/SecDb.h>
 #include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
+#import "CKKSItem.h"
+#import "CKKSSQLDatabaseObject.h"
 
 #ifndef CKKSMirrorEntry_h
 #define CKKSMirrorEntry_h
 
 @property uint64_t wasCurrent;
 
--(instancetype)initWithCKKSItem:(CKKSItem*)item;
--(instancetype)initWithCKRecord:(CKRecord*)record;
--(void)setFromCKRecord: (CKRecord*) record;
-- (bool)matchesCKRecord: (CKRecord*) record;
+- (instancetype)initWithCKKSItem:(CKKSItem*)item;
+- (instancetype)initWithCKRecord:(CKRecord*)record;
+- (void)setFromCKRecord:(CKRecord*)record;
+- (bool)matchesCKRecord:(CKRecord*)record;
 
-+ (instancetype) fromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)fromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
-+ (NSDictionary<NSString*,NSNumber*>*)countsByParentKey:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSDictionary<NSString*, NSNumber*>*)countsByParentKey:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 @end
 
index fa7bac4933a1be65fe547aea3c5cca7f3ea2c29a..b8670e018332b3f25d7315ff539846157b2251a5 100644 (file)
@@ -23,6 +23,7 @@
 
 #import <Foundation/Foundation.h>
 #import <dispatch/dispatch.h>
+NS_ASSUME_NONNULL_BEGIN
 
 /*
  * The CKKSNearFutureScheduler is intended to rate-limit an operation. When
 
 @interface CKKSNearFutureScheduler : NSObject
 
-@property (readonly) NSDate* nextFireTime;
-@property void (^futureOperation)(void);
+@property (nullable, readonly) NSDate* nextFireTime;
+@property void (^futureBlock)(void);
 
--(instancetype)initWithName:(NSString*)name
-                      delay:(dispatch_time_t)ns
-           keepProcessAlive:(bool)keepProcessAlive
-                      block:(void (^)(void))futureOperation;
+// Will execute every time futureBlock is called, just after the future block.
+// Operations added in the futureBlock will receive the next operationDependency, so they won't run again until futureBlock occurs again.
+@property (nullable, readonly) NSOperation* operationDependency;
 
--(instancetype)initWithName:(NSString*)name
-               initialDelay:(dispatch_time_t)initialDelay
-            continuingDelay:(dispatch_time_t)continuingDelay
-           keepProcessAlive:(bool)keepProcessAlive
-                      block:(void (^)(void))futureOperation;
+- (instancetype)initWithName:(NSString*)name
+                       delay:(dispatch_time_t)ns
+            keepProcessAlive:(bool)keepProcessAlive
+                       block:(void (^_Nonnull)(void))futureOperation;
 
--(void)trigger;
+- (instancetype)initWithName:(NSString*)name
+                initialDelay:(dispatch_time_t)initialDelay
+             continuingDelay:(dispatch_time_t)continuingDelay
+            keepProcessAlive:(bool)keepProcessAlive
+                       block:(void (^_Nonnull)(void))futureBlock;
 
--(void)cancel;
+- (void)trigger;
+
+- (void)cancel;
 
 // Don't trigger again until at least this much time has passed.
--(void)waitUntil:(uint64_t)delay;
+- (void)waitUntil:(uint64_t)delay;
 
 @end
+
+NS_ASSUME_NONNULL_END
index 3a2f049d6add6e66d89f46b1ab8cec0314ac7097..5d1ed527eaa6b47739dd27c5301af901781bb7a9 100644 (file)
@@ -23,6 +23,7 @@
 
 #import "CKKSNearFutureScheduler.h"
 #import "CKKSCondition.h"
+#import "keychain/ckks/NSOperationCategories.h"
 #include <os/transaction_private.h>
 
 @interface CKKSNearFutureScheduler ()
@@ -30,6 +31,9 @@
 @property dispatch_time_t initialDelay;
 @property dispatch_time_t continuingDelay;
 
+@property NSOperation* operationDependency;
+@property (nonnull) NSOperationQueue* operationQueue;
+
 @property NSDate* predictedNextFireTime;
 @property bool liveRequest;
 @property CKKSCondition* liveRequestReceived; // Triggered when liveRequest goes to true.
 
 @implementation CKKSNearFutureScheduler
 
--(instancetype)initWithName:(NSString*)name delay:(dispatch_time_t)ns keepProcessAlive:(bool)keepProcessAlive block:(void (^)(void))futureOperation
+-(instancetype)initWithName:(NSString*)name delay:(dispatch_time_t)ns keepProcessAlive:(bool)keepProcessAlive block:(void (^)(void))futureBlock
 {
-    return [self initWithName:name initialDelay:ns continuingDelay:ns keepProcessAlive:keepProcessAlive block:futureOperation];
+    return [self initWithName:name initialDelay:ns continuingDelay:ns keepProcessAlive:keepProcessAlive block:futureBlock];
 }
 
 -(instancetype)initWithName:(NSString*)name
                initialDelay:(dispatch_time_t)initialDelay
             continuingDelay:(dispatch_time_t)continuingDelay
            keepProcessAlive:(bool)keepProcessAlive
-                      block:(void (^)(void))futureOperation
+                      block:(void (^)(void))futureBlock
 {
     if((self = [super init])) {
         _name = name;
         _queue = dispatch_queue_create([[NSString stringWithFormat:@"near-future-scheduler-%@",name] UTF8String], DISPATCH_QUEUE_SERIAL);
         _initialDelay = initialDelay;
         _continuingDelay = continuingDelay;
-        _futureOperation = futureOperation;
+        _futureBlock = futureBlock;
 
         _liveRequest = false;
         _liveRequestReceived = [[CKKSCondition alloc] init];
         _predictedNextFireTime = nil;
 
         _keepProcessAlive = keepProcessAlive;
+
+        _operationQueue = [[NSOperationQueue alloc] init];
+        _operationDependency = [self makeOperationDependency];
     }
     return self;
 }
 
+- (NSOperation*)makeOperationDependency {
+    return [NSBlockOperation named:[NSString stringWithFormat:@"nfs-%@", self.name] withBlock:^{}];
+}
+
 -(NSString*)description {
     NSDate* nextAt = self.nextFireTime;
     if(nextAt) {
@@ -86,7 +97,7 @@
     // If we have a live request, send the next fire time back. Otherwise, wait a tiny tiny bit to see if we receive a request.
     if(self.liveRequest) {
         return self.predictedNextFireTime;
-    } else if([self.liveRequestReceived wait:100*NSEC_PER_USEC] == 0) {
+    } else if([self.liveRequestReceived wait:50*NSEC_PER_USEC] == 0) {
         return self.predictedNextFireTime;
     }
 
     dispatch_assert_queue(self.queue);
 
     if(self.liveRequest) {
-        self.futureOperation();
+        // Put a new dependency in place, and save the old one for execution
+        NSOperation* dependency = self.operationDependency;
+        self.operationDependency = [self makeOperationDependency];
+
+        self.futureBlock();
         self.liveRequest = false;
         self.liveRequestReceived = [[CKKSCondition alloc] init];
         self.transaction = nil;
 
+        [self.operationQueue addOperation: dependency];
+
         self.predictedNextFireTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) self.continuingDelay) / (double) NSEC_PER_SEC];
     } else {
         // The timer has fired with no requests to call the block. Cancel it.
index 918dd14726bf7d31a49ea8f47c1de9f39c17c425..f75052e757707574efd3eaa6e51c9927968394f8 100644 (file)
@@ -36,5 +36,4 @@
 
 @end
 
-#endif // OCTAGON
-
+#endif  // OCTAGON
index bdccb8028e4d356a268735cace5ae3e88d3d57ce..b9d09a86d03928391f362bdae043117cd9b83e31 100644 (file)
 
         ckks.lastNewTLKOperation = self;
 
+        if(ckks.currentSelfPeersError) {
+            if([ckks.lockStateTracker isLockedError: ckks.currentSelfPeersError]) {
+                ckkserror("ckksshare", ckks, "Can't create new TLKs: keychain is locked so self peers are unavailable: %@", ckks.currentSelfPeersError);
+                [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
+            } else {
+                ckkserror("ckkstlk", ckks, "Couldn't create new TLKs because self peers aren't available: %@", ckks.currentSelfPeersError);
+                [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateNewTLKsFailed withError: ckks.currentSelfPeersError];
+            }
+            self.error = ckks.currentSelfPeersError;
+            return false;
+        }
+        if(ckks.currentTrustedPeersError) {
+            if([ckks.lockStateTracker isLockedError: ckks.currentTrustedPeersError]) {
+                ckkserror("ckksshare", ckks, "Can't create new TLKs: keychain is locked so trusted peers are unavailable: %@", ckks.currentTrustedPeersError);
+                [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
+            } else {
+                ckkserror("ckkstlk", ckks, "Couldn't create new TLKs because trusted peers aren't available: %@", ckks.currentTrustedPeersError);
+                [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateNewTLKsFailed withError: ckks.currentTrustedPeersError];
+            }
+            self.error = ckks.currentTrustedPeersError;
+            return false;
+        }
+
         NSError* error = nil;
 
         ckksinfo("ckkstlk", ckks, "Generating new TLK");
 
         // Generate the TLK sharing records for all trusted peers
         NSMutableSet<CKKSTLKShare*>* tlkShares = [NSMutableSet set];
-        if(SecCKKSShareTLKs()) {
-            for(id<CKKSPeer> trustedPeer in ckks.currentTrustedPeers) {
-                ckksnotice("ckkstlk", ckks, "Generating TLK(%@) share for %@", newTLK, trustedPeer);
-                CKKSTLKShare* share = [CKKSTLKShare share:newTLK as:ckks.currentSelfPeers.currentSelf to:trustedPeer epoch:-1 poisoned:0 error:&error];
+        for(id<CKKSPeer> trustedPeer in ckks.currentTrustedPeers) {
+            ckksnotice("ckkstlk", ckks, "Generating TLK(%@) share for %@", newTLK, trustedPeer);
+            CKKSTLKShare* share = [CKKSTLKShare share:newTLK as:ckks.currentSelfPeers.currentSelf to:trustedPeer epoch:-1 poisoned:0 error:&error];
 
-                [tlkShares addObject:share];
-                [recordsToSave addObject: [share CKRecordWithZoneID: ckks.zoneID]];
-            }
+            [tlkShares addObject:share];
+            [recordsToSave addObject: [share CKRecordWithZoneID: ckks.zoneID]];
         }
 
         // Use the spare operation trick to wait for the CKModifyRecordsOperation to complete
index 85324ce4f3a0915f8e114969084c472629a21570..64b550fdb9460f4848159f8715e6f320bb3d0f20 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
+#if OCTAGON
 #import <Foundation/Foundation.h>
 
+NS_ASSUME_NONNULL_BEGIN
+
 // There's terrible testing support for notify_post, but that's what our clients
 // are listening for. Use this structure to mock out notification sending for testing.
 
 @protocol CKKSNotifier
-+(void)post:(NSString*) notification;
++ (void)post:(NSString*)notification;
 @end
 
 @interface CKKSNotifyPostNotifier : NSObject <CKKSNotifier>
-+(void)post:(NSString*) notification;
++ (void)post:(NSString*)notification;
 @end
+
+NS_ASSUME_NONNULL_END
+
+#endif  //OCTAGON
index 916a4675915c3613e29bb5c79580809572939b1d..2591a046c9f94a9f145c728b881e573fc80c6ed7 100644 (file)
@@ -21,6 +21,8 @@
  * @APPLE_LICENSE_HEADER_END@
  */
 
+#if OCTAGON
+
 #import "CKKSNotifier.h"
 #import <notify.h>
 #import <utilities/debugging.h>
@@ -35,3 +37,5 @@
 }
 
 @end
+
+#endif // OCTAGON
index f8d1516f533149843d19be0294303e742b1d2705..aadeb473ac0082926f1c5f72760a89a6ffade45b 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#import "CKKSSQLDatabaseObject.h"
+#include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
 #import "CKKSItem.h"
 #import "CKKSMirrorEntry.h"
-#include <utilities/SecDb.h>
-#include <securityd/SecDbItem.h>
+#import "CKKSSQLDatabaseObject.h"
 
 #ifndef CKKSOutgoingQueueEntry_h
 #define CKKSOutgoingQueueEntry_h
 @interface CKKSOutgoingQueueEntry : CKKSSQLDatabaseObject
 
 @property CKKSItem* item;
-@property NSString* uuid; // property access to underlying CKKSItem
+@property NSString* uuid;  // property access to underlying CKKSItem
 
 @property NSString* action;
 @property NSString* state;
 @property NSString* accessgroup;
-@property NSDate* waitUntil; // If non-null, the time at which this entry should be processed
+@property NSDate* waitUntil;  // If non-null, the time at which this entry should be processed
 
-- (instancetype) initWithCKKSItem:(CKKSItem*) item
-                           action:(NSString*) action
-                            state:(NSString*) state
-                        waitUntil:(NSDate*) waitUntil
-                      accessGroup:(NSString*) accessgroup;
+- (instancetype)initWithCKKSItem:(CKKSItem*)item
+                          action:(NSString*)action
+                           state:(NSString*)state
+                       waitUntil:(NSDate*)waitUntil
+                     accessGroup:(NSString*)accessgroup;
 
-+ (instancetype) withItem: (SecDbItemRef) item action: (NSString*) action ckks:(CKKSKeychainView*)ckks error: (NSError * __autoreleasing *) error;
-+ (instancetype) fromDatabase: (NSString*) uuid state: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (NSString*) uuid state: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (instancetype)withItem:(SecDbItemRef)item
+                  action:(NSString*)action
+                    ckks:(CKKSKeychainView*)ckks
+                   error:(NSError* __autoreleasing*)error;
++ (instancetype)fromDatabase:(NSString*)uuid
+                       state:(NSString*)state
+                      zoneID:(CKRecordZoneID*)zoneID
+                       error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)uuid
+                          state:(NSString*)state
+                         zoneID:(CKRecordZoneID*)zoneID
+                          error:(NSError* __autoreleasing*)error;
 
-+ (NSArray<CKKSOutgoingQueueEntry*>*) fetch:(ssize_t) n state: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
-+ (NSArray<CKKSOutgoingQueueEntry*>*) allInState: (NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSArray<CKKSOutgoingQueueEntry*>*)fetch:(ssize_t)n
+                                     state:(NSString*)state
+                                    zoneID:(CKRecordZoneID*)zoneID
+                                     error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSOutgoingQueueEntry*>*)allInState:(NSString*)state
+                                         zoneID:(CKRecordZoneID*)zoneID
+                                          error:(NSError* __autoreleasing*)error;
 
-+ (NSDictionary<NSString*,NSNumber*>*)countsByState:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error;
++ (NSDictionary<NSString*, NSNumber*>*)countsByState:(CKRecordZoneID*)zoneID error:(NSError* __autoreleasing*)error;
 
 @end
 
index aeeed762dc6afb0bb6da1497c0fc6f6312e853a7..32348f4595b0439cd5a8cba41007f258c4099fd2 100644 (file)
@@ -41,4 +41,4 @@
 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
 
 @end
-#endif // OCTAGON
+#endif  // OCTAGON
index 96fbe10e472a2e7997f1c44ed0f99b63ceaf0dbe..ea8a9540d530e017d2de4d9245ce46e0954f5063 100644 (file)
@@ -29,7 +29,7 @@
 #import "CKKSOutgoingQueueEntry.h"
 #import "CKKSReencryptOutgoingItemsOperation.h"
 #import "CKKSManifest.h"
-#import "CKKSAnalyticsLogger.h"
+#import  "CKKSAnalyticsLogger.h"
 
 #include <securityd/SecItemServer.h>
 #include <securityd/SecItemDb.h>
@@ -87,6 +87,7 @@
 
         NSError* error = nil;
 
+
         // We only actually care about queue items in the 'new' state
         NSArray<CKKSOutgoingQueueEntry*> * queueEntries = [CKKSOutgoingQueueEntry fetch:SecCKKSOutgoingQueueItemsAtOnce state: SecCKKSStateNew zoneID:ckks.zoneID error:&error];
 
@@ -96,6 +97,8 @@
             return false;
         }
 
+        //[CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventOutgoingQueue zone:ckks.zoneName count:[queueEntries count]];
+
         ckksinfo("ckksoutgoing", ckks, "processing outgoing queue: %@", queueEntries);
 
         NSMutableDictionary<CKRecordID*, CKRecord*>* recordsToSave = [[NSMutableDictionary alloc] init];
                 }
 
             } else if ([oqe.action isEqualToString: SecCKKSActionDelete]) {
-                [recordIDsToDelete addObject: [[CKRecordID alloc] initWithRecordName: oqe.item.uuid zoneID: ckks.zoneID]];
+                CKRecordID* recordIDToDelete = [[CKRecordID alloc] initWithRecordName: oqe.item.uuid zoneID: ckks.zoneID];
+                [recordIDsToDelete addObject: recordIDToDelete];
+                [oqesModified addObject: recordIDToDelete];
 
                 [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateInFlight error:&error];
                 if(error) {
                 return;
             }
 
+            //CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
+
             [strongCKKS dispatchSync: ^bool{
                 if(ckerror) {
                     ckkserror("ckksoutgoing", strongCKKS, "error processing outgoing queue: %@", ckerror);
 
+                    /*[logger logRecoverableError:ckerror
+                                       forEvent:CKKSEventProcessOutgoingQueue
+                                         inView:strongCKKS
+                                 withAttributes:NULL];*/
+
                     // Tell CKKS about any out-of-date records
                     [strongCKKS _onqueueCKWriteFailed:ckerror attemptedRecordsChanged:recordsToSave];
 
                                 if([recordID.recordName isEqualToString: SecCKKSKeyClassA] ||
                                    [recordID.recordName isEqualToString: SecCKKSKeyClassC]) {
                                     // The current key pointers have updated without our knowledge, so CloudKit failed this operation. Mark all records as 'needs reencryption' and kick that off.
-                                    [strongSelf _onqueueModifyAllRecordsAsReencrypt: failedRecords.allKeys];
+                                    [strongSelf _onqueueModifyAllRecords:failedRecords.allKeys as:SecCKKSStateReencrypt];
 
                                     // Note that _onqueueCKWriteFailed is responsible for kicking the key state machine, so we don't need to do it here.
                                     // This will wait for the key hierarchy to become 'ready'
                                 // OQEs should be placed back into the 'new' state, unless they've been overwritten by a new OQE. Other records should be ignored.
 
                                 if([oqesModified containsObject:recordID]) {
-                                    NSError* error = nil;
-                                    CKKSOutgoingQueueEntry* inflightOQE = [CKKSOutgoingQueueEntry tryFromDatabase:recordID.recordName state:SecCKKSStateInFlight zoneID:recordID.zoneID error:&error];
-                                    CKKSOutgoingQueueEntry* newOQE = [CKKSOutgoingQueueEntry tryFromDatabase:recordID.recordName state:SecCKKSStateNew zoneID:recordID.zoneID error:&error];
-                                    if(error) {
-                                        ckkserror("ckksoutgoing", strongCKKS, "Couldn't try to fetch an overwriting OQE: %@", error);
-                                    }
-
-                                    if(newOQE) {
-                                        ckksnotice("ckksoutgoing", strongCKKS, "New modification has come in behind failed change for %@; dropping failed change", inflightOQE);
-                                        [strongCKKS _onqueueChangeOutgoingQueueEntry:inflightOQE toState:SecCKKSStateDeleted error:&error];
-                                        if(error) {
-                                            ckkserror("ckksoutgoing", strongCKKS, "Couldn't delete in-flight OQE: %@", error);
-                                        }
-                                    } else {
-                                        [strongCKKS _onqueueChangeOutgoingQueueEntry:inflightOQE toState:SecCKKSStateNew error:&error];
+                                    NSError* localerror = nil;
+                                    CKKSOutgoingQueueEntry* inflightOQE = [CKKSOutgoingQueueEntry tryFromDatabase:recordID.recordName state:SecCKKSStateInFlight zoneID:recordID.zoneID error:&localerror];
+                                    [strongCKKS _onqueueChangeOutgoingQueueEntry:inflightOQE toState:SecCKKSStateNew error:&localerror];
+                                    if(localerror) {
+                                        ckkserror("ckksoutgoing", strongCKKS, "Couldn't clean up outgoing queue entry: %@", localerror);
                                     }
                                 }
 
-                            } else if ([recordID.recordName hasPrefix:@"Manifest:-:"] || [recordID.recordName hasPrefix:@"ManifestLeafRecord:-:"]) {
-                                [[CKKSAnalyticsLogger logger] logSoftFailureForEventNamed:@"ManifestUpload" withAttributes:@{CKKSManifestZoneKey : strongCKKS.zoneID.zoneName, CKKSManifestSignerIDKey : strongCKKS.egoManifest.signerID, CKKSManifestGenCountKey : @(strongCKKS.egoManifest.generationCount)}];
                             } else {
                                 // Some unknown error occurred on this record. If it's an OQE, move it to the error state.
                                 ckkserror("ckksoutgoing", strongCKKS, "Unknown error on row: %@ %@", recordID, recordError);
                                 }
                             }
                         }
+                    } else {
+                        // Some non-partial error occured. We should place all "inflight" OQEs back into the outgoing queue.
+                        ckksnotice("ckks", strongCKKS, "Error is scary: putting all inflight OQEs back into state 'new'");
+                        [strongSelf _onqueueModifyAllRecords:[oqesModified allObjects] as:SecCKKSStateNew];
                     }
 
                     strongSelf.error = error;
                         }
 
                     } else if ([record.recordType isEqualToString:SecCKRecordManifestType]) {
-                        [[CKKSAnalyticsLogger logger] logSuccessForEventNamed:@"ManifestUpload"];
+
                     } else if (![record.recordType isEqualToString:SecCKRecordManifestLeafType]) {
                         ckkserror("ckksoutgoing", strongCKKS, "unknown record type in results: %@", record);
                     }
 
                 if(strongSelf.error) {
                     ckkserror("ckksoutgoing", strongCKKS, "Operation failed; rolling back: %@", strongSelf.error);
+                    /*[logger logRecoverableError:strongSelf.error
+                                       forEvent:CKKSEventProcessOutgoingQueue
+                                         inView:strongCKKS
+                                 withAttributes:NULL];*/
                     return false;
+                } else {
+                    //[logger logSuccessForEvent:CKKSEventProcessOutgoingQueue inView:strongCKKS];
                 }
                 return true;
             }];
         self.modifyRecordsOperation.savePolicy = CKRecordSaveIfServerRecordUnchanged;
         self.modifyRecordsOperation.group = self.ckoperationGroup;
         ckksnotice("ckksoutgoing", ckks, "Operation group is %@", self.ckoperationGroup);
+        ckksnotice("ckksoutgoing", ckks, "Beginning upload for %@ %@", recordsToSave.allValues, recordIDsToDelete);
 
         self.modifyRecordsOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
             __strong __typeof(weakSelf) strongSelf = weakSelf;
             __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
 
             if(!error) {
-                ckksnotice("ckksoutgoing", blockCKKS, "Record upload successful for %@", record.recordID.recordName);
+                ckksnotice("ckksoutgoing", blockCKKS, "Record upload successful for %@ (%@)", record.recordID.recordName, record.recordChangeTag);
             } else {
                 ckkserror("ckksoutgoing", blockCKKS, "error on row: %@ %@", error, record);
             }
 }
 
 
-- (void)_onqueueModifyAllRecordsAsReencrypt: (NSArray<CKRecordID*>*) recordIDs {
+- (void)_onqueueModifyAllRecords:(NSArray<CKRecordID*>*)recordIDs as:(CKKSItemState*)state {
     CKKSKeychainView* ckks = self.ckks;
     if(!ckks) {
         ckkserror("ckksoutgoing", ckks, "no CKKS object");
             // Nothing to do here. We need a whole key refetch and synchronize.
         } else {
             CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry fromDatabase:recordID.recordName state: SecCKKSStateInFlight zoneID:ckks.zoneID error:&error];
-            [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:SecCKKSStateReencrypt error:&error];
+            [ckks _onqueueChangeOutgoingQueueEntry:oqe toState:state error:&error];
             if(error) {
-                ckkserror("ckksoutgoing", ckks, "Couldn't set OQE %@ as reencrypt: %@", recordID.recordName, error);
+                ckkserror("ckksoutgoing", ckks, "Couldn't set OQE %@ as %@: %@", recordID.recordName, state, error);
                 self.error = error;
             }
             count ++;
         }
     }
 
-    SecADAddValueForScalarKey((__bridge CFStringRef) SecCKKSAggdItemReencryption, count);
+    if([state isEqualToString:SecCKKSStateReencrypt]) {
+        SecADAddValueForScalarKey((__bridge CFStringRef) SecCKKSAggdItemReencryption, count);
+    }
 }
 
 @end;
index dd9f93943f1912dd325895c69f416bd5d8a5ddfd..9c6dc3bed0136d6061118c91cc0ba33c29fd1f30 100644 (file)
 #import <Foundation/Foundation.h>
 #import <SecurityFoundation/SFKey.h>
 
+NS_ASSUME_NONNULL_BEGIN
 
 // ==== Peer protocols ====
 
 @protocol CKKSPeer <NSObject>
 @property (readonly) NSString* peerID;
-@property (readonly) SFECPublicKey* publicEncryptionKey;
-@property (readonly) SFECPublicKey* publicSigningKey;
+@property (nullable, readonly) SFECPublicKey* publicEncryptionKey;
+@property (nullable, readonly) SFECPublicKey* publicSigningKey;
 
 // Not exactly isEqual, since this only compares peerID
 - (bool)matchesPeer:(id<CKKSPeer>)peer;
@@ -47,8 +48,8 @@
 
 @interface CKKSSelves : NSObject
 @property id<CKKSSelfPeer> currentSelf;
-@property NSSet<id<CKKSSelfPeer>>* allSelves;
-- (instancetype)initWithCurrent:(id<CKKSSelfPeer>)selfPeer allSelves:(NSSet<id<CKKSSelfPeer>>*)allSelves;
+@property (nullable) NSSet<id<CKKSSelfPeer>>* allSelves;
+- (instancetype)initWithCurrent:(id<CKKSSelfPeer>)selfPeer allSelves:(NSSet<id<CKKSSelfPeer>>* _Nullable)allSelves;
 @end
 
 // ==== Peer handler protocols ====
@@ -56,8 +57,8 @@
 @protocol CKKSPeerUpdateListener;
 
 @protocol CKKSPeerProvider <NSObject>
-- (CKKSSelves*)fetchSelfPeers:(NSError* __autoreleasing *)error;
-- (NSSet<id<CKKSPeer>>*)fetchTrustedPeers:(NSError* __autoreleasing *)error;
+- (CKKSSelves* _Nullable)fetchSelfPeers:(NSError* _Nullable __autoreleasing* _Nullable)error;
+- (NSSet<id<CKKSPeer>>* _Nullable)fetchTrustedPeers:(NSError* _Nullable __autoreleasing* _Nullable)error;
 // Trusted peers should include self peers
 
 - (void)registerForPeerChangeUpdates:(id<CKKSPeerUpdateListener>)listener;
 - (void)trustedPeerSetChanged;
 @end
 
-
 // These should be replaced by Octagon peers, when those exist
 @interface CKKSSOSPeer : NSObject <CKKSPeer>
 @property (readonly) NSString* peerID;
-@property (readonly) SFECPublicKey* publicEncryptionKey;
-@property (readonly) SFECPublicKey* publicSigningKey;
+@property (nullable, readonly) SFECPublicKey* publicEncryptionKey;
+@property (nullable, readonly) SFECPublicKey* publicSigningKey;
 
 - (instancetype)initWithSOSPeerID:(NSString*)syncingPeerID
-              encryptionPublicKey:(SFECPublicKey*)encryptionKey
-                 signingPublicKey:(SFECPublicKey*)signingKey;
+              encryptionPublicKey:(SFECPublicKey* _Nullable)encryptionKey
+                 signingPublicKey:(SFECPublicKey* _Nullable)signingKey;
 @end
 
 @interface CKKSSOSSelfPeer : NSObject <CKKSPeer, CKKSSelfPeer>
@@ -94,4 +94,5 @@
                        signingKey:(SFECKeyPair*)signingKey;
 @end
 
-#endif // OCTAGON
+NS_ASSUME_NONNULL_END
+#endif  // OCTAGON
index d05c869578d9629cda0e1f8f5b246b9aca6e5f07..98f9ba060b54ce2a4ddee52af0efcb1fbf6a0804 100644 (file)
             } else {
                 // Otherwise, something has gone horribly wrong. enter error state.
                 ckkserror("ckkskey", ckks, "CKKS claims %@ is not a valid TLK: %@", tlk, error);
-                [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:[NSError errorWithDomain: @"securityd" code:0 userInfo:@{NSLocalizedDescriptionKey: @"invalid TLK from CloudKit", NSUnderlyingErrorKey: error}]];
+                NSError* newError = nil;
+                if(error) {
+                    newError = [NSError errorWithDomain: @"securityd" code:0 userInfo:@{NSLocalizedDescriptionKey: @"invalid TLK from CloudKit", NSUnderlyingErrorKey: error}];
+                } else {
+                    newError = [NSError errorWithDomain: @"securityd" code:0 userInfo:@{NSLocalizedDescriptionKey: @"invalid TLK from CloudKit (unknown error)"}];
+                }
+                [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:newError];
                 return true;
             }
         }
index 8dc8f2f7868482b4735981054951add4a9494e80..5d29077372074d6ebb019c63d87daa35a3a1f15c 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#ifndef RateLimiter_h
-#define RateLimiter_h
-
 #if OCTAGON
 
 #import <Foundation/Foundation.h>
 #import "CKKSOutgoingQueueEntry.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 @interface CKKSRateLimiter : NSObject <NSSecureCoding>
 
-@property (readonly, nonnull) NSDictionary * config;  // of NSString : NSNumber
+@property (readonly) NSDictionary* config;  // of NSString : NSNumber
 
 /*!
  * @brief Find out whether outgoing items are okay to send.
  * At badness 5 judge:at: has determined there is too much activity so the caller should hold off altogether. The limitTime object will indicate when
  * this overloaded state will end.
  */
-- (int)judge:(CKKSOutgoingQueueEntry * _Nonnull const)entry
-          at:(NSDate * _Nonnull)time
-   limitTime:(NSDate * _Nonnull __autoreleasing * _Nonnull) limitTime;
+- (int)judge:(CKKSOutgoingQueueEntry* const)entry
+           at:(NSDate*)time
+    limitTime:(NSDate* _Nonnull __autoreleasing* _Nonnull)limitTime;
 
 - (instancetype _Nullable)init;
-- (instancetype _Nullable)initWithCoder:(NSCoder * _Nullable)coder NS_DESIGNATED_INITIALIZER;
+- (instancetype _Nullable)initWithCoder:(NSCoder* _Nullable)coder NS_DESIGNATED_INITIALIZER;
 - (NSUInteger)stateSize;
 - (void)reset;
-- (NSString * _Nonnull)diagnostics;
+- (NSString*)diagnostics;
 
 + (BOOL)supportsSecureCoding;
 
 @end
 
-#endif
+NS_ASSUME_NONNULL_END
 #endif
index 5f2aae702366875ea6a7ee5d0a62dea1959ee798..fc16cef79b9bc3d2a9a6d9e620df5c6d0413cae5 100644 (file)
@@ -41,7 +41,7 @@ typedef NS_ENUM(int, BucketType) {
 };
 
 @interface CKKSRateLimiter()
-@property (readwrite, nonnull) NSDictionary<NSString *, NSNumber *> *config;
+@property (readwrite) NSDictionary<NSString *, NSNumber *> *config;
 @property NSMutableDictionary<NSString *, NSDate *> *buckets;
 @property NSDate *overloadUntil;
 #if !TARGET_OS_BRIDGE
index df6253424ceb394570e60a4ebeff1b49783b5278..81e24714fd25fda634789637db78811246960984 100644 (file)
 
 #if OCTAGON
 
-#include <utilities/SecDb.h>
+#import <CloudKit/CloudKit.h>
 #include <securityd/SecDbItem.h>
-
-#ifndef CKKSRecordHolder_h
-#define CKKSRecordHolder_h
-
+#include <utilities/SecDb.h>
 #import "keychain/ckks/CKKSSQLDatabaseObject.h"
-#import <CloudKit/CloudKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
 
 @class CKKSWrappedAESSIVKey;
 
 // Helper class that includes a single encoded CKRecord
-@interface CKKSCKRecordHolder : CKKSSQLDatabaseObject {
-}
+@interface CKKSCKRecordHolder : CKKSSQLDatabaseObject
 
-- (instancetype)initWithCKRecord: (CKRecord*) record;
-- (instancetype)initWithCKRecordType: (NSString*) recordType encodedCKRecord: (NSData*) encodedCKRecord zoneID:(CKRecordZoneID*)zoneID;
+- (instancetype)initWithCKRecord:(CKRecord*)record;
+- (instancetype)initWithCKRecordType:(NSString*)recordType
+                     encodedCKRecord:(NSData* _Nullable)encodedCKRecord
+                              zoneID:(CKRecordZoneID*)zoneID;
 
 @property (copy) CKRecordZoneID* zoneID;
 @property (copy) NSString* ckRecordType;
-@property (copy) NSData* encodedCKRecord;
-@property (getter=storedCKRecord,setter=setStoredCKRecord:) CKRecord* storedCKRecord;
+@property (nullable, copy) NSData* encodedCKRecord;
+@property (nullable, getter=storedCKRecord, setter=setStoredCKRecord:) CKRecord* storedCKRecord;
 
-- (CKRecord*) CKRecordWithZoneID: (CKRecordZoneID*) zoneID;
+- (CKRecord*)CKRecordWithZoneID:(CKRecordZoneID*)zoneID;
 
 // All of the following are virtual: you must override to use
-- (NSString*) CKRecordName;
-- (CKRecord*) updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID;
-- (void) setFromCKRecord: (CKRecord*) record; // When you override this, make sure to call [setStoredCKRecord]
-- (bool) matchesCKRecord: (CKRecord*) record;
+- (NSString*)CKRecordName;
+- (CKRecord*)updateCKRecord:(CKRecord*)record zoneID:(CKRecordZoneID*)zoneID;
+- (void)setFromCKRecord:(CKRecord*)record;  // When you override this, make sure to call [setStoredCKRecord]
+- (bool)matchesCKRecord:(CKRecord*)record;
 
 @end
 
+NS_ASSUME_NONNULL_END
 #endif
-#endif /* CKKSRecordHolder_h */
index 4a163ebddc45e4276912f8b5870fbd94f3b554fe..6cd17a242d66c3838779959326145b906297a014 100644 (file)
@@ -22,8 +22,8 @@
  */
 
 #if OCTAGON
-#import <Foundation/Foundation.h>
 #import <CloudKit/CloudKit.h>
+#import <Foundation/Foundation.h>
 #import "keychain/ckks/CKKSGroupOperation.h"
 
 @class CKKSKeychainView;
@@ -37,5 +37,4 @@
 
 @end
 
-#endif // OCTAGON
-
+#endif  // OCTAGON
index 44956f0fce5fd16847acb524705f1d696257f39f..3b975a66719a98dc31d8f1391206c8c4e267ce6b 100644 (file)
@@ -28,6 +28,7 @@
 #import "keychain/ckks/CKKSOutgoingQueueEntry.h"
 #import "keychain/ckks/CKKSReencryptOutgoingItemsOperation.h"
 #import "keychain/ckks/CKKSItemEncrypter.h"
+#import "keychain/ckks/CloudKitCategories.h"
 
 #if OCTAGON
 
@@ -91,7 +92,7 @@
                 continue;
             }
             if(newOQE) {
-                ckksinfo("ckksreencrypt", ckks, "Have a new OQE superceding %@ (%@), skipping", oqe, newOQE);
+                ckksnotice("ckksreencrypt", ckks, "Have a new OQE superceding %@ (%@), skipping", oqe, newOQE);
                 // Don't use the state transition here, either, since this item isn't really changing states
                 [oqe deleteFromDatabase:&error];
                 if(error) {
                 continue;
             }
 
-            ckksinfo("ckksreencrypt", ckks, "Reencrypting item %@", oqe);
+            ckksnotice("ckksreencrypt", ckks, "Reencrypting item %@", oqe);
 
             NSDictionary* item = [CKKSItemEncrypter decryptItemToDictionary: oqe.item error:&error];
             if(error) {
index 20fc548de8805ff175da16034661e3be8fc891b7..b387ba5b591bc3f50f8d2f4dee2a4b8ab7f06bfd 100644 (file)
@@ -21,8 +21,7 @@
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#ifndef CKKSResultOperation_h
-#define CKKSResultOperation_h
+#if OCTAGON
 
 #import <Foundation/Foundation.h>
 #import <dispatch/dispatch.h>
@@ -44,7 +43,7 @@ enum {
 
 // Very similar to addDependency, but:
 //   if the dependent operation has an error or is canceled, cancel this operation
-- (void)addSuccessDependency: (CKKSResultOperation*) operation;
+- (void)addSuccessDependency:(CKKSResultOperation*)operation;
 - (void)addNullableSuccessDependency:(CKKSResultOperation*)operation;
 
 // Call to check if you should run.
@@ -57,16 +56,17 @@ enum {
 - (instancetype)timeout:(dispatch_time_t)timeout;
 
 // Convenience constructor.
-+(instancetype)operationWithBlock:(void (^)(void))block;
-+(instancetype)named: (NSString*)name withBlock: (void(^)(void)) block;
++ (instancetype)operationWithBlock:(void (^)(void))block;
++ (instancetype)named:(NSString*)name withBlock:(void (^)(void))block;
++ (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSResultOperation* op))block;
 
 // Determine if all these operations were successful, and set this operation's result if not.
-- (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations;
+- (bool)allSuccessful:(NSArray<CKKSResultOperation*>*)operations;
 
 // Call this to prevent the timeout on this operation from occuring.
 // Upon return, either this operation is cancelled, or the timeout will never fire.
--(void)invalidateTimeout;
+- (void)invalidateTimeout;
 @end
 
-#endif // CKKSResultOperation_h
+#endif // OCTAGON
 
index 0adc51149b265b6fbf9741e36039aff805e30d39..f92f957ff4b009febabdcdf72b10fa52229cbeee 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
+#if OCTAGON
+
 #import "keychain/ckks/CKKSResultOperation.h"
+#import "keychain/ckks/NSOperationCategories.h"
 #import "keychain/ckks/CKKSCondition.h"
+#import "keychain/ckks/CloudKitCategories.h"
 #include <utilities/debugging.h>
 
 @interface CKKSResultOperation()
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{
         __strong __typeof(self) strongSelf = weakSelf;
         if(strongSelf.timeoutCanOccur) {
-            strongSelf.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultTimedOut userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation timed out waiting to start for [%@]", [self pendingDependenciesString:@""]]}];
+            strongSelf.error = [NSError errorWithDomain:CKKSResultErrorDomain
+                                                   code:CKKSResultTimedOut
+                                            description:[NSString stringWithFormat:@"Operation(%@) timed out waiting to start for [%@]", [self selfname], [self pendingDependenciesString:@""]]];
             strongSelf.timeoutCanOccur = false;
             [strongSelf cancel];
         }
         bool finished = true;   // all dependents must be finished
         bool cancelled = false; // no dependents can be cancelled
         bool failed = false;    // no dependents can have failed
+        NSMutableArray<NSOperation*>* cancelledSuboperations = [NSMutableArray array];
 
         for(CKKSResultOperation* op in operations) {
             finished  &= !!([op isFinished]);
             cancelled |= !!([op isCancelled]);
             failed    |= (op.error != nil);
 
+            if([op isCancelled]) {
+                [cancelledSuboperations addObject:op];
+            }
+
             // TODO: combine suberrors
             if(op.error != nil) {
                 if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) {
         result = finished && !( cancelled || failed );
 
         if(!result && self.error == nil) {
-            self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:nil];
+            self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation (%@) cancelled", cancelledSuboperations]}];
         }
         return result;
     }
     blockOp.name = name;
     return blockOp;
 }
+
++ (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSResultOperation* op))block
+{
+    CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
+    __weak __typeof(op) weakOp = op;
+    [op addExecutionBlock:^{
+        __strong __typeof(op) strongOp = weakOp;
+        block(strongOp);
+    }];
+    op.name = name;
+    return op;
+}
 @end
+
+#endif // OCTAGON
index 5b1b268708afd58f1299423bb8dd44a0bab76163..db1cc3a907389ea79c29ca12374af44d8c1a3913 100644 (file)
 
 // For AES-SIV 512.
 
-#define CKKSKeySize (512/8)
-#define CKKSWrappedKeySize (CKKSKeySize+16)
+#define CKKSKeySize (512 / 8)
+#define CKKSWrappedKeySize (CKKSKeySize + 16)
 
-@interface CKKSBaseAESSIVKey : NSObject <NSCopying> {
-    @package
-    uint8_t key[CKKSWrappedKeySize]; // subclasses can use less than the whole buffer, and set key to be precise
+@interface CKKSBaseAESSIVKey : NSObject <NSCopying>
+{
+   @package
+    uint8_t key[CKKSWrappedKeySize];  // subclasses can use less than the whole buffer, and set key to be precise
     size_t size;
 }
 - (instancetype)init;
-- (instancetype)initWithBytes:(uint8_t *)bytes len:(size_t)len;
+- (instancetype)initWithBytes:(uint8_t*)bytes len:(size_t)len;
 - (void)zeroKey;
-- (instancetype)copyWithZone:(NSZone *)zone;
+- (instancetype)copyWithZone:(NSZone*)zone;
 
 // Mostly for testing.
-- (instancetype)initWithBase64: (NSString*) base64bytes;
-- (BOOL)isEqual: (id) object;
+- (instancetype)initWithBase64:(NSString*)base64bytes;
+- (BOOL)isEqual:(id)object;
 @end
 
 @interface CKKSWrappedAESSIVKey : CKKSBaseAESSIVKey
-- (instancetype)initWithData: (NSData*) data;
+- (instancetype)initWithData:(NSData*)data;
 - (NSData*)wrappedData;
-- (NSString*) base64WrappedKey;
+- (NSString*)base64WrappedKey;
 @end
 
 @interface CKKSAESSIVKey : CKKSBaseAESSIVKey
 + (instancetype)randomKey;
 
-- (CKKSWrappedAESSIVKey*)wrapAESKey: (CKKSAESSIVKey*) keyToWrap error: (NSError * __autoreleasing *) error;
-- (CKKSAESSIVKey*)unwrapAESKey: (CKKSWrappedAESSIVKey*) keyToUnwrap error: (NSError * __autoreleasing *) error;
+- (CKKSWrappedAESSIVKey*)wrapAESKey:(CKKSAESSIVKey*)keyToWrap error:(NSError* __autoreleasing*)error;
+- (CKKSAESSIVKey*)unwrapAESKey:(CKKSWrappedAESSIVKey*)keyToUnwrap error:(NSError* __autoreleasing*)error;
 
 // Encrypt and decrypt data into buffers. Adds a nonce for ciphertext protection.
-- (NSData*)encryptData: (NSData*) plaintext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error;
-- (NSData*)decryptData: (NSData*) ciphertext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error;
+- (NSData*)encryptData:(NSData*)plaintext
+     authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
+                 error:(NSError* __autoreleasing*)error;
+- (NSData*)decryptData:(NSData*)ciphertext
+     authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
+                 error:(NSError* __autoreleasing*)error;
 
 @end
 
-#endif // OCTAGON
+#endif  // OCTAGON
index d016e35decbdfa8a69f7abaf84f0e8d21c107927..efa8c45c966772cfdf0b002ea0063398bfe3b216 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#ifndef DatabaseObject_h
-#define DatabaseObject_h
-
-#include <utilities/SecDb.h>
 #include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
 
-#define CKKSNilToNSNull(obj) ({ id o = (obj); o ? o : [NSNull null]; })
-#define CKKSNSNullToNil(obj) ({ id o = (obj); ([o isEqual: [NSNull null]]) ? nil : o; })
-
-#define CKKSIsNull(x) ({ id y = (x); ((y == nil) || ([y isEqual: [NSNull null]])); })
+#define CKKSNilToNSNull(obj)   \
+    ({                         \
+        id o = (obj);          \
+        o ? o : [NSNull null]; \
+    })
+#define CKKSNSNullToNil(obj)                   \
+    ({                                         \
+        id o = (obj);                          \
+        ([o isEqual:[NSNull null]]) ? nil : o; \
+    })
+
+#define CKKSIsNull(x)                                \
+    ({                                               \
+        id y = (x);                                  \
+        ((y == nil) || ([y isEqual:[NSNull null]])); \
+    })
 #define CKKSUnbase64NullableString(x) (!CKKSIsNull(x) ? [[NSData alloc] initWithBase64EncodedString:x options:0] : nil)
 
-@interface CKKSSQLDatabaseObject : NSObject <NSCopying> {
+NS_ASSUME_NONNULL_BEGIN
 
-}
+@interface CKKSSQLDatabaseObject : NSObject <NSCopying>
 
-@property (copy) NSDictionary<NSString*,NSString*>* originalSelfWhereClause;
+@property (copy) NSDictionary<NSString*, NSString*>* originalSelfWhereClause;
 
-- (bool) saveToDatabase: (NSError * __autoreleasing *) error;
-- (bool) saveToDatabaseWithConnection: (SecDbConnectionRef) conn error: (NSError * __autoreleasing *) error;
-- (bool) deleteFromDatabase: (NSError * __autoreleasing *) error;
-+ (bool) deleteAll: (NSError * __autoreleasing *) error;
+- (bool)saveToDatabase:(NSError* _Nullable __autoreleasing* _Nullable)error;
+- (bool)saveToDatabaseWithConnection:(SecDbConnectionRef _Nullable)conn
+                               error:(NSError* _Nullable __autoreleasing* _Nullable)error;
+- (bool)deleteFromDatabase:(NSError* _Nullable __autoreleasing* _Nullable)error;
++ (bool)deleteAll:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
 // Load the object from the database, and error if it doesn't exist
-+ (instancetype) fromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error;
++ (instancetype _Nullable)fromDatabaseWhere:(NSDictionary*)whereDict error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
 // Load the object from the database, and return nil if it doesn't exist
-+ (instancetype) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error;
++ (instancetype _Nullable)tryFromDatabaseWhere:(NSDictionary*)whereDict
+                                         error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
-+ (NSArray*) all: (NSError * __autoreleasing *) error;
-+ (NSArray*) allWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error;
++ (NSArray*)all:(NSError* _Nullable __autoreleasing* _Nullable)error;
++ (NSArray*)allWhere:(NSDictionary* _Nullable)whereDict error:(NSError* _Nullable __autoreleasing* _Nullable)error;
 
 // Like all() above, but with limits on how many will return
-+ (NSArray*)fetch:(size_t)count error: (NSError * __autoreleasing *) error;
-+ (NSArray*)fetch:(size_t)count where:(NSDictionary*)whereDict error: (NSError * __autoreleasing *) error;
-+ (NSArray*)fetch: (size_t)count where:(NSDictionary*)whereDict orderBy:(NSArray*) orderColumns error: (NSError * __autoreleasing *) error;
-
-
-+ (bool) saveToDatabaseTable: (NSString*) table row: (NSDictionary*) row connection: (SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error;
-+ (bool) deleteFromTable: (NSString*) table where: (NSDictionary*) whereDict connection:(SecDbConnectionRef) dbconn error: (NSError * __autoreleasing *) error;
-
-+ (bool) queryDatabaseTable:(NSString*) table
-                      where:(NSDictionary*) whereDict
-                    columns:(NSArray*) names
-                    groupBy:(NSArray*) groupColumns
-                    orderBy:(NSArray*) orderColumns
-                      limit:(ssize_t)limit
-                 processRow:(void (^)(NSDictionary*)) processRow
-                      error:(NSError * __autoreleasing *) error;
-
-+ (bool)queryMaxValueForField:(NSString*)maxField inTable:(NSString*)table where:(NSDictionary*)whereDict columns:(NSArray*)names processRow:(void (^)(NSDictionary*))processRow;
++ (NSArray*)fetch:(size_t)count error:(NSError* _Nullable __autoreleasing* _Nullable)error;
++ (NSArray*)fetch:(size_t)count
+            where:(NSDictionary* _Nullable)whereDict
+            error:(NSError* _Nullable __autoreleasing* _Nullable)error;
++ (NSArray*)fetch:(size_t)count
+            where:(NSDictionary* _Nullable)whereDict
+          orderBy:(NSArray* _Nullable)orderColumns
+            error:(NSError* _Nullable __autoreleasing* _Nullable)error;
+
+
++ (bool)saveToDatabaseTable:(NSString*)table
+                        row:(NSDictionary*)row
+                 connection:(SecDbConnectionRef _Nullable)dbconn
+                      error:(NSError* _Nullable __autoreleasing* _Nullable)error;
++ (bool)deleteFromTable:(NSString*)table
+                  where:(NSDictionary* _Nullable)whereDict
+             connection:(SecDbConnectionRef _Nullable)dbconn
+                  error:(NSError* _Nullable __autoreleasing* _Nullable)error;
+
++ (bool)queryDatabaseTable:(NSString*)table
+                     where:(NSDictionary* _Nullable)whereDict
+                   columns:(NSArray*)names
+                   groupBy:(NSArray* _Nullable)groupColumns
+                   orderBy:(NSArray* _Nullable)orderColumns
+                     limit:(ssize_t)limit
+                processRow:(void (^)(NSDictionary*))processRow
+                     error:(NSError* _Nullable __autoreleasing* _Nullable)error;
+
++ (bool)queryMaxValueForField:(NSString*)maxField
+                      inTable:(NSString*)table
+                        where:(NSDictionary* _Nullable)whereDict
+                      columns:(NSArray*)names
+                   processRow:(void (^)(NSDictionary*))processRow;
 
 // Note: if you don't use the SQLDatabase methods of loading yourself,
 //  make sure you call this directly after loading.
-- (instancetype) memoizeOriginalSelfWhereClause;
+- (instancetype)memoizeOriginalSelfWhereClause;
 
 #pragma mark - Subclasses must implement the following:
 
 // Given a row from the database, make this object
-+ (instancetype) fromDatabaseRow: (NSDictionary*) row;
++ (instancetype _Nullable)fromDatabaseRow:(NSDictionary*)row;
 
 // Return the columns, in order, that this row wants to fetch
-+ (NSArray<NSString*>*) sqlColumns;
++ (NSArray<NSString*>*)sqlColumns;
 
 // Return the table name for objects of this class
-+ (NSString*) sqlTable;
++ (NSString*)sqlTable;
 
 // Return the columns and values, in order, that this row wants to save
-- (NSDictionary<NSString*,NSString*>*) sqlValues;
+- (NSDictionary<NSString*, NSString*>*)sqlValues;
 
 // Return a set of key-value pairs that will uniquely find This Row in the table
-- (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf;
+- (NSDictionary<NSString*, NSString*>*)whereClauseToFindSelf;
 
-- (instancetype)copyWithZone:(NSZone *)zone;
+//- (instancetype)copyWithZone:(NSZone* _Nullable)zone;
 @end
 
 // Helper class to use with where clauses
 @interface CKKSSQLWhereObject : NSObject
 @property NSString* sqlOp;
 @property NSString* contents;
-- (instancetype) initWithOperation:(NSString*)op string: (NSString*) str;
-+ (instancetype) op:(NSString*)op string:(NSString*) str;
-+ (instancetype)op:(NSString*) op stringValue: (NSString*) str; // Will add single quotes around your value.
+- (instancetype)initWithOperation:(NSString*)op string:(NSString*)str;
++ (instancetype)op:(NSString*)op string:(NSString*)str;
++ (instancetype)op:(NSString*)op stringValue:(NSString*)str;  // Will add single quotes around your value.
 @end
 
-#endif /* DatabaseObject_h */
+NS_ASSUME_NONNULL_END
index ed859d2de0b2941a5e605268fe5e6c653b294c1c..8e6ab0861b0133b81bf0a418c4828c2c3ffda31e 100644 (file)
@@ -25,6 +25,7 @@
 #import "CKKSSQLDatabaseObject.h"
 #include <securityd/SecItemServer.h>
 
+#import "keychain/ckks/CKKS.h"
 #import "CKKSKeychainView.h"
 
 @implementation CKKSSQLDatabaseObject
     return ret;
 }
 
-+ (instancetype) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
++ (instancetype _Nullable) tryFromDatabaseWhere: (NSDictionary*) whereDict error: (NSError * __autoreleasing *) error {
     __block id ret = nil;
 
     [CKKSSQLDatabaseObject queryDatabaseTable: [self sqlTable]
index cd02e822444364b85154504d3293e94c46e401ec..b1b7754d02f92ac2b81d305330cb45a110414659 100644 (file)
@@ -41,4 +41,4 @@
 
 @end
 
-#endif // OCTAGON
+#endif  // OCTAGON
index 065d06a0143b6800d6fdd9d054c071b613b6472c..cc42a1d5f4a69dab5c679018f51501680cb5543c 100644 (file)
@@ -33,6 +33,8 @@
 #import "keychain/ckks/CKKSViewManager.h"
 #import "keychain/ckks/CKKSManifest.h"
 
+#import "CKKSPowerCollection.h"
+
 #include <securityd/SecItemSchema.h>
 #include <securityd/SecItemServer.h>
 #include <securityd/SecItemDb.h>
@@ -40,6 +42,7 @@
 
 @interface CKKSScanLocalItemsOperation ()
 @property CKOperationGroup* ckoperationGroup;
+@property (assign) NSUInteger processsedItems;
 @end
 
 @implementation CKKSScanLocalItemsOperation
@@ -78,7 +81,7 @@
         __block CFErrorRef cferror = NULL;
         __block NSError* error = nil;
         __block bool newEntries = false;
-        
+
         // Must query per-class, so:
         const SecDbSchema *newSchema = current_schema();
         for (const SecDbClass *const *class = newSchema->classes; *class != NULL; class++) {
                 return SecDbItemQuery(q, NULL, dbt, &cferror, ^(SecDbItemRef item, bool *stop) {
                     ckksnotice("ckksscan", ckks, "scanning item: %@", item);
 
+                    self.processsedItems += 1;
+
                     SecDbItemRef itemToSave = NULL;
 
                     // First check: is this a tombstone? If so, skip with prejudice.
                         // We don't care about the oqe state here, just that one exists
                         CKKSOutgoingQueueEntry* oqe = [CKKSOutgoingQueueEntry tryFromDatabase: uuid zoneID:ckks.zoneID error: &error];
                         if(oqe != nil) {
-                            ckksinfo("ckksscan", ckks, "Existing outgoing queue entry with UUID %@", uuid);
+                            ckksnotice("ckksscan", ckks, "Existing outgoing queue entry with UUID %@", uuid);
                             // If its state is 'new', mark down that we've seen new entries that need processing
                             newEntries |= !![oqe.state isEqualToString: SecCKKSStateNew];
                             return;
                 continue;
             }
         }
-        
+
+        //[CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventScanLocalItems  zone:ckks.zoneName count:self.processsedItems];
+
         if ([CKKSManifest shouldSyncManifests]) {
             // TODO: this manifest needs to incorporate peer manifests
             CKKSEgoManifest* manifest = [CKKSEgoManifest newManifestForZone:ckks.zoneName withItems:itemsForManifest peerManifestIDs:@[] currentItems:@{} error:&error];
             [ckks processOutgoingQueue:self.ckoperationGroup];
         }
 
+        ckksnotice("ckksscan", ckks, "Completed scan");
+        ckks.droppedItems = false;
         return true;
     }];
 }
index 70534d0ef21188e149da94e055c7e7eddd8d81d7..cb6cbd6b054c37562d06a95b8fae2940730124c5 100644 (file)
@@ -36,5 +36,4 @@
 
 @end
 
-#endif // OCTAGON
-
+#endif  // OCTAGON
index 2ef3b854f98668850c465e42793b43799ff308b9..239e492a0577795947f2dc75779cf3f394b936f3 100644 (file)
 #import "keychain/ckks/CKKSKey.h"
 #import "keychain/ckks/CKKSPeer.h"
 
-#import <SecurityFoundation/SFKey.h>
 #import <SecurityFoundation/SFEncryptionOperation.h>
+#import <SecurityFoundation/SFKey.h>
+
+NS_ASSUME_NONNULL_BEGIN
 
 typedef NS_ENUM(NSUInteger, SecCKKSTLKShareVersion) {
     SecCKKSTLKShareVersion0 = 0,  // Signature is over all fields except (signature) and (receiverPublicKey)
-                                  // Unknown fields in the CKRecord will be appended to the end, in sorted order based on column ID
+    // Unknown fields in the CKRecord will be appended to the end, in sorted order based on column ID
 };
 
 #define SecCKKSTLKShareCurrentVersion SecCKKSTLKShareVersion0
@@ -53,53 +55,49 @@ typedef NS_ENUM(NSUInteger, SecCKKSTLKShareVersion) {
 @property NSInteger epoch;
 @property NSInteger poisoned;
 
-@property NSData* wrappedTLK;
-@property NSData* signature;
+@property (nullable) NSData* wrappedTLK;
+@property (nullable) NSData* signature;
 
--(instancetype)init NS_UNAVAILABLE;
+- (instancetype)init NS_UNAVAILABLE;
 
-- (CKKSKey*)recoverTLK:(id<CKKSSelfPeer>)recoverer
-          trustedPeers:(NSSet<id<CKKSPeer>>*)peers
-                 error:(NSError* __autoreleasing *)error;
+- (CKKSKey* _Nullable)recoverTLK:(id<CKKSSelfPeer>)recoverer trustedPeers:(NSSet<id<CKKSPeer>>*)peers error:(NSError**)error;
 
-+ (CKKSTLKShare*)share:(CKKSKey*)key
-                    as:(id<CKKSSelfPeer>)sender
-                    to:(id<CKKSPeer>)receiver
-                 epoch:(NSInteger)epoch
-              poisoned:(NSInteger)poisoned
-                 error:(NSError* __autoreleasing *)error;
++ (CKKSTLKShare* _Nullable)share:(CKKSKey*)key
+                              as:(id<CKKSSelfPeer>)sender
+                              to:(id<CKKSPeer>)receiver
+                           epoch:(NSInteger)epoch
+                        poisoned:(NSInteger)poisoned
+                           error:(NSError**)error;
 
 // Database loading
-+ (instancetype)fromDatabase:(NSString*)uuid
-              receiverPeerID:(NSString*)receiverPeerID
-                senderPeerID:(NSString*)senderPeerID
-                      zoneID:(CKRecordZoneID*)zoneID
-                       error:(NSError * __autoreleasing *)error;
-+ (instancetype)tryFromDatabase:(NSString*)uuid
-                 receiverPeerID:(NSString*)receiverPeerID
-                   senderPeerID:(NSString*)senderPeerID
-                         zoneID:(CKRecordZoneID*)zoneID
-                          error:(NSError * __autoreleasing *)error;
++ (instancetype _Nullable)fromDatabase:(NSString*)uuid
+                        receiverPeerID:(NSString*)receiverPeerID
+                          senderPeerID:(NSString*)senderPeerID
+                                zoneID:(CKRecordZoneID*)zoneID
+                                 error:(NSError* __autoreleasing*)error;
++ (instancetype _Nullable)tryFromDatabase:(NSString*)uuid
+                           receiverPeerID:(NSString*)receiverPeerID
+                             senderPeerID:(NSString*)senderPeerID
+                                   zoneID:(CKRecordZoneID*)zoneID
+                                    error:(NSError**)error;
 + (NSArray<CKKSTLKShare*>*)allFor:(NSString*)receiverPeerID
                           keyUUID:(NSString*)uuid
                            zoneID:(CKRecordZoneID*)zoneID
-                            error:(NSError * __autoreleasing *)error;
-+ (NSArray<CKKSTLKShare*>*)allForUUID:(NSString*)uuid
-                               zoneID:(CKRecordZoneID*)zoneID
-                                error:(NSError * __autoreleasing *)error;
-+ (NSArray<CKKSTLKShare*>*)allInZone:(CKRecordZoneID*)zoneID
-                               error:(NSError * __autoreleasing *)error;
-+ (instancetype)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID
-                                        error:(NSError * __autoreleasing *)error;
+                            error:(NSError* __autoreleasing*)error;
++ (NSArray<CKKSTLKShare*>*)allForUUID:(NSString*)uuid zoneID:(CKRecordZoneID*)zoneID error:(NSError**)error;
++ (NSArray<CKKSTLKShare*>*)allInZone:(CKRecordZoneID*)zoneID error:(NSError**)error;
++ (instancetype _Nullable)tryFromDatabaseFromCKRecordID:(CKRecordID*)recordID error:(NSError**)error;
 
 // Returns a prefix that all every CKKSTLKShare CKRecord will have
 + (NSString*)ckrecordPrefix;
 
 // For tests
-- (CKKSKey*)unwrapUsing:(id<CKKSSelfPeer>)localPeer error:(NSError * __autoreleasing *)error;
-- (NSData*)signRecord:(SFECKeyPair*)signingKey error:(NSError* __autoreleasing *)error;
-- (bool)verifySignature:(NSData*)signature verifyingPeer:(id<CKKSPeer>)peer error:(NSError* __autoreleasing *)error;
+- (CKKSKey* _Nullable)unwrapUsing:(id<CKKSSelfPeer>)localPeer error:(NSError**)error;
+- (NSData* _Nullable)signRecord:(SFECKeyPair*)signingKey error:(NSError**)error;
+- (bool)verifySignature:(NSData*)signature verifyingPeer:(id<CKKSPeer>)peer error:(NSError**)error;
 - (NSData*)dataForSigning;
 @end
 
-#endif // OCTAGON
+NS_ASSUME_NONNULL_END
+
+#endif  // OCTAGON
index 6ce5057c25148056d4174acee209ddb2a4d56589..3cdbdd921e393804c6c04263563b2b2b8a5ec931 100644 (file)
@@ -30,7 +30,7 @@
 @property (weak) CKKSKeychainView* ckks;
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*) ckks
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks
                           currentPointer:(NSString*)identifier
                              oldItemUUID:(NSString*)oldItemUUID
                              oldItemHash:(NSData*)oldItemHash
@@ -38,5 +38,4 @@
                         ckoperationGroup:(CKOperationGroup*)ckoperationGroup;
 @end
 
-#endif // OCTAGON
-
+#endif  // OCTAGON
index 1d14471769d00a7324febccf50061b69f4c0384e..39a2d8864a36ff285d4b37d11bc25b8cd1a40d94 100644 (file)
         }
 
         // Check if either item is currently in any sync queue, and fail if so
-        NSArray* oqes = [CKKSOutgoingQueueEntry allUUIDs:&error];
-        NSArray* iqes = [CKKSIncomingQueueEntry allUUIDs:&error];
+        NSArray* oqes = [CKKSOutgoingQueueEntry allUUIDs:ckks.zoneID error:&error];
+        NSArray* iqes = [CKKSIncomingQueueEntry allUUIDs:ckks.zoneID error:&error];
         if([oqes containsObject:self.currentItemUUID] || [iqes containsObject:self.currentItemUUID]) {
             error = [NSError errorWithDomain:CKKSErrorDomain
                                         code:CKKSLocalItemChangePending
                 ckkserror("ckkscurrent", strongCKKS, "CloudKit returned an error: %@", ckerror);
                 strongSelf.error = ckerror;
 
-                [ckks dispatchSync:^bool {
-                    return [ckks _onqueueCKWriteFailed:ckerror attemptedRecordsChanged:recordsToSave];
+                [strongCKKS dispatchSync:^bool {
+                    return [strongCKKS _onqueueCKWriteFailed:ckerror attemptedRecordsChanged:recordsToSave];
                 }];
 
                 [strongCKKS scheduleOperation: modifyComplete];
index 0d82e7a7254da6aea6e1e605bc021ddf491cb6d4..59507bb56f61c1e952e1b38a0651790c79d7b2d3 100644 (file)
 
 #if OCTAGON
 
-#import "keychain/ckks/CKKSGroupOperation.h"
-#import "keychain/ckks/CKKSDeviceStateEntry.h"
 #import <Foundation/Foundation.h>
+#import "keychain/ckks/CKKSDeviceStateEntry.h"
+#import "keychain/ckks/CKKSGroupOperation.h"
 
 @interface CKKSUpdateDeviceStateOperation : CKKSGroupOperation
 @property (weak) CKKSKeychainView* ckks;
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks rateLimit:(bool)rateLimit ckoperationGroup:(CKOperationGroup*)group;
+- (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks
+                               rateLimit:(bool)rateLimit
+                        ckoperationGroup:(CKOperationGroup*)group;
 @end
 
-#endif // OCTAGON
+#endif  // OCTAGON
index 8b071b0d09de328657c67fabbd170253c86dd4c7..06f24aa7a712586d766aa2f924406fdadcbe9847 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
+
 #import <Foundation/Foundation.h>
-#include <securityd/SecDbItem.h>
-#import "keychain/ckks/CKKS.h"
 
-#import "keychain/ckks/CKKSControlProtocol.h"
 #if OCTAGON
-#import "keychain/ckks/CloudKitDependencies.h"
+
+#include <securityd/SecDbItem.h>
+#import "keychain/ckks/CKKS.h"
 #import "keychain/ckks/CKKSAPSReceiver.h"
 #import "keychain/ckks/CKKSCKAccountStateTracker.h"
+#import "keychain/ckks/CKKSCondition.h"
+#import "keychain/ckks/CKKSControlProtocol.h"
 #import "keychain/ckks/CKKSLockStateTracker.h"
-#import "keychain/ckks/CKKSRateLimiter.h"
 #import "keychain/ckks/CKKSNotifier.h"
-#import "keychain/ckks/CKKSCondition.h"
 #import "keychain/ckks/CKKSPeer.h"
-#endif
+#import "keychain/ckks/CKKSRateLimiter.h"
+#import "keychain/ckks/CloudKitDependencies.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 @class CKKSKeychainView, CKKSRateLimiter;
 
-#if !OCTAGON
-@interface CKKSViewManager : NSObject
-#else
 @interface CKKSViewManager : NSObject <CKKSControlProtocol, CKKSPeerProvider>
 
 @property CKContainer* container;
 
 @property CKKSRateLimiter* globalRateLimiter;
 
-// Set this and all newly-created zones will wait to do setup until it completes.
-// this gives you a bit more control than initializedNewZones above.
-@property NSOperation* zoneStartupDependency;
-
-- (instancetype)initCloudKitWithContainerName: (NSString*) containerName usePCS:(bool)usePCS;
-- (instancetype)initWithContainerName: (NSString*) containerName
-                               usePCS: (bool)usePCS
- fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
-           fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
-                  queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
-    modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
-      modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
-                   apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
-            nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass
-                        notifierClass: (Class<CKKSNotifier>) notifierClass
-                            setupHold:(NSOperation*) setupHold;
+- (instancetype)initCloudKitWithContainerName:(NSString*)containerName usePCS:(bool)usePCS;
+- (instancetype)initWithContainerName:(NSString*)containerName
+                                  usePCS:(bool)usePCS
+    fetchRecordZoneChangesOperationClass:(Class<CKKSFetchRecordZoneChangesOperation>)fetchRecordZoneChangesOperationClass
+              fetchRecordsOperationClass:(Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
+                     queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
+       modifySubscriptionsOperationClass:(Class<CKKSModifySubscriptionsOperation>)modifySubscriptionsOperationClass
+         modifyRecordZonesOperationClass:(Class<CKKSModifyRecordZonesOperation>)modifyRecordZonesOperationClass
+                      apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass
+               nsnotificationCenterClass:(Class<CKKSNSNotificationCenter>)nsnotificationCenterClass
+                           notifierClass:(Class<CKKSNotifier>)notifierClass;
 
 - (CKKSKeychainView*)findView:(NSString*)viewName;
 - (CKKSKeychainView*)findOrCreateView:(NSString*)viewName;
 + (CKKSKeychainView*)findOrCreateView:(NSString*)viewName;
-- (void)setView: (CKKSKeychainView*) obj;
-- (void)clearView:(NSString*) viewName;
+- (void)setView:(CKKSKeychainView*)obj;
+- (void)clearView:(NSString*)viewName;
 
-- (NSDictionary<NSString *,NSString *>*)activeTLKs;
+- (NSDictionary<NSString*, NSString*>*)activeTLKs;
 
 // Call this to bring zones up (and to do so automatically in the future)
 - (void)initializeZones;
 
-- (NSString*)viewNameForItem: (SecDbItemRef) item;
+- (NSString*)viewNameForItem:(SecDbItemRef)item;
 
-- (void) handleKeychainEventDbConnection: (SecDbConnectionRef) dbconn source:(SecDbTransactionSource)txionSource added: (SecDbItemRef) added deleted: (SecDbItemRef) deleted;
+- (void)handleKeychainEventDbConnection:(SecDbConnectionRef)dbconn
+                                 source:(SecDbTransactionSource)txionSource
+                                  added:(SecDbItemRef _Nullable)added
+                                deleted:(SecDbItemRef _Nullable)deleted;
 
--(void)setCurrentItemForAccessGroup:(SecDbItemRef)newItem
-                               hash:(NSData*)newItemSHA1
-                        accessGroup:(NSString*)accessGroup
-                         identifier:(NSString*)identifier
-                           viewHint:(NSString*)viewHint
-                          replacing:(SecDbItemRef)oldItem
-                               hash:(NSData*)oldItemSHA1
-                           complete:(void (^) (NSError* operror)) complete;
+- (void)setCurrentItemForAccessGroup:(SecDbItemRef)newItem
+                                hash:(NSData*)newItemSHA1
+                         accessGroup:(NSString*)accessGroup
+                          identifier:(NSString*)identifier
+                            viewHint:(NSString*)viewHint
+                           replacing:(SecDbItemRef _Nullable)oldItem
+                                hash:(NSData* _Nullable)oldItemSHA1
+                            complete:(void (^)(NSError* operror))complete;
 
--(void)getCurrentItemForAccessGroup:(NSString*)accessGroup
-                         identifier:(NSString*)identifier
-                           viewHint:(NSString*)viewHint
-                    fetchCloudValue:(bool)fetchCloudValue
-                           complete:(void (^) (NSString* uuid, NSError* operror)) complete;
+- (void)getCurrentItemForAccessGroup:(NSString*)accessGroup
+                          identifier:(NSString*)identifier
+                            viewHint:(NSString*)viewHint
+                     fetchCloudValue:(bool)fetchCloudValue
+                            complete:(void (^)(NSString* uuid, NSError* operror))complete;
 
-- (NSString*)viewNameForAttributes: (NSDictionary*) item;
+- (NSString*)viewNameForAttributes:(NSDictionary*)item;
 
-- (void)registerSyncStatusCallback: (NSString*) uuid callback: (SecBoolNSErrorCallback) callback;
+- (void)registerSyncStatusCallback:(NSString*)uuid callback:(SecBoolNSErrorCallback)callback;
 
 // Cancels pending operations owned by this view manager
 - (void)cancelPendingOperations;
 
 // Use these to acquire (and set) the singleton
-+ (instancetype) manager;
-+ (instancetype) resetManager: (bool) reset setTo: (CKKSViewManager*) obj;
++ (instancetype)manager;
++ (instancetype _Nullable)resetManager:(bool)reset setTo:(CKKSViewManager* _Nullable)obj;
 
 // Called by XPC every 24 hours
--(void)xpc24HrNotification;
+- (void)xpc24HrNotification;
 
 /* Interface to CCKS control channel */
 - (xpc_endpoint_t)xpcControlEndpoint;
 - (CKKSKeychainView*)restartZone:(NSString*)viewName;
 
 // Returns the viewList for a CKKSViewManager
--(NSSet*)viewList;
+- (NSSet*)viewList;
 
 // Notify sbd to re-backup.
--(void)notifyNewTLKsInKeychain;
--(void)syncBackupAndNotifyAboutSync;
+- (void)notifyNewTLKsInKeychain;
+- (void)syncBackupAndNotifyAboutSync;
 
 // Fetch peers from SOS
-- (CKKSSelves*)fetchSelfPeers:(NSError* __autoreleasing *)error;
-- (NSSet<id<CKKSPeer>>*)fetchTrustedPeers:(NSError* __autoreleasing *)error;
+- (CKKSSelves* _Nullable)fetchSelfPeers:(NSError* __autoreleasing*)error;
+- (NSSet<id<CKKSPeer>>* _Nullable)fetchTrustedPeers:(NSError* __autoreleasing*)error;
 
 - (void)sendSelfPeerChangedUpdate;
 - (void)sendTrustedPeerSetChangedUpdate;
 
-#endif // OCTAGON
 @end
+NS_ASSUME_NONNULL_END
+
+#else
+@interface CKKSViewManager : NSObject
+@end
+#endif  // OCTAGON
index 436c0d5abb586d0e7692c8bf3a05f3104e2a91c2..1e197fd99767d9f536a153ccc8d72d13ae2d32a1 100644 (file)
@@ -30,7 +30,6 @@
 #import "keychain/ckks/CKKSNotifier.h"
 #import "keychain/ckks/CKKSCondition.h"
 #import "keychain/ckks/CloudKitCategories.h"
-#import "CKKSAnalyticsLogger.h"
 
 #import "SecEntitlements.h"
 
@@ -51,6 +50,8 @@
 
 #import <SecurityFoundation/SFKey.h>
 #import <SecurityFoundation/SFKey_Private.h>
+
+#import  "CKKSAnalyticsLogger.h"
 #endif
 
 @interface CKKSViewManager () <NSXPCListenerDelegate>
        modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class]
                     apsConnectionClass:[APSConnection class]
              nsnotificationCenterClass:[NSNotificationCenter class]
-                         notifierClass:[CKKSNotifyPostNotifier class]
-                             setupHold:nil];
+                         notifierClass:[CKKSNotifyPostNotifier class]];
 }
 
 - (instancetype)initWithContainerName: (NSString*) containerName
-                               usePCS:(bool)usePCS
+                               usePCS: (bool)usePCS
  fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
            fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
-                  queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
+                  queryOperationClass: (Class<CKKSQueryOperation>)queryOperationClass
     modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
       modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
                    apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass
             nsnotificationCenterClass: (Class<CKKSNSNotificationCenter>) nsnotificationCenterClass
                         notifierClass: (Class<CKKSNotifier>) notifierClass
-                            setupHold: (NSOperation*) setupHold {
+{
     if(self = [super init]) {
         _fetchRecordZoneChangesOperationClass = fetchRecordZoneChangesOperationClass;
         _fetchRecordsOperationClass = fetchRecordsOperationClass;
         _views = [[NSMutableDictionary alloc] init];
         _pendingSyncCallbacks = [[NSMutableDictionary alloc] init];
 
-        _zoneStartupDependency = setupHold;
         _initializeNewZones = false;
 
         _completedSecCKKSInitialize = [[CKKSCondition alloc] init];
@@ -278,10 +277,6 @@ dispatch_once_t globalZoneStateQueueOnce;
                                                         apsConnectionClass: self.apsConnectionClass
                                                              notifierClass: self.notifierClass];
 
-        if(self.zoneStartupDependency) {
-            [self.views[viewName].zoneSetupOperation addDependency: self.zoneStartupDependency];
-        }
-
         if(self.initializeNewZones) {
             [self.views[viewName] initializeZone];
         }
@@ -560,28 +555,40 @@ dispatch_once_t globalZoneStateQueueOnce;
     reply(@{});
 }
 
-- (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
+- (NSArray<CKKSKeychainView*>*)views:(NSString*)viewName operation:(NSString*)opName error:(NSError**)error
+{
     NSArray* actualViews = nil;
-    if(viewName) {
-        secnotice("ckksreset", "Received a local reset RPC for zone %@", viewName);
-        CKKSKeychainView* view = self.views[viewName];
-
-        if(!view) {
-            secerror("ckks: Zone %@ does not exist!", viewName);
-            reply([NSError errorWithDomain:@"securityd"
-                                      code:kSOSCCNoSuchView
-                                  userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
-            return;
-        }
+    @synchronized(self.views) {
+        if(viewName) {
+            secnotice("ckks", "Received a %@ request for zone %@", opName, viewName);
+            CKKSKeychainView* view = self.views[viewName];
+
+            if(!view) {
+                if(error) {
+                    *error = [NSError errorWithDomain:CKKSErrorDomain
+                                                 code:CKKSNoSuchView
+                                             userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}];
+                }
+                return nil;
+            }
 
-        actualViews = @[view];
-    } else {
-        secnotice("ckksreset", "Received a local reset RPC for all zones");
-        @synchronized(self.views) {
-            // Can't safely iterate a mutable collection, so copy it.
+            actualViews = @[view];
+        } else {
+            secnotice("ckks", "Received a %@ request for all zones", opName);
             actualViews = [self.views.allValues copy];
         }
     }
+    return actualViews;
+}
+
+- (void)rpcResetLocal:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
+    NSError* localError = nil;
+    NSArray* actualViews = [self views:viewName operation:@"local reset" error:&localError];
+    if(localError) {
+        secerror("ckks: Error getting view %@: %@", viewName, localError);
+        reply(localError);
+        return;
+    }
 
     CKKSResultOperation* op = [CKKSResultOperation named:@"local-reset-zones-waiter" withBlock:^{}];
 
@@ -603,26 +610,12 @@ dispatch_once_t globalZoneStateQueueOnce;
 }
 
 - (void)rpcResetCloudKit:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
-    NSArray* actualViews = nil;
-    if(viewName) {
-        secnotice("ckksreset", "Received a cloudkit reset RPC for zone %@", viewName);
-        CKKSKeychainView* view = self.views[viewName];
-
-        if(!view) {
-            secerror("ckks: Zone %@ does not exist!", viewName);
-            reply([NSError errorWithDomain:@"securityd"
-                                      code:kSOSCCNoSuchView
-                                  userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"No view for '%@'", viewName]}]);
-            return;
-        }
-
-        actualViews = @[view];
-    } else {
-        secnotice("ckksreset", "Received a cloudkit reset RPC for all zones");
-        @synchronized(self.views) {
-            // Can't safely iterate a mutable collection, so copy it.
-            actualViews = [self.views.allValues copy];
-        }
+    NSError* localError = nil;
+    NSArray* actualViews = [self views:viewName operation:@"CloudKit reset" error:&localError];
+    if(localError) {
+        secerror("ckks: Error getting view %@: %@", viewName, localError);
+        reply(localError);
+        return;
     }
 
     CKKSResultOperation* op = [CKKSResultOperation named:@"cloudkit-reset-zones-waiter" withBlock:^{}];
@@ -645,38 +638,24 @@ dispatch_once_t globalZoneStateQueueOnce;
 }
 
 - (void)rpcResync:(NSString*)viewName reply: (void(^)(NSError* result)) reply {
-    secnotice("ckksresync", "Received a resync RPC for zone %@. Beginning resync...", viewName);
-
-    NSArray* actualViews = nil;
-    if(viewName) {
-        secnotice("ckks", "Received a resync RPC for zone %@", viewName);
-        CKKSKeychainView* view = self.views[viewName];
-
-        if(!view) {
-            secerror("ckks: Zone %@ does not exist!", viewName);
-            reply(nil);
-            return;
-        }
-
-        actualViews = @[view];
-
-    } else {
-        @synchronized(self.views) {
-            // Can't safely iterate a mutable collection, so copy it.
-            actualViews = [self.views.allValues copy];
-        }
+    NSError* localError = nil;
+    NSArray* actualViews = [self views:viewName operation:@"CloudKit resync" error:&localError];
+    if(localError) {
+        secerror("ckks: Error getting view %@: %@", viewName, localError);
+        reply(localError);
+        return;
     }
 
     CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
-    op.name = @"rpc-resync";
+    op.name = @"rpc-resync-cloudkit";
     __weak __typeof(op) weakOp = op;
     [op addExecutionBlock:^{
         __strong __typeof(op) strongOp = weakOp;
-        secnotice("ckks", "Ending rsync rpc with %@", strongOp.error);
+        secnotice("ckks", "Ending rsync-CloudKit rpc with %@", strongOp.error);
     }];
 
     for(CKKSKeychainView* view in actualViews) {
-        ckksnotice("ckksresync", view, "Beginning resync for %@", view);
+        ckksnotice("ckksresync", view, "Beginning resync (CloudKit) for %@", view);
 
         CKKSSynchronizeOperation* resyncOp = [view resyncWithCloud];
         [op addSuccessDependency:resyncOp];
@@ -688,6 +667,34 @@ dispatch_once_t globalZoneStateQueueOnce;
     reply(CKXPCSuitableError(op.error));
 }
 
+- (void)rpcResyncLocal:(NSString*)viewName reply:(void(^)(NSError* result))reply {
+    NSError* localError = nil;
+    NSArray* actualViews = [self views:viewName operation:@"local resync" error:&localError];
+    if(localError) {
+        secerror("ckks: Error getting view %@: %@", viewName, localError);
+        reply(localError);
+        return;
+    }
+
+    CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
+    op.name = @"rpc-resync-local";
+    __weak __typeof(op) weakOp = op;
+    [op addExecutionBlock:^{
+        __strong __typeof(op) strongOp = weakOp;
+        secnotice("ckks", "Ending rsync-local rpc with %@", strongOp.error);
+        reply(CKXPCSuitableError(strongOp.error));
+    }];
+
+    for(CKKSKeychainView* view in actualViews) {
+        ckksnotice("ckksresync", view, "Beginning resync (local) for %@", view);
+
+        CKKSLocalSynchronizeOperation* resyncOp = [view resyncLocal];
+        [op addSuccessDependency:resyncOp];
+    }
+
+    [op timeout:120*NSEC_PER_SEC];
+}
+
 - (void)rpcStatus: (NSString*)viewName reply: (void(^)(NSArray<NSDictionary*>* result, NSError* error)) reply {
     NSMutableArray* a = [[NSMutableArray alloc] init];
 
@@ -958,7 +965,12 @@ dispatch_once_t globalZoneStateQueueOnce;
             SecKeyRef cfOctagonEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(sosPeerInfoRef, &cfPeerError);
 
             if(cfPeerError) {
-                secerror("ckkspeer: error fetching octagon keys for peer: %@ %@", sosPeerInfoRef, cfPeerError);
+                // Don't log non-debug for -50; it almost always just means this peer didn't have octagon keys
+                if(!(CFEqualSafe(CFErrorGetDomain(cfPeerError), kCFErrorDomainOSStatus) && (CFErrorGetCode(cfPeerError) == errSecParam))) {
+                    secerror("ckkspeer: error fetching octagon keys for peer: %@ %@", sosPeerInfoRef, cfPeerError);
+                } else {
+                    secinfo("ckkspeer", "Peer doesn't have Octagon keys, but this is expected: %@", cfPeerError);
+                }
             } else {
                 SFECPublicKey* signingPublicKey = cfOctagonSigningKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonSigningKey] : nil;
                 SFECPublicKey* encryptionPublicKey = cfOctagonEncryptionKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonEncryptionKey] : nil;
index a46deabb4bbd55cc21bf3d59f72cf889c29839d4..3e4b94ee25240fd2db5b2ce1770e8a3babe0b59a 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#ifndef CKKSZone_h
-#define CKKSZone_h
-
 #import <Foundation/Foundation.h>
 
 #if OCTAGON
-#import "keychain/ckks/CloudKitDependencies.h"
 #import "keychain/ckks/CKKSCKAccountStateTracker.h"
-#endif
+#import "keychain/ckks/CloudKitDependencies.h"
 
-#if OCTAGON
-@interface CKKSZone : NSObject<CKKSZoneUpdateReceiver, CKKSAccountStateListener> {
+NS_ASSUME_NONNULL_BEGIN
+
+@interface CKKSZone : NSObject <CKKSZoneUpdateReceiver, CKKSAccountStateListener>
+{
     CKContainer* _container;
     CKDatabase* _database;
     CKRecordZone* _zone;
 }
-#else
-@interface CKKSZone : NSObject {
-}
-#endif
 
 @property (readonly) NSString* zoneName;
 
-@property bool setupStarted;
-@property bool setupComplete;
 @property CKKSGroupOperation* zoneSetupOperation;
 
 @property bool zoneCreated;
 @property bool zoneSubscribed;
-@property NSError* zoneCreatedError;
-@property NSError* zoneSubscribedError;
+@property (nullable) NSError* zoneCreatedError;
+@property (nullable) NSError* zoneSubscribedError;
+
+// True if this zone object has been halted. Halted zones will never recover.
+@property (readonly) bool halted;
 
-#if OCTAGON
 @property CKKSAccountStatus accountStatus;
 
 @property (readonly) CKContainer* container;
 
 @property dispatch_queue_t queue;
 
-- (instancetype)initWithContainer:     (CKContainer*) container
-                             zoneName: (NSString*) zoneName
-                       accountTracker:(CKKSCKAccountStateTracker*) tracker
- fetchRecordZoneChangesOperationClass: (Class<CKKSFetchRecordZoneChangesOperation>) fetchRecordZoneChangesOperationClass
-           fetchRecordsOperationClass: (Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
-                  queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
-    modifySubscriptionsOperationClass: (Class<CKKSModifySubscriptionsOperation>) modifySubscriptionsOperationClass
-      modifyRecordZonesOperationClass: (Class<CKKSModifyRecordZonesOperation>) modifyRecordZonesOperationClass
-                   apsConnectionClass: (Class<CKKSAPSConnection>) apsConnectionClass;
-
+- (instancetype)initWithContainer:(CKContainer*)container
+                                zoneName:(NSString*)zoneName
+                          accountTracker:(CKKSCKAccountStateTracker*)tracker
+    fetchRecordZoneChangesOperationClass:(Class<CKKSFetchRecordZoneChangesOperation>)fetchRecordZoneChangesOperationClass
+              fetchRecordsOperationClass:(Class<CKKSFetchRecordsOperation>)fetchRecordsOperationClass
+                     queryOperationClass:(Class<CKKSQueryOperation>)queryOperationClass
+       modifySubscriptionsOperationClass:(Class<CKKSModifySubscriptionsOperation>)modifySubscriptionsOperationClass
+         modifyRecordZonesOperationClass:(Class<CKKSModifyRecordZonesOperation>)modifyRecordZonesOperationClass
+                      apsConnectionClass:(Class<CKKSAPSConnection>)apsConnectionClass;
 
-- (NSOperation*) createSetupOperation: (bool) zoneCreated zoneSubscribed: (bool) zoneSubscribed;
 
-- (CKKSResultOperation*beginResetCloudKitZoneOperation;
+- (CKKSResultOperation* _Nullable)beginResetCloudKitZoneOperation;
 
 // Called when CloudKit notifies us that we just logged in.
 // That is, if we transition from any state to CKAccountStatusAvailable.
-// This will be called under the protection of dispatchSync
+// This will be called under the protection of dispatchSync.
+// This is a no-op; you should intercept this call and call handleCKLogin:zoneSubscribed:
+// with the appropriate state
 - (void)handleCKLogin;
 
+// Actually start a cloudkit login. Pass in whether you believe this zone has been created and if this device has
+// subscribed to this zone on the server.
+- (NSOperation* _Nullable)handleCKLogin:(bool)zoneCreated zoneSubscribed:(bool)zoneSubscribed;
+
 // Called when CloudKit notifies us that we just logged out.
 // i.e. we transition from CKAccountStatusAvailable to any other state.
 // This will be called under the protection of dispatchSync
 - (void)handleCKLogout;
 
+// Call this when you're ready for this zone to kick off operations
+// based on iCloud account status
+- (void)initializeZone;
+
 // Cancels all operations (no matter what they are).
 - (void)cancelAllOperations;
+// Reissues the call
+- (void)restartCurrentAccountStateOperation;
 
 // Schedules this operation for execution (if the CloudKit account exists)
-- (bool)scheduleOperation: (NSOperation*) op;
+- (bool)scheduleOperation:(NSOperation*)op;
 
 // Use this to schedule an operation handling account status (cleaning up after logout, etc.).
-- (bool)scheduleAccountStatusOperation: (NSOperation*) op;
+- (bool)scheduleAccountStatusOperation:(NSOperation*)op;
 
 // Schedules this operation for execution, and doesn't do any dependency magic
 // This should _only_ be used if you want to run something even if the CloudKit account is logged out
 - (void)waitUntilAllOperationsAreFinished;
 
 // Use this for testing, to only wait for a certain type of operation to finish.
-- (void)waitForOperationsOfClass:(Class) operationClass;
+- (void)waitForOperationsOfClass:(Class)operationClass;
 
 // If this object wants to do anything that needs synchronization, use this.
-- (void) dispatchSync: (bool (^)(void)) block;
+// If this object has had -halt called, this block will never fire.
+- (void)dispatchSync:(bool (^)(void))block;
+
+// Call this to halt everything this zone is doing. This object will never recover. Use for testing.
+- (void)halt;
 
 // Call this to reset this object's setup, so you can call createSetupOperation again.
 - (void)resetSetup;
 
-#endif
 @end
 
-#endif /* CKKSZone_h */
+NS_ASSUME_NONNULL_END
+#endif  // OCTAGON
index eee5cdb4799b49f1bd7c2f52e313963a4b4f5478..d1006737839ee8e4079d78c640fcf6a8ee15cfce 100644 (file)
@@ -30,7 +30,6 @@
 #import "keychain/ckks/CKKSCKAccountStateTracker.h"
 #import <CloudKit/CloudKit.h>
 #import <CloudKit/CloudKit_Private.h>
-#endif
 
 #import "CKKSKeychainView.h"
 #import "CKKSZone.h"
 #include <utilities/debugging.h>
 
 @interface CKKSZone()
-#if OCTAGON
 
 @property CKDatabaseOperation<CKKSModifyRecordZonesOperation>* zoneCreationOperation;
 @property CKDatabaseOperation<CKKSModifyRecordZonesOperation>* zoneDeletionOperation;
 @property CKDatabaseOperation<CKKSModifySubscriptionsOperation>* zoneSubscriptionOperation;
 
-@property bool acceptingNewOperations;
 @property NSOperationQueue* operationQueue;
 @property NSOperation* accountLoggedInDependency;
 
 @property NSHashTable<NSOperation*>* accountOperations;
-#endif
+
+// Make writable
+@property bool halted;
 @end
 
 @implementation CKKSZone
 
-#if OCTAGON
-
 - (instancetype)initWithContainer:     (CKContainer*) container
                              zoneName: (NSString*) zoneName
                        accountTracker:(CKKSCKAccountStateTracker*) tracker
         _zoneName = zoneName;
         _accountTracker = tracker;
 
+        _halted = false;
+
         _database = [_container privateCloudDatabase];
         _zone = [[CKRecordZone alloc] initWithZoneID: [[CKRecordZoneID alloc] initWithZoneName:zoneName ownerName:CKCurrentUserDefaultName]];
 
-        // Every subclass must set up call beginSetup at least once.
         _accountStatus = CKKSAccountStatusUnknown;
-        [self resetSetup];
+
+        __weak __typeof(self) weakSelf = self;
+        self.accountLoggedInDependency = [NSBlockOperation blockOperationWithBlock:^{
+            ckksnotice("ckkszone", weakSelf, "CloudKit account logged in.");
+        }];
+        self.accountLoggedInDependency.name = @"account-logged-in-dependency";
 
         _accountOperations = [NSHashTable weakObjectsHashTable];
 
 
         _queue = dispatch_queue_create([[NSString stringWithFormat:@"CKKSQueue.%@.zone.%@", container.containerIdentifier, zoneName] UTF8String], DISPATCH_QUEUE_SERIAL);
         _operationQueue = [[NSOperationQueue alloc] init];
-        _acceptingNewOperations = true;
     }
     return self;
 }
 
-// Initialize this object so that we can call beginSetup again
-- (void)resetSetup {
-    self.setupStarted = false;
-    self.setupComplete = false;
-
-    if([self.zoneSetupOperation isPending]) {
-        // Nothing to do here: there's already an existing zoneSetupOperation
-    } else {
-        self.zoneSetupOperation = [[CKKSGroupOperation alloc] init];
-        self.zoneSetupOperation.name = @"zone-setup-operation";
-    }
-
-    if([self.accountLoggedInDependency isPending]) {
-        // Nothing to do here: there's already an existing accountLoggedInDependency
-    } else {
-        __weak __typeof(self) weakSelf = self;
-        self.accountLoggedInDependency = [NSBlockOperation blockOperationWithBlock:^{
-            ckksnotice("ckkszone", weakSelf, "CloudKit account logged in.");
-        }];
-        self.accountLoggedInDependency.name = @"account-logged-in-dependency";
-    }
+- (void)initializeZone {
+    [self.accountTracker notifyOnAccountStatusChange:self];
+}
 
+- (void)resetSetup {
     self.zoneCreated = false;
     self.zoneSubscribed = false;
     self.zoneCreatedError = nil;
 }
 
 
--(void)ckAccountStatusChange: (CKKSAccountStatus)oldStatus to:(CKKSAccountStatus)currentStatus {
-
-    // dispatch this on a serial queue, so we get each transition in order
-    [self dispatchSync: ^bool {
-        ckksnotice("ckkszone", self, "%@ Received notification of CloudKit account status change, moving from %@ to %@",
-                   self.zoneID.zoneName,
-                   [CKKSCKAccountStateTracker stringFromAccountStatus: self.accountStatus],
-                   [CKKSCKAccountStateTracker stringFromAccountStatus: currentStatus]);
-        CKKSAccountStatus oldStatus = self.accountStatus;
-        self.accountStatus = currentStatus;
-
-        switch(currentStatus) {
-            case CKKSAccountStatusAvailable: {
-
-                ckksinfo("ckkszone", self, "logging in while setup started: %d and complete: %d", self.setupStarted, self.setupComplete);
-
-                // This is only a login if we're not in the middle of setup, and the previous state was not logged in
-                if(!(self.setupStarted ^ self.setupComplete) && oldStatus != CKKSAccountStatusAvailable) {
-                    [self resetSetup];
-                    [self handleCKLogin];
-                }
+-(void)ckAccountStatusChange:(CKKSAccountStatus)oldStatus to:(CKKSAccountStatus)currentStatus {
+    ckksnotice("ckkszone", self, "%@ Received notification of CloudKit account status change, moving from %@ to %@",
+               self.zoneID.zoneName,
+               [CKKSCKAccountStateTracker stringFromAccountStatus: oldStatus],
+               [CKKSCKAccountStateTracker stringFromAccountStatus: currentStatus]);
 
-                if(self.accountLoggedInDependency) {
-                    [self.operationQueue addOperation:self.accountLoggedInDependency];
-                    self.accountLoggedInDependency = nil;
-                };
-            }
+    __weak __typeof(self) weakSelf = self;
+    switch(currentStatus) {
+        case CKKSAccountStatusAvailable: {
+            ckksnotice("ckkszone", self, "Logged into iCloud.");
+            [self handleCKLogin];
+
+            if(self.accountLoggedInDependency) {
+                [self.operationQueue addOperation:self.accountLoggedInDependency];
+                self.accountLoggedInDependency = nil;
+            };
+        }
             break;
 
-            case CKKSAccountStatusNoAccount: {
-                ckksnotice("ckkszone", self, "Logging out of iCloud. Shutting down.");
+        case CKKSAccountStatusNoAccount: {
+            ckksnotice("ckkszone", self, "Logging out of iCloud. Shutting down.");
 
-                self.accountLoggedInDependency = [NSBlockOperation blockOperationWithBlock:^{
-                    ckksnotice("ckkszone", self, "CloudKit account logged in again.");
-                }];
-                self.accountLoggedInDependency.name = @"account-logged-in-dependency";
+            self.accountLoggedInDependency = [NSBlockOperation blockOperationWithBlock:^{
+                ckksnotice("ckkszone", weakSelf, "CloudKit account logged in again.");
+            }];
+            self.accountLoggedInDependency.name = @"account-logged-in-dependency";
 
-                [self.operationQueue cancelAllOperations];
-                [self handleCKLogout];
-
-                // now we're in a logged out state. Optimistically prepare for a log in!
-                [self resetSetup];
-            }
+            [self handleCKLogout];
+        }
             break;
 
-            case CKKSAccountStatusUnknown: {
-                // We really don't expect to receive this as a notification, but, okay!
-                ckksnotice("ckkszone", self, "Account status has become undetermined. Pausing for %@", self.zoneID.zoneName);
+        case CKKSAccountStatusUnknown: {
+            // We really don't expect to receive this as a notification, but, okay!
+            ckksnotice("ckkszone", self, "Account status has become undetermined. Pausing for %@", self.zoneID.zoneName);
 
-                self.accountLoggedInDependency = [NSBlockOperation blockOperationWithBlock:^{
-                    ckksnotice("ckkszone", self, "CloudKit account restored from 'unknown'.");
-                }];
-                self.accountLoggedInDependency.name = @"account-logged-in-dependency";
+            self.accountLoggedInDependency = [NSBlockOperation blockOperationWithBlock:^{
+                ckksnotice("ckkszone", weakSelf, "CloudKit account restored from 'unknown'.");
+            }];
+            self.accountLoggedInDependency.name = @"account-logged-in-dependency";
 
-                [self.operationQueue cancelAllOperations];
-                [self resetSetup];
-            }
-            break;
+            [self handleCKLogout];
         }
+            break;
+    }
+}
 
-        return true;
-    }];
+- (void)restartCurrentAccountStateOperation {
+    __weak __typeof(self) weakSelf = self;
+    dispatch_async(self.queue, ^{
+        __strong __typeof(self) strongSelf = weakSelf;
+        ckksnotice("ckksaccount", strongSelf, "Restarting account in state %@", [CKKSCKAccountStateTracker stringFromAccountStatus:strongSelf.accountStatus]);
+        [strongSelf ckAccountStatusChange:strongSelf.accountStatus to:strongSelf.accountStatus];
+    });
 }
 
-- (NSOperation*) createSetupOperation: (bool) zoneCreated zoneSubscribed: (bool) zoneSubscribed {
+- (NSOperation*)handleCKLogin:(bool)zoneCreated zoneSubscribed:(bool)zoneSubscribed {
     if(!SecCKKSIsEnabled()) {
         ckksinfo("ckkszone", self, "Skipping CloudKit registration due to disabled CKKS");
         return nil;
     }
 
     // If we've already started set up, skip doing it again.
-    if(self.setupStarted) {
-        ckksinfo("ckkszone", self, "skipping startup: it's already started");
+    if([self.zoneSetupOperation isPending] || [self.zoneSetupOperation isExecuting]) {
+        ckksnotice("ckkszone", self, "skipping startup: it's already started");
         return self.zoneSetupOperation;
     }
 
-    if(self.zoneSetupOperation == nil) {
-        ckkserror("ckkszone", self, "trying to set up but the setup operation is gone; what happened?");
-        return nil;
-    }
+    self.zoneSetupOperation = [[CKKSGroupOperation alloc] init];
+    self.zoneSetupOperation.name = [NSString stringWithFormat:@"zone-setup-operation-%@", self.zoneName];
 
     self.zoneCreated = zoneCreated;
     self.zoneSubscribed = zoneSubscribed;
     self.zoneSetupOperation.qualityOfService = NSQualityOfServiceUserInitiated;
 
     ckksnotice("ckkszone", self, "Setting up zone %@", self.zoneName);
-    self.setupStarted = true;
 
     __weak __typeof(self) weakSelf = self;
 
     // First, check the account status. If it's sufficient, add the necessary CloudKit operations to this operation
-    NSBlockOperation* doSetup = [NSBlockOperation blockOperationWithBlock:^{
+    __weak CKKSGroupOperation* weakZoneSetupOperation = self.zoneSetupOperation;
+    [self.zoneSetupOperation runBeforeGroupFinished:[CKKSResultOperation named:[NSString stringWithFormat:@"zone-setup-%@", self.zoneName] withBlock:^{
         __strong __typeof(weakSelf) strongSelf = weakSelf;
-        if(!strongSelf) {
+        __strong __typeof(self.zoneSetupOperation) zoneSetupOperation = weakZoneSetupOperation;
+        if(!strongSelf || !zoneSetupOperation) {
             ckkserror("ckkszone", strongSelf, "received callback for released object");
             return;
         }
 
-        __block bool ret = false;
-        [strongSelf dispatchSync: ^bool {
-            strongSelf.accountStatus = [strongSelf.accountTracker currentCKAccountStatusAndNotifyOnChange:strongSelf];
-
-            switch(strongSelf.accountStatus) {
-                case CKKSAccountStatusNoAccount:
-                    ckkserror("ckkszone", strongSelf, "No CloudKit account; quitting setup for %@", strongSelf.zoneID.zoneName);
-                    [strongSelf handleCKLogout];
-                    ret = true;
-                    break;
-                case CKKSAccountStatusAvailable:
-                    if(strongSelf.accountLoggedInDependency) {
-                        [strongSelf.operationQueue addOperation: strongSelf.accountLoggedInDependency];
-                        strongSelf.accountLoggedInDependency = nil;
-                    }
-                    break;
-                case CKKSAccountStatusUnknown:
-                    ckkserror("ckkszone", strongSelf, "CloudKit account status currently unknown; stopping setup for %@", strongSelf.zoneID.zoneName);
-                    ret = true;
-                    break;
-            }
-
-            return true;
-        }];
+        if(strongSelf.accountStatus != CKKSAccountStatusAvailable) {
+            ckkserror("ckkszone", strongSelf, "Zone doesn't believe it's logged in; quitting setup");
+            return;
+        }
 
         NSBlockOperation* setupCompleteOperation = [NSBlockOperation blockOperationWithBlock:^{
             __strong __typeof(weakSelf) strongSelf = weakSelf;
                 return;
             }
 
-            ckksinfo("ckkszone", strongSelf, "%@: Setup complete", strongSelf.zoneName);
-            strongSelf.setupComplete = true;
+            ckksnotice("ckkszone", strongSelf, "%@: Setup complete", strongSelf.zoneName);
         }];
         setupCompleteOperation.name = @"zone-setup-complete-operation";
 
-        // If we don't have an CloudKit account, don't bother continuing
-        if(ret) {
-            [strongSelf.zoneSetupOperation runBeforeGroupFinished:setupCompleteOperation];
-            return;
-        }
-
         // We have an account, so fetch the push environment and bring up APS
         [strongSelf.container serverPreferredPushEnvironmentWithCompletionHandler: ^(NSString *apsPushEnvString, NSError *error) {
             __strong __typeof(weakSelf) strongSelf = weakSelf;
                 CKKSAPSReceiver* aps = [CKKSAPSReceiver receiverForEnvironment:apsPushEnvString
                                                              namedDelegatePort:SecCKKSAPSNamedPort
                                                             apsConnectionClass:strongSelf.apsConnectionClass];
-                [aps register:strongSelf forZoneID:strongSelf.zoneID];
+                [aps registerReceiver:strongSelf forZoneID:strongSelf.zoneID];
             }
         }];
 
             ckksnotice("ckkszone", strongSelf, "Adding CKKSModifyRecordZonesOperation: %@ %@", zoneCreationOperation, zoneCreationOperation.dependencies);
             strongSelf.zoneCreationOperation = zoneCreationOperation;
             [setupCompleteOperation addDependency: modifyRecordZonesCompleteOperation];
-            [strongSelf.zoneSetupOperation runBeforeGroupFinished: zoneCreationOperation];
-            [strongSelf.zoneSetupOperation dependOnBeforeGroupFinished: modifyRecordZonesCompleteOperation];
+            [zoneSetupOperation runBeforeGroupFinished: zoneCreationOperation];
+            [zoneSetupOperation dependOnBeforeGroupFinished: modifyRecordZonesCompleteOperation];
         } else {
-            ckksinfo("ckkszone", strongSelf, "no need to create the zone '%@'", strongSelf.zoneName);
+            ckksnotice("ckkszone", strongSelf, "no need to create the zone '%@'", strongSelf.zoneName);
         }
 
         if(!zoneSubscribed) {
             }
             strongSelf.zoneSubscriptionOperation = zoneSubscriptionOperation;
             [setupCompleteOperation addDependency: zoneSubscriptionCompleteOperation];
-            [strongSelf.zoneSetupOperation runBeforeGroupFinished:zoneSubscriptionOperation];
-            [strongSelf.zoneSetupOperation dependOnBeforeGroupFinished: zoneSubscriptionCompleteOperation];
+            [zoneSetupOperation runBeforeGroupFinished:zoneSubscriptionOperation];
+            [zoneSetupOperation dependOnBeforeGroupFinished: zoneSubscriptionCompleteOperation];
         } else {
-            ckksinfo("ckkszone", strongSelf, "no need to create database subscription");
+            ckksnotice("ckkszone", strongSelf, "no need to create database subscription");
         }
 
         [strongSelf.zoneSetupOperation runBeforeGroupFinished:setupCompleteOperation];
-    }];
-    doSetup.name = @"begin-zone-setup";
-
-    [self.zoneSetupOperation runBeforeGroupFinished:doSetup];
+    }]];
 
+    [self scheduleAccountStatusOperation:self.zoneSetupOperation];
     return self.zoneSetupOperation;
 }
 
 
 - (CKKSResultOperation*)beginResetCloudKitZoneOperation {
     if(!SecCKKSIsEnabled()) {
-        ckksinfo("ckkszone", self, "Skipping CloudKit reset due to disabled CKKS");
+        ckksnotice("ckkszone", self, "Skipping CloudKit reset due to disabled CKKS");
         return nil;
     }
 
         }
 
         ckksinfo("ckkszone", strongSelf, "record zones deletion %@ completed with error: %@", deletedRecordZoneIDs, operationError);
-        [strongSelf resetSetup];
 
         if(operationError && fatalError) {
             // If the error wasn't actually a problem, don't report it upward.
     // If the zone creation operation is still pending, wait for it to complete before attempting zone deletion
     [zoneDeletionOperation addNullableDependency: self.zoneCreationOperation];
 
-    ckksinfo("ckkszone", self, "deleting zone with %@ %@", zoneDeletionOperation, zoneDeletionOperation.dependencies);
+    ckksnotice("ckkszone", self, "deleting zone with %@ %@", zoneDeletionOperation, zoneDeletionOperation.dependencies);
     // Don't use scheduleOperation: zone deletions should be attempted even if we're "logged out"
     [self.operationQueue addOperation: zoneDeletionOperation];
     self.zoneDeletionOperation = zoneDeletionOperation;
 }
 
 - (void)handleCKLogin {
-    ckksinfo("ckkszone", self, "received a notification of CK login, ignoring");
+    ckksinfo("ckkszone", self, "received a notification of CK login");
+    self.accountStatus = CKKSAccountStatusAvailable;
 }
 
 - (void)handleCKLogout {
-    ckksinfo("ckkszone", self, "received a notification of CK logout, ignoring");
+    ckksinfo("ckkszone", self, "received a notification of CK logout");
+    self.accountStatus = CKKSAccountStatusNoAccount;
+    [self resetSetup];
 }
 
 - (bool)scheduleOperation: (NSOperation*) op {
-    if(!self.acceptingNewOperations) {
-        ckksdebug("ckkszone", self, "attempted to schedule an operation on a cancelled zone, ignoring");
+    if(self.halted) {
+        ckkserror("ckkszone", self, "attempted to schedule an operation on a halted zone, ignoring");
         return false;
     }
 
 }
 
 - (bool)scheduleAccountStatusOperation: (NSOperation*) op {
+    if(self.halted) {
+        ckkserror("ckkszone", self, "attempted to schedule an account operation on a halted zone, ignoring");
+        return false;
+    }
+
     // Always succeed. But, account status operations should always proceed in-order.
     [op linearDependencies:self.accountOperations];
     [self.operationQueue addOperation: op];
 
 // to be used rarely, if at all
 - (bool)scheduleOperationWithoutDependencies:(NSOperation*)op {
+    if(self.halted) {
+        ckkserror("ckkszone", self, "attempted to schedule an non-dependent operation on a halted zone, ignoring");
+        return false;
+    }
+
     [self.operationQueue addOperation: op];
     return true;
 }
     // important enough to block this thread.
     __block bool ok = false;
     dispatch_sync(self.queue, ^{
+        if(self.halted) {
+            ckkserror("ckkszone", self, "CKKSZone not dispatchSyncing a block (due to being halted)");
+            return;
+        }
+
         ok = block();
         if(!ok) {
             ckkserror("ckkszone", self, "CKKSZone block returned false");
     });
 }
 
+- (void)halt {
+    // Synchronously set the 'halted' bit
+    dispatch_sync(self.queue, ^{
+        self.halted = true;
+    });
 
-#endif /* OCTAGON */
-@end
+    // Bring all operations down, too
+    [self cancelAllOperations];
+}
 
+@end
 
+#endif /* OCTAGON */
 
index 41d4d4ff63d755cde00211d20631e83fe8e411dd..f03db22af5de067ab4362e9604dc7b0b4e46623e 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
+#if OCTAGON
+#import <CloudKit/CloudKit.h>
 #import <Foundation/Foundation.h>
+#import "keychain/ckks/CKKSResultOperation.h"
 
-#if OCTAGON
+NS_ASSUME_NONNULL_BEGIN
 
 /* Fetch Reasons */
 @protocol SecCKKSFetchBecause
@@ -39,7 +42,7 @@ extern CKKSFetchBecause* const CKKSFetchBecauseKeyHierarchy;
 extern CKKSFetchBecause* const CKKSFetchBecauseTesting;
 
 @protocol CKKSChangeFetcherErrorOracle
-- (bool) isFatalCKFetchError: (NSError*) error;
+- (bool)isFatalCKFetchError:(NSError*)error;
 @end
 
 /*
@@ -49,12 +52,10 @@ extern CKKSFetchBecause* const CKKSFetchBecauseTesting;
  */
 
 @class CKKSKeychainView;
-#import "keychain/ckks/CKKSGroupOperation.h"
-#import <CloudKit/CloudKit.h>
 
 @interface CKKSZoneChangeFetcher : NSObject
 
-@property (weak) CKKSKeychainView* ckks;
+@property (nullable, weak) CKKSKeychainView* ckks;
 @property CKRecordZoneID* zoneID;
 
 - (instancetype)init NS_UNAVAILABLE;
@@ -64,12 +65,10 @@ extern CKKSFetchBecause* const CKKSFetchBecauseTesting;
 - (CKKSResultOperation*)requestSuccessfulResyncFetch:(CKKSFetchBecause*)why;
 
 // We don't particularly care what this does, as long as it finishes
-- (void)holdFetchesUntil:(CKKSResultOperation*)holdOperation;
+- (void)holdFetchesUntil:(CKKSResultOperation* _Nullable)holdOperation;
 
--(void)cancel;
+- (void)cancel;
 @end
 
-
+NS_ASSUME_NONNULL_END
 #endif
-
-
index 274d3dc909d20e0aaf8c0fc37fdc7877997c17e3..e66f16bfee00b1c5876e148f40e6dce5c20a7147 100644 (file)
@@ -21,9 +21,9 @@
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#import "CKKSSQLDatabaseObject.h"
-#include <utilities/SecDb.h>
 #include <securityd/SecDbItem.h>
+#include <utilities/SecDb.h>
+#import "CKKSSQLDatabaseObject.h"
 
 #ifndef CKKSZoneStateEntry_h
 #define CKKSZoneStateEntry_h
 
 @class CKKSRateLimiter;
 
-@interface CKKSZoneStateEntry : CKKSSQLDatabaseObject {
-
-}
+@interface CKKSZoneStateEntry : CKKSSQLDatabaseObject
 
 @property NSString* ckzone;
 @property bool ckzonecreated;
 @property bool ckzonesubscribed;
-@property (getter=getChangeToken,setter=setChangeToken:) CKServerChangeToken* changeToken;
+@property (getter=getChangeToken, setter=setChangeToken:) CKServerChangeToken* changeToken;
 @property NSData* encodedChangeToken;
 @property NSDate* lastFetchTime;
 
 @property CKKSRateLimiter* rateLimiter;
 @property NSData* encodedRateLimiter;
 
-+ (instancetype) state: (NSString*) ckzone;
++ (instancetype)state:(NSString*)ckzone;
 
-+ (instancetype) fromDatabase: (NSString*) ckzone error: (NSError * __autoreleasing *) error;
-+ (instancetype) tryFromDatabase: (NSString*) ckzone error: (NSError * __autoreleasing *) error;
++ (instancetype)fromDatabase:(NSString*)ckzone error:(NSError* __autoreleasing*)error;
++ (instancetype)tryFromDatabase:(NSString*)ckzone error:(NSError* __autoreleasing*)error;
 
 - (instancetype)initWithCKZone:(NSString*)ckzone
                    zoneCreated:(bool)ckzonecreated
                      lastFixup:(CKKSFixup)lastFixup
             encodedRateLimiter:(NSData*)encodedRateLimiter;
 
-- (CKServerChangeToken*) getChangeToken;
-- (void) setChangeToken: (CKServerChangeToken*) token;
+- (CKServerChangeToken*)getChangeToken;
+- (void)setChangeToken:(CKServerChangeToken*)token;
 
-- (BOOL)isEqual: (id) object;
+- (BOOL)isEqual:(id)object;
 @end
 
 #endif
index 25d0a49935ec624ff5799f9bb54f538b94b44885..9ba1d850b05a55728c742221c7801bc2abc73243 100644 (file)
 
 #if OCTAGON
 
-#import <Foundation/Foundation.h>
 #import <CloudKit/CloudKit.h>
 #import <CloudKit/CloudKit_Private.h>
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
 
 @interface CKOperationGroup (CKKS)
-+(instancetype) CKKSGroupWithName:(NSString*)name;
++ (instancetype)CKKSGroupWithName:(NSString*)name;
 @end
 
 @interface NSError (CKKS)
 
 // More useful constructor
 + (instancetype)errorWithDomain:(NSErrorDomain)domain code:(NSInteger)code description:(NSString*)description;
-+ (instancetype)errorWithDomain:(NSErrorDomain)domain code:(NSInteger)code description:(NSString*)description underlying:(NSError*)underlying;
+
++ (instancetype)errorWithDomain:(NSErrorDomain)domain
+                           code:(NSInteger)code
+                    description:(NSString*)description
+                     underlying:(NSError* _Nullable)underlying;
 
 // Returns true if this is a CloudKit error where
 // 1) An atomic write failed
 // 2) Every single suberror is either CKErrorServerRecordChanged or CKErrorUnknownItem
--(bool) ckksIsCKErrorRecordChangedError;
+- (bool)ckksIsCKErrorRecordChangedError;
 @end
 
 // Ensure we don't print addresses
 @interface CKAccountInfo (CKKS)
--(NSString*)description;
+- (NSString*)description;
 @end
 
-#endif // OCTAGON
+NS_ASSUME_NONNULL_END
+#endif  // OCTAGON
index 39fb4a2ce02b6af06f0767db99d119aac4780ee2..ae8f9e3ab28dad7fe03de9f7cfde1761ddd016d4 100644 (file)
 #ifndef CloudKitDependencies_h
 #define CloudKitDependencies_h
 
-#import <Foundation/Foundation.h>
-#import <CloudKit/CloudKit.h>
 #import <ApplePushService/ApplePushService.h>
+#import <CloudKit/CloudKit.h>
+#import <Foundation/Foundation.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
 /* CKModifyRecordZonesOperation */
 @protocol CKKSModifyRecordZonesOperation <NSObject>
 + (instancetype)alloc;
-- (instancetype)initWithRecordZonesToSave:(nullable NSArray<CKRecordZone *> *)recordZonesToSave recordZoneIDsToDelete:(nullable NSArray<CKRecordZoneID *> *)recordZoneIDsToDelete;
+- (instancetype)initWithRecordZonesToSave:(nullable NSArray<CKRecordZone*>*)recordZonesToSave
+                    recordZoneIDsToDelete:(nullable NSArray<CKRecordZoneID*>*)recordZoneIDsToDelete;
 
-@property (nonatomic, strong, nullable) CKDatabase *database;
-@property (nonatomic, copy, nullable) NSArray<CKRecordZone *> *recordZonesToSave;
-@property (nonatomic, copy, nullable) NSArray<CKRecordZoneID *> *recordZoneIDsToDelete;
+@property (nonatomic, strong, nullable) CKDatabasedatabase;
+@property (nonatomic, copy, nullable) NSArray<CKRecordZone*>* recordZonesToSave;
+@property (nonatomic, copy, nullable) NSArray<CKRecordZoneID*>* recordZoneIDsToDelete;
 @property NSOperationQueuePriority queuePriority;
 @property NSQualityOfService qualityOfService;
 
-@property (nonatomic, copy, nullable) void (^modifyRecordZonesCompletionBlock)(NSArray<CKRecordZone *> * _Nullable savedRecordZones, NSArray<CKRecordZoneID *> * _Nullable deletedRecordZoneIDs, NSError * _Nullable operationError);
+@property (nonatomic, copy, nullable) void (^modifyRecordZonesCompletionBlock)
+    (NSArray<CKRecordZone*>* _Nullable savedRecordZones, NSArray<CKRecordZoneID*>* _Nullable deletedRecordZoneIDs, NSError* _Nullable operationError);
 
 @end
 
-@interface CKModifyRecordZonesOperation (SecCKKSModifyRecordZonesOperation) <CKKSModifyRecordZonesOperation>;
+@interface CKModifyRecordZonesOperation (SecCKKSModifyRecordZonesOperation) <CKKSModifyRecordZonesOperation>
+;
 @end
 
 /* CKModifySubscriptionsOperation */
 @protocol CKKSModifySubscriptionsOperation <NSObject>
 + (instancetype)alloc;
-- (instancetype)initWithSubscriptionsToSave:(nullable NSArray<CKSubscription *> *)subscriptionsToSave subscriptionIDsToDelete:(nullable NSArray<NSString *> *)subscriptionIDsToDelete;
+- (instancetype)initWithSubscriptionsToSave:(nullable NSArray<CKSubscription*>*)subscriptionsToSave
+                    subscriptionIDsToDelete:(nullable NSArray<NSString*>*)subscriptionIDsToDelete;
 
-@property (nonatomic, strong, nullable) CKDatabase *database;
-@property (nonatomic, copy, nullable) NSArray<CKSubscription *> *subscriptionsToSave;
-@property (nonatomic, copy, nullable) NSArray<NSString *> *subscriptionIDsToDelete;
+@property (nonatomic, strong, nullable) CKDatabasedatabase;
+@property (nonatomic, copy, nullable) NSArray<CKSubscription*>* subscriptionsToSave;
+@property (nonatomic, copy, nullable) NSArray<NSString*>* subscriptionIDsToDelete;
 @property NSOperationQueuePriority queuePriority;
 @property NSQualityOfService qualityOfService;
-@property (nonatomic, strong, nullable) CKOperationGroup *group;
+@property (nonatomic, strong, nullable) CKOperationGroupgroup;
 
-@property (nonatomic, copy, nullable) void (^modifySubscriptionsCompletionBlock)(NSArray<CKSubscription *> * _Nullable savedSubscriptions, NSArray<NSString *> * _Nullable deletedSubscriptionIDs, NSError * _Nullable operationError);
+@property (nonatomic, copy, nullable) void (^modifySubscriptionsCompletionBlock)
+    (NSArray<CKSubscription*>* _Nullable savedSubscriptions, NSArray<NSString*>* _Nullable deletedSubscriptionIDs, NSError* _Nullable operationError);
 @end
 
-@interface CKModifySubscriptionsOperation (SecCKKSModifySubscriptionsOperation) <CKKSModifySubscriptionsOperation>;
+@interface CKModifySubscriptionsOperation (SecCKKSModifySubscriptionsOperation) <CKKSModifySubscriptionsOperation>
+;
 @end
 
 /* CKFetchRecordZoneChangesOperation */
 @protocol CKKSFetchRecordZoneChangesOperation <NSObject>
 + (instancetype)alloc;
-- (instancetype)initWithRecordZoneIDs:(NSArray<CKRecordZoneID *> *)recordZoneIDs optionsByRecordZoneID:(nullable NSDictionary<CKRecordZoneID *, CKFetchRecordZoneChangesOptions *> *)optionsByRecordZoneID;
+- (instancetype)initWithRecordZoneIDs:(NSArray<CKRecordZoneID*>*)recordZoneIDs
+                optionsByRecordZoneID:(nullable NSDictionary<CKRecordZoneID*, CKFetchRecordZoneChangesOptions*>*)optionsByRecordZoneID;
 
-@property (nonatomic, copy, nullable) NSArray<CKRecordZoneID *> *recordZoneIDs;
-@property (nonatomic, copy, nullable) NSDictionary<CKRecordZoneID *, CKFetchRecordZoneChangesOptions *> *optionsByRecordZoneID;
+@property (nonatomic, copy, nullable) NSArray<CKRecordZoneID*>* recordZoneIDs;
+@property (nonatomic, copy, nullable) NSDictionary<CKRecordZoneID*, CKFetchRecordZoneChangesOptions*>* optionsByRecordZoneID;
 
 @property (nonatomic, assign) BOOL fetchAllChanges;
-@property (nonatomic, copy, nullable) void (^recordChangedBlock)(CKRecord *record);
-@property (nonatomic, copy, nullable) void (^recordWithIDWasDeletedBlock)(CKRecordID *recordID, NSString *recordType);
-@property (nonatomic, copy, nullable) void (^recordZoneChangeTokensUpdatedBlock)(CKRecordZoneID *recordZoneID, CKServerChangeToken * _Nullable serverChangeToken, NSData * _Nullable clientChangeTokenData);
-@property (nonatomic, copy, nullable) void (^recordZoneFetchCompletionBlock)(CKRecordZoneID *recordZoneID, CKServerChangeToken * _Nullable serverChangeToken, NSData * _Nullable clientChangeTokenData, BOOL moreComing, NSError * _Nullable recordZoneError);
-@property (nonatomic, copy, nullable) void (^fetchRecordZoneChangesCompletionBlock)(NSError * _Nullable operationError);
+@property (nonatomic, copy, nullable) void (^recordChangedBlock)(CKRecord* record);
+@property (nonatomic, copy, nullable) void (^recordWithIDWasDeletedBlock)(CKRecordID* recordID, NSString* recordType);
+@property (nonatomic, copy, nullable) void (^recordZoneChangeTokensUpdatedBlock)
+    (CKRecordZoneID* recordZoneID, CKServerChangeToken* _Nullable serverChangeToken, NSData* _Nullable clientChangeTokenData);
+@property (nonatomic, copy, nullable) void (^recordZoneFetchCompletionBlock)(CKRecordZoneID* recordZoneID,
+                                                                             CKServerChangeToken* _Nullable serverChangeToken,
+                                                                             NSData* _Nullable clientChangeTokenData,
+                                                                             BOOL moreComing,
+                                                                             NSError* _Nullable recordZoneError);
+@property (nonatomic, copy, nullable) void (^fetchRecordZoneChangesCompletionBlock)(NSError* _Nullable operationError);
 
-@property (nonatomic, strong, nullable) CKOperationGroup *group;
+@property (nonatomic, strong, nullable) CKOperationGroupgroup;
 @end
 
-@interface CKFetchRecordZoneChangesOperation () <CKKSFetchRecordZoneChangesOperation>;
+@interface CKFetchRecordZoneChangesOperation () <CKKSFetchRecordZoneChangesOperation>
+;
 @end
 
 /* CKFetchRecordsOperation */
 @protocol CKKSFetchRecordsOperation <NSObject>
 + (instancetype)alloc;
 - (instancetype)init;
-- (instancetype)initWithRecordIDs:(NSArray<CKRecordID *> *)recordIDs;
+- (instancetype)initWithRecordIDs:(NSArray<CKRecordID*>*)recordIDs;
 
-@property (nonatomic, copy, nullable) NSArray<CKRecordID *> *recordIDs;
-@property (nonatomic, copy, nullable) NSArray<NSString *> *desiredKeys;
-@property (nonatomic, copy, nullable) void (^perRecordProgressBlock)(CKRecordID *recordID, double progress);
-@property (nonatomic, copy, nullable) void (^perRecordCompletionBlock)(CKRecord * _Nullable record, CKRecordID * _Nullable recordID, NSError * _Nullable error);
-@property (nonatomic, copy, nullable) void (^fetchRecordsCompletionBlock)(NSDictionary<CKRecordID * , CKRecord *> * _Nullable recordsByRecordID, NSError * _Nullable operationError);
+@property (nonatomic, copy, nullable) NSArray<CKRecordID*>* recordIDs;
+@property (nonatomic, copy, nullable) NSArray<NSString*>* desiredKeys;
+@property (nonatomic, copy, nullable) void (^perRecordProgressBlock)(CKRecordID* recordID, double progress);
+@property (nonatomic, copy, nullable) void (^perRecordCompletionBlock)
+    (CKRecord* _Nullable record, CKRecordID* _Nullable recordID, NSError* _Nullable error);
+@property (nonatomic, copy, nullable) void (^fetchRecordsCompletionBlock)
+    (NSDictionary<CKRecordID*, CKRecord*>* _Nullable recordsByRecordID, NSError* _Nullable operationError);
 @end
 
 @interface CKFetchRecordsOperation () <CKKSFetchRecordsOperation>
@@ -107,18 +122,18 @@ NS_ASSUME_NONNULL_BEGIN
 
 @protocol CKKSQueryOperation <NSObject>
 + (instancetype)alloc;
-- (instancetype)initWithQuery:(CKQuery *)query;
+- (instancetype)initWithQuery:(CKQuery*)query;
 //Not implemented: - (instancetype)initWithCursor:(CKQueryCursor *)cursor;
 
-@property (nonatomic, copy, nullable) CKQuery *query;
-@property (nonatomic, copy, nullable) CKQueryCursor *cursor;
+@property (nonatomic, copy, nullable) CKQueryquery;
+@property (nonatomic, copy, nullable) CKQueryCursorcursor;
 
-@property (nonatomic, copy, nullable) CKRecordZoneID *zoneID;
+@property (nonatomic, copy, nullable) CKRecordZoneIDzoneID;
 @property (nonatomic, assign) NSUInteger resultsLimit;
-@property (nonatomic, copy, nullable) NSArray<NSString *> *desiredKeys;
+@property (nonatomic, copy, nullable) NSArray<NSString*>* desiredKeys;
 
-@property (nonatomic, copy, nullable) void (^recordFetchedBlock)(CKRecord *record);
-@property (nonatomic, copy, nullable) void (^queryCompletionBlock)(CKQueryCursor * _Nullable cursor, NSError * _Nullable operationError);
+@property (nonatomic, copy, nullable) void (^recordFetchedBlock)(CKRecordrecord);
+@property (nonatomic, copy, nullable) void (^queryCompletionBlock)(CKQueryCursor* _Nullable cursor, NSError* _Nullable operationError);
 @end
 
 @interface CKQueryOperation () <CKKSQueryOperation>
@@ -127,14 +142,16 @@ NS_ASSUME_NONNULL_BEGIN
 /* APSConnection */
 @protocol CKKSAPSConnection <NSObject>
 + (instancetype)alloc;
-- (id)initWithEnvironmentName:(NSString *)environmentName namedDelegatePort:(NSString*)namedDelegatePort queue:(dispatch_queue_t)queue;
+- (id)initWithEnvironmentName:(NSString*)environmentName
+            namedDelegatePort:(NSString*)namedDelegatePort
+                        queue:(dispatch_queue_t)queue;
 
-- (void)setEnabledTopics:(NSArray *)enabledTopics;
+- (void)setEnabledTopics:(NSArray*)enabledTopics;
 
 @property (nonatomic, readwrite, assign) id<APSConnectionDelegate> delegate;
 @end
 
-@interface APSConnection (SecCKKSAPSConnection) <CKKSAPSConnection>;
+@interface APSConnection (SecCKKSAPSConnection) <CKKSAPSConnection>
 @end
 
 /* NSNotificationCenter */
@@ -148,13 +165,13 @@ NS_ASSUME_NONNULL_BEGIN
 
 /* Since CKDatabase doesn't share any types with NSOperationQueue, tell the type system about addOperation */
 @protocol CKKSOperationQueue <NSObject>
-- (void)addOperation:(NSOperation *)operation;
+- (void)addOperation:(NSOperation*)operation;
 @end
 
-@interface CKDatabase () <CKKSOperationQueue>;
+@interface CKDatabase () <CKKSOperationQueue>
 @end
 
-@interface NSOperationQueue () <CKKSOperationQueue>;
+@interface NSOperationQueue () <CKKSOperationQueue>
 @end
 
 NS_ASSUME_NONNULL_END
index 1255446fa4234dd22174a09e0e51818d9d06b33f..3672768be893ddba65f1c25cda61c3f5be89f5c7 100644 (file)
 - (NSString*)selfname;
 
 // If op is nonnull, op becomes a dependency of this operation
-- (void)addNullableDependency: (NSOperation*) op;
+- (void)addNullableDependency:(NSOperation*)op;
 
 // Add all operations in this collection as dependencies, then add yourself to the collection
--(void)linearDependencies:(NSHashTable*)collection;
+- (void)linearDependencies:(NSHashTable*)collection;
 
 // Insert yourself as high up the linearized list of dependencies as possible
--(void)linearDependenciesWithSelfFirst: (NSHashTable*) collection;
+- (void)linearDependenciesWithSelfFirst:(NSHashTable*)collection;
 
 // Return a stringified representation of this operation's live dependencies.
--(NSString*)pendingDependenciesString:(NSString*)prefix;
+- (NSString*)pendingDependenciesString:(NSString*)prefix;
 @end
 
 @interface NSBlockOperation (CKKSUsefulConstructorOperation)
-+(instancetype)named: (NSString*)name withBlock: (void(^)(void)) block;
++ (instancetype)named:(NSString*)name withBlock:(void (^)(void))block;
 @end
-
index 8d9725df1fc2af39db1e26822eac23076628a08e..da565e2c871f804a8215a7e01f493bcfee562ce6 100644 (file)
@@ -77,7 +77,7 @@
     dependencies = [dependencies objectsAtIndexes: [dependencies indexesOfObjectsPassingTest: ^BOOL (id obj,
                                                                                                      NSUInteger idx,
                                                                                                      BOOL* stop) {
-        return [obj isPending] ? YES : NO;
+        return [obj isFinished] ? NO : YES;
     }]];
 
     if(dependencies.count == 0u) {
index 7e986c6f3847b24a4db186f8ea3d4eab9e05b0e8..da51fc15e543294675d26967c2e3459c99749ce9 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#ifndef RateLimiter_h
-#define RateLimiter_h
-
 #import <Foundation/Foundation.h>
 
+NS_ASSUME_NONNULL_BEGIN
+
 @interface RateLimiter : NSObject <NSSecureCoding>
 
-@property (readonly, nonatomic, nonnull) NSDictionary *config;
+@property (readonly, nonatomic) NSDictionary* config;
 @property (readonly, nonatomic) NSUInteger stateSize;
-@property (readonly, nonatomic, nullable) NSString *assetType;
+@property (readonly, nonatomic, nullable) NSStringassetType;
 
 typedef NS_ENUM(NSInteger, RateLimiterBadness) {
-    RateLimiterBadnessClear = 0,            // everything is fine, process right now
+    RateLimiterBadnessClear = 0,  // everything is fine, process right now
     RateLimiterBadnessCongested,
     RateLimiterBadnessSeverelyCongested,
     RateLimiterBadnessGridlocked,
-    RateLimiterBadnessOverloaded,           // everything is on fire, go away
+    RateLimiterBadnessOverloaded,  // everything is on fire, go away
 };
 
-- (instancetype _Nullable)initWithConfig:(NSDictionary * _Nonnull)config;
-- (instancetype _Nullable)initWithPlistFromURL:(NSURL * _Nonnull)url;
-- (instancetype _Nullable)initWithAssetType:(NSString * _Nonnull)type;  // Not implemented yet
-- (instancetype _Nullable)initWithCoder:(NSCoder * _Nonnull)coder;
+- (instancetype _Nullable)initWithConfig:(NSDictionary*)config;
+- (instancetype _Nullable)initWithPlistFromURL:(NSURL*)url;
+- (instancetype _Nullable)initWithAssetType:(NSString*)type;  // Not implemented yet
+- (instancetype _Nullable)initWithCoder:(NSCoder*)coder;
 - (instancetype _Nullable)init NS_UNAVAILABLE;
 
 /*!
@@ -57,10 +56,10 @@ typedef NS_ENUM(NSInteger, RateLimiterBadness) {
  * At badness 5 judge:at: has determined there is too much activity so the caller should hold off altogether. The limitTime object will indicate when
  * this overloaded state will end.
  */
-- (NSInteger)judge:(id _Nonnull)obj at:(NSDate * _Nonnull)time limitTime:(NSDate * _Nullable __autoreleasing * _Nonnull)limitTime;
+- (NSInteger)judge:(id)obj at:(NSDate*)time limitTime:(NSDate* _Nonnull __autoreleasing* _Nonnull)limitTime;
 
 - (void)reset;
-- (NSString * _Nonnull)diagnostics;
+- (NSString*)diagnostics;
 + (BOOL)supportsSecureCoding;
 
 // TODO:
@@ -68,7 +67,7 @@ typedef NS_ENUM(NSInteger, RateLimiterBadness) {
 
 @end
 
-#endif /* RateLimiter_h */
+NS_ASSUME_NONNULL_END
 
 /* Annotated example plist
 
index 810651bc2a47e4fb88afb18f28b25a1c91216ef0..fbbc5ec7bac2b9f422c2370831dd5c6e92409cbc 100644 (file)
@@ -35,7 +35,7 @@
 #endif
 
 @interface RateLimiter()
-@property (readwrite, nonatomic, nonnull) NSDictionary *config;
+@property (readwrite, nonatomic) NSDictionary *config;
 @property (nonatomic) NSArray<NSMutableDictionary<NSString *, NSDate *> *> *groups;
 @property (nonatomic) NSDate *lastJudgment;
 @property (nonatomic) NSDate *overloadUntil;
diff --git a/keychain/ckks/tests/AutoreleaseTest.c b/keychain/ckks/tests/AutoreleaseTest.c
new file mode 100644 (file)
index 0000000..796ef50
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2017 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <dispatch/dispatch.h>
+#include <objc/objc-internal.h>
+
+#include "AutoreleaseTest.h"
+
+static void
+read_releases_pending(int fd, void (^handler)(ssize_t))
+{
+    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+        ssize_t result = -1;
+
+        FILE *fp = fdopen(fd, "r");
+
+        char *line = NULL;
+        size_t linecap = 0;
+        ssize_t linelen;
+        while ((linelen = getline(&line, &linecap, fp)) > 0) {
+            ssize_t pending;
+
+            if (sscanf(line, "objc[%*d]: %ld releases pending", &pending) == 1) {
+                result = pending;
+                break;
+            }
+        }
+        free(line);
+
+        fclose(fp);
+
+        handler(result);
+    });
+}
+
+ssize_t
+pending_autorelease_count(void)
+{
+    __block ssize_t result = -1;
+    dispatch_semaphore_t sema;
+    int fds[2];
+    int saved_stderr;
+
+    // stderr replacement pipe
+    pipe(fds);
+    fcntl(fds[1], F_SETNOSIGPIPE, 1);
+
+    // sead asynchronously - takes ownership of fds[0]
+    sema = dispatch_semaphore_create(0);
+    read_releases_pending(fds[0], ^(ssize_t pending) {
+        result = pending;
+        dispatch_semaphore_signal(sema);
+    });
+
+    // save and replace stderr
+    saved_stderr = dup(STDERR_FILENO);
+    dup2(fds[1], STDERR_FILENO);
+    close(fds[1]);
+
+    // make objc print the current autorelease pool
+    _objc_autoreleasePoolPrint();
+
+    // restore stderr
+    dup2(saved_stderr, STDERR_FILENO);
+    close(saved_stderr);
+
+    // wait for the reader
+    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
+#if !__has_feature(objc_arc)
+    dispatch_release(sema);
+#endif
+
+    return result;
+}
diff --git a/keychain/ckks/tests/AutoreleaseTest.h b/keychain/ckks/tests/AutoreleaseTest.h
new file mode 100644 (file)
index 0000000..9ef88f1
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2017 Apple Inc. All Rights Reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#define TEST_API_AUTORELEASE_BEFORE(name) size_t _pending_before_##name = pending_autorelease_count()
+
+#define TEST_API_AUTORELEASE_AFTER(name)                        \
+    size_t _pending_after_##name = pending_autorelease_count(); \
+    XCTAssertEqual(_pending_before_##name, _pending_after_##name, "pending autoreleases unchanged (%lu->%lu)", _pending_before_##name, _pending_after_##name)
+
+ssize_t pending_autorelease_count(void);
index e1cecaf373e1d4f60776e63597e7d366f3d594c9..570c4c8cd5af7fb9cc9aea31ccd0399b305a7c11 100644 (file)
     XCTAssertNil(unwrappedKey, "unwrapped key was not returned in error case");
 }
 
+- (void)testKeyKeychainSaving {
+    NSError* error = nil;
+    CKKSKey* tlk =  [self fakeTLK:self.testZoneID];
+
+    XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
+    XCTAssertNil(error, "tlk should save to database without error");
+    XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
+    XCTAssertNil(error, "should be no error loading the tlk from the keychain");
+
+    XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
+    XCTAssertNil(error, "tlk should save again to database without error");
+    XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
+    XCTAssertNil(error, "should be no error loading the tlk from the keychain");
+
+    [tlk deleteKeyMaterialFromKeychain:&error];
+    XCTAssertNil(error, "tlk should be able to delete itself without error");
+
+    XCTAssertFalse([tlk loadKeyMaterialFromKeychain:&error], "Should not able to reload key material");
+    XCTAssertNotNil(error, "should be error loading the tlk from the keychain");
+    error = nil;
+
+    NSData* keydata = [@"asdf" dataUsingEncoding:NSUTF8StringEncoding];
+
+    // Add an item using no viewhint that will conflict with itself upon a SecItemUpdate (internal builds only)
+    NSMutableDictionary* query = [@{
+                                    (id)kSecClass : (id)kSecClassInternetPassword,
+                                    (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
+                                    (id)kSecAttrNoLegacy : @YES,
+                                    (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
+                                    (id)kSecAttrDescription: tlk.keyclass,
+                                    (id)kSecAttrServer: tlk.zoneID.zoneName,
+                                    (id)kSecAttrAccount: tlk.uuid,
+                                    (id)kSecAttrPath: tlk.parentKeyUUID,
+                                    (id)kSecAttrIsInvisible: @YES,
+                                    (id)kSecValueData : keydata,
+                                    (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+                                    } mutableCopy];
+    XCTAssertEqual(errSecSuccess, SecItemAdd((__bridge CFDictionaryRef)query, NULL), "Should be able to add a conflicting item");
+
+    XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
+    XCTAssertNil(error, "tlk should save to database without error");
+    XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
+    XCTAssertNil(error, "should be no error loading the tlk from the keychain");
+
+    XCTAssertTrue([tlk saveKeyMaterialToKeychain:false error:&error], "should be able to save key material to keychain (without stashing)");
+    XCTAssertNil(error, "tlk should save again to database without error");
+    XCTAssertTrue([tlk loadKeyMaterialFromKeychain:&error], "Should be able to reload key material");
+    XCTAssertNil(error, "should be no error loading the tlk from the keychain");
+}
+
 - (void)testKeyHierarchy {
     NSError* error = nil;
     NSData* testCKRecord = [@"nonsense" dataUsingEncoding:NSUTF8StringEncoding];
        XCTAssertNil(unpadded, "Cannot remove padding where none exists");
 
        // Feeding nil
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
        padded = [CKKSItemEncrypter padData:nil blockSize:0 additionalBlock:extra];
        XCTAssertNotNil(padded, "padData always returns a data object");
     XCTAssertEqual(padded.length, extra ? 2ul : 1ul, "Length of padded nil object is padding byte only--two if extra");
        unpadded = [CKKSItemEncrypter removePaddingFromData:nil];
        XCTAssertNil(unpadded, "Removing padding from nil is senseless");
+#pragma clang diagnostic pop
 }
 
 - (BOOL)encryptAndDecryptDictionary:(NSDictionary<NSString*, NSData*>*)data key:(CKKSKey *)key {
index 4b8fb99762d5aef5deac0f97182f34f0f02955ed..6daee17d6bbfc27b97663127ed1368e7e0dfb2c7 100644 (file)
@@ -30,6 +30,8 @@
 #import "keychain/ckks/tests/MockCloudKit.h"
 #import "keychain/ckks/CKKSCondition.h"
 
+#if OCTAGON
+
 @interface CKKSAPSNotificationReceiver : NSObject <CKKSZoneUpdateReceiver>
 @property XCTestExpectation* expectation;
 @property void (^block)(CKRecordZoneNotification* notification);
                                             XCTAssertEqual(strongSelf.testZoneID, notification.recordZoneID, "Should have received a notification for the test zone");
                                         }];
 
-    CKKSCondition* registered = [apsr register:anr forZoneID:self.testZoneID];
+    CKKSCondition* registered = [apsr registerReceiver:anr forZoneID:self.testZoneID];
     XCTAssertEqual(0, [registered wait:1*NSEC_PER_SEC], "Registration should have completed within a second");
     APSIncomingMessage* message = [self messageForZoneID:self.testZoneID];
     XCTAssertNotNil(message, "Should have received a APSIncomingMessage");
                                             XCTAssertEqual(otherZoneID, notification.recordZoneID, "Should have received a notification for the test zone");
                                         }];
 
-    CKKSCondition* registered = [apsr register:anr forZoneID:self.testZoneID];
-    CKKSCondition* registered2 = [apsr register:anr2 forZoneID:otherZoneID];
+    CKKSCondition* registered = [apsr registerReceiver:anr forZoneID:self.testZoneID];
+    CKKSCondition* registered2 = [apsr registerReceiver:anr2 forZoneID:otherZoneID];
     XCTAssertEqual(0, [registered wait:1*NSEC_PER_SEC], "Registration should have completed within a second");
     XCTAssertEqual(0, [registered2 wait:1*NSEC_PER_SEC], "Registration should have completed within a second");
 
                                             XCTAssertNil(notification, "Should not have received a notification, since we weren't alive to receive it");
                                         }];
 
-    CKKSCondition* registered = [apsr register:anr forZoneID:self.testZoneID];
+    CKKSCondition* registered = [apsr registerReceiver:anr forZoneID:self.testZoneID];
     XCTAssertEqual(0, [registered wait:1*NSEC_PER_SEC], "Registration should have completed within a second");
 
     [self waitForExpectationsWithTimeout:5.0 handler:nil];
 }
 
 @end
+
+#endif
index 007d5eacfadf95d3eca39c3e1f973da2893ce97d..f0ad55f8ee14b368536e8115b48e484caedd1457 100644 (file)
@@ -44,7 +44,6 @@
 @interface CKKSCloudKitTests : XCTestCase
 
 @property NSOperationQueue *operationQueue;
-@property NSBlockOperation *ckksHoldOperation;
 @property CKContainer *container;
 @property CKDatabase *database;
 @property CKKSKeychainView *kcv;
     SecCKKSTestSetDisableSOS(true);
 
     self.operationQueue = [NSOperationQueue new];
-    self.ckksHoldOperation = [NSBlockOperation new];
-    [self.ckksHoldOperation addExecutionBlock:^{
-        secnotice("ckks", "CKKS testing hold released");
-    }];
 
     CKKSViewManager* manager = [[CKKSViewManager alloc] initWithContainerName:containerName
                                                                        usePCS:SecCKKSContainerUsePCS
@@ -97,8 +92,7 @@
                                               modifyRecordZonesOperationClass:[CKModifyRecordZonesOperation class]
                                                            apsConnectionClass:[APSConnection class]
                                                     nsnotificationCenterClass:[NSNotificationCenter class]
-                                                                notifierClass:[FakeCKKSNotifier class]
-                                                                    setupHold:self.ckksHoldOperation];
+                                                                notifierClass:[FakeCKKSNotifier class]];
     [CKKSViewManager resetManager:false setTo:manager];
 
     // Make a new fake keychain
     self.zoneName = @"keychain";
     self.zoneID = [[CKRecordZoneID alloc] initWithZoneName:self.zoneName ownerName:CKCurrentUserDefaultName];
     self.kcv = [[CKKSViewManager manager] findOrCreateView:@"keychain"];
-    [self.kcv.zoneSetupOperation addDependency: self.ckksHoldOperation];
 }
 
 - (void)tearDown {
 }
 
 - (void)startCKKSSubsystem {
-    if(self.ckksHoldOperation) {
-        [self.operationQueue addOperation: self.ckksHoldOperation];
-        self.ckksHoldOperation = nil;
-    }
+    // TODO: we removed this mechanism, but haven't tested to see if these tests still succeed
 }
 
 - (NSMutableDictionary *)fetchLocalItems {
index 65f064158e53ee7c9490d880cde850e79868b211..091500d19975136b6e29176f15d2072e0a6e5237 100644 (file)
     [self waitForExpectations: @[expectation] timeout:0.5];
 }
 
+-(void)testConditionChain {
+    CKKSCondition* chained = [[CKKSCondition alloc] init];
+    CKKSCondition* c = [[CKKSCondition alloc] initToChain: chained];
+
+    XCTAssertNotEqual(0, [chained wait:50*NSEC_PER_MSEC], "waiting on chained condition without fulfilling times out");
+    XCTAssertNotEqual(0, [c       wait:50*NSEC_PER_MSEC], "waiting on condition without fulfilling times out");
+
+    [c fulfill];
+    XCTAssertEqual(0, [c       wait:100*NSEC_PER_MSEC], "first wait after fulfill succeeds");
+    XCTAssertEqual(0, [chained wait:100*NSEC_PER_MSEC], "first chained wait after fulfill succeeds");
+    XCTAssertEqual(0, [c       wait:100*NSEC_PER_MSEC], "second wait after fulfill succeeds");
+    XCTAssertEqual(0, [chained wait:100*NSEC_PER_MSEC], "second chained wait after fulfill succeeds");
+}
+
 @end
index 9c0afc62a687cf7a8a83873db89a0ef326cfcd40..0ef3b1e57f6f4ee651959fc16d4f97356b92668e 100644 (file)
@@ -27,6 +27,8 @@
 #import <Foundation/Foundation.h>
 #import <XCTest/XCTest.h>
 
+#if OCTAGON
+
 static NSString* tablePath = nil;
 
 @interface SQLiteTests : XCTestCase
@@ -126,83 +128,10 @@ static NSString* tablePath = nil;
 
 @end
 
-@interface CKKSAnalyticsLoggerTests : CloudKitKeychainSyncingTestsBase
+@interface CKKSAnalyticsTests : CloudKitKeychainSyncingTestsBase
 @end
 
-@implementation CKKSAnalyticsLoggerTests
-
-- (void)testLoggingJSONGenerated
-{
-    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
-
-    // We expect a single record to be uploaded.
-    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
-
-    [self startCKKSSubsystem];
-
-    [self addGenericPassword: @"data" account: @"account-delete-me"];
-
-    OCMVerifyAllWithDelay(self.mockDatabase, 8);
-
-    NSError* error = nil;
-    NSData* json = [[CKKSAnalyticsLogger logger] getLoggingJSON:false error:&error];
-    XCTAssertNotNil(json, @"failed to generate logging json");
-    XCTAssertNil(error, @"encourntered error getting logging json: %@", error);
-
-    NSDictionary* dictionary = [NSJSONSerialization JSONObjectWithData:json options:0 error:&error];
-    XCTAssertNotNil(dictionary, @"failed to generate dictionary from json data");
-    XCTAssertNil(error, @"encountered error deserializing json: %@", error);
-    XCTAssertTrue([dictionary isKindOfClass:[NSDictionary class]], @"did not get the class we expected from json deserialization");
-
-    XCTAssertNotNil(dictionary[@"postTime"], @"Failed to get posttime");
-
-    NSArray *events = dictionary[@"events"];
-    XCTAssertNotNil(events, @"Failed to get events");
-    XCTAssert([events isKindOfClass:[NSArray class]], @"did not get the class we expected for events");
-
-
-    for (NSDictionary *event in events) {
-        XCTAssert([event isKindOfClass:[NSDictionary class]], @"did not get the class we expected for events");
-        XCTAssertNotNil(event[@"build"], @"Failed to get build in event");
-        XCTAssertNotNil(event[@"product"], @"Failed to get product in event");
-        XCTAssertNotNil(event[@"topic"], @"Failed to get topic in event");
-
-        NSString *eventtype = event[@"eventType"];
-        XCTAssertNotNil(eventtype, @"Failed to get eventType in eventtype");
-        XCTAssert([eventtype isKindOfClass:[NSString class]], @"did not get the class we expected for events");
-        if ([eventtype isEqualToString:@"ckksHealthSummary"]) {
-            XCTAssertNotNil(event[@"ckdeviceID"], @"Failed to get deviceID in event");
-        }
-    }
-}
-
-- (void)testSplunkDefaultTopicNameExists
-{
-    CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
-    dispatch_sync(logger.splunkLoggingQueue, ^{
-        XCTAssertNotNil(logger.splunkTopicName);
-    });
-}
-
-- (void)testSplunkDefaultBagURLExists
-{
-    CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
-    dispatch_sync(logger.splunkLoggingQueue, ^{
-        XCTAssertNotNil(logger.splunkBagURL);
-    });
-}
-
-// <rdar://problem/32983193> test_KeychainCKKS | CKKSTests failed: "Failed subtests: -[CloudKitKeychainSyncingTests testSplunkUploadURLExists]" [j71ap][CoreOSTigris15Z240][bats-e-27-204-1]
-#if 0
-- (void)testSplunkUploadURLExists
-{
-    CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
-    dispatch_sync(logger.splunkLoggingQueue, ^{
-        logger.ignoreServerDisablingMessages = YES;
-        XCTAssertNotNil(logger.splunkUploadURL);
-    });
-}
-#endif
+@implementation CKKSAnalyticsTests
 
 - (void)testLastSuccessfulSyncDate
 {
@@ -223,40 +152,6 @@ static NSString* tablePath = nil;
     XCTAssertTrue(timeIntervalSinceSyncDate >= 0.0 && timeIntervalSinceSyncDate <= 15.0, "Last sync date does not look like a reasonable one");
 }
 
-- (void)testExtraValuesToUploadToServer
-{
-    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
-    [self startCKKSSubsystem];
-    CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
-    [self.keychainZone addToZone: ckr];
-
-    // Trigger a notification (with hilariously fake data)
-    [self.keychainView notifyZoneChange:nil];
-
-    [[[self.keychainView waitForFetchAndIncomingQueueProcessing] completionHandlerDidRunCondition] wait:4 * NSEC_PER_SEC];
-
-    NSDictionary* extraValues = [[CKKSAnalyticsLogger logger] extraValuesToUploadToServer];
-    XCTAssertTrue([extraValues[@"inCircle"] boolValue]);
-    XCTAssertTrue([extraValues[@"keychain-TLKs"] boolValue]);
-    XCTAssertTrue([extraValues[@"keychain-inSyncA"] boolValue]);
-    XCTAssertTrue([extraValues[@"keychain-inSyncC"] boolValue]);
-    XCTAssertTrue([extraValues[@"keychain-IQNOE"] boolValue]);
-    XCTAssertTrue([extraValues[@"keychain-OQNOE"] boolValue]);
-    XCTAssertTrue([extraValues[@"keychain-inSync"] boolValue]);
-}
-
-- (void)testNilEventDoesNotCrashTheSystem
-{
-    CKKSAnalyticsLogger* logger = [CKKSAnalyticsLogger logger];
-    [logger logSuccessForEventNamed:nil];
-
-    NSData* json = nil;
-    NSError* error = nil;
-    XCTAssertNoThrow(json = [logger getLoggingJSON:false error:&error]);
-    XCTAssertNotNil(json, @"Failed to get JSON after logging nil event");
-    XCTAssertNil(error, @"Got error when grabbing JSON after logging nil event: %@", error);
-}
-
 - (void)testRaceToCreateLoggers
 {
     dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
@@ -273,22 +168,6 @@ static NSString* tablePath = nil;
     }
 }
 
-- (void)testSysdiagnoseDump
-{
-    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
-    [self startCKKSSubsystem];
-    CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
-    [self.keychainZone addToZone: ckr];
-
-    // Trigger a notification (with hilariously fake data)
-    [self.keychainView notifyZoneChange:nil];
-
-    [self.keychainView waitForFetchAndIncomingQueueProcessing];
-
-    NSError* error = nil;
-    NSString* sysdiagnose = [[CKKSAnalyticsLogger logger] getSysdiagnoseDumpWithError:&error];
-    XCTAssertNil(error, @"encountered an error grabbing CKKS analytics sysdiagnose: %@", error);
-    XCTAssertTrue(sysdiagnose.length > 0, @"failed to get a sysdiagnose from CKKS analytics");
-}
-
 @end
+
+#endif
index 3a2e1688d582856e310adf688e69d335c74054a6..2728f7c6fc7e5bb01a12cb81b00c8be1e375cf84 100644 (file)
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
-    // If TLK sharing is enabled, CKKS will save a share for itself
-    if(SecCKKSShareTLKs()) {
-        [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
-    }
-    
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
+
     [self addGenericPassword:@"data" account:@"first"];
     [self addGenericPassword:@"data" account:@"second"];
     [self addGenericPassword:@"data" account:@"third"];
index eff091fc7b2d45dc5f572ccd0c376b4e7bcb8a08..84de78f7fc7ffde319ab90689b74d9c5b31d7e7c 100644 (file)
 #include <dispatch/dispatch.h>
 #import <XCTest/XCTest.h>
 #import "keychain/ckks/CKKSNearFutureScheduler.h"
+#import "keychain/ckks/CKKSResultOperation.h"
 
 @interface CKKSNearFutureSchedulerTests : XCTestCase
-
+@property NSOperationQueue* operationQueue;
 @end
 
 @implementation CKKSNearFutureSchedulerTests
 
 - (void)setUp {
     [super setUp];
+
+    self.operationQueue = [[NSOperationQueue alloc] init];
 }
 
 - (void)tearDown {
     [super tearDown];
 }
 
-- (void)testOneShot {
+#pragma mark - Block-based tests
+
+- (void)testBlockOneShot {
     XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"];
 
     CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay:50*NSEC_PER_MSEC keepProcessAlive:true block:^{
@@ -51,7 +56,7 @@
     [self waitForExpectationsWithTimeout:1 handler:nil];
 }
 
-- (void)testOneShotDelay {
+- (void)testBlockOneShotDelay {
     XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
     toofastexpectation.inverted = YES;
 
@@ -71,7 +76,7 @@
     [self waitForExpectations: @[expectation] timeout:1];
 }
 
-- (void)testOneShotManyTrigger {
+- (void)testBlockOneShotManyTrigger {
     XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
     toofastexpectation.inverted = YES;
 
 }
 
 
-- (void)testMultiShot {
+- (void)testBlockMultiShot {
     XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
     first.assertForOverFulfill = NO;
 
 
     [scheduler trigger];
 
-    [self waitForExpectations: @[first] timeout:0.2];
+    [self waitForExpectations: @[first] timeout:0.4];
 
     [scheduler trigger];
     [scheduler trigger];
     [scheduler trigger];
 
-    [self waitForExpectations: @[second] timeout:0.2];
+    [self waitForExpectations: @[second] timeout:0.4];
 
     XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"];
     waitmore.inverted = YES;
-    [self waitForExpectations: @[waitmore] timeout: 0.2];
+    [self waitForExpectations: @[waitmore] timeout: 0.4];
 }
 
-- (void)testMultiShotDelays {
+- (void)testBlockMultiShotDelays {
     XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
     first.assertForOverFulfill = NO;
 
     second.expectedFulfillmentCount = 2;
     second.assertForOverFulfill = YES;
 
-    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" initialDelay: 50*NSEC_PER_MSEC continuingDelay:300*NSEC_PER_MSEC keepProcessAlive:false block:^{
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" initialDelay: 50*NSEC_PER_MSEC continuingDelay:600*NSEC_PER_MSEC keepProcessAlive:false block:^{
         [first fulfill];
         [longdelay fulfill];
         [second fulfill];
 
     [scheduler trigger];
 
-    [self waitForExpectations: @[first] timeout:0.2];
+    [self waitForExpectations: @[first] timeout:0.5];
 
     [scheduler trigger];
     [scheduler trigger];
     [scheduler trigger];
 
-    // longdelay should NOT be fulfilled twice in the first 0.3 seconds
-    [self waitForExpectations: @[longdelay] timeout:0.2];
+    // longdelay should NOT be fulfilled twice in the first 0.9 seconds
+    [self waitForExpectations: @[longdelay] timeout:0.4];
 
-    // But second should be fulfilled in the first 0.8 seconds
+    // But second should be fulfilled in the first 1.4 seconds
     [self waitForExpectations: @[second] timeout:0.5];
 
     XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"];
     [self waitForExpectations: @[waitmore] timeout: 0.2];
 }
 
-- (void)testCancel {
+- (void)testBlockCancel {
     XCTestExpectation *cancelexpectation = [self expectationWithDescription:@"FutureScheduler fired (after cancel)"];
     cancelexpectation.inverted = YES;
 
     [self waitForExpectations: @[cancelexpectation] timeout:0.2];
 }
 
-- (void)testDelayedNoShot {
+- (void)testBlockDelayedNoShot {
     XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
     toofastexpectation.inverted = YES;
 
     [self waitForExpectations: @[toofastexpectation] timeout:0.1];
 }
 
-- (void)testDelayedOneShot {
+- (void)testBlockDelayedOneShot {
     XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
     first.assertForOverFulfill = NO;
 
     [scheduler trigger];
 
     [self waitForExpectations: @[toofastexpectation] timeout:0.1];
-    [self waitForExpectations: @[first] timeout:0.2];
+    [self waitForExpectations: @[first] timeout:0.5];
 }
 
-- (void)testDelayedMultiShot {
+- (void)testBlockWaitedMultiShot {
     XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
     first.assertForOverFulfill = NO;
 
     }];
 
     [scheduler trigger];
-    [self waitForExpectations: @[first] timeout:0.2];
+    [self waitForExpectations: @[first] timeout:0.5];
 
     [scheduler waitUntil: 150*NSEC_PER_MSEC];
     [scheduler trigger];
     [self waitForExpectations: @[second] timeout:0.3];
 }
 
+#pragma mark - Operation-based tests
+
+- (NSOperation*)operationFulfillingExpectations:(NSArray<XCTestExpectation*>*)expectations {
+    return [NSBlockOperation named:@"test" withBlock:^{
+        for(XCTestExpectation* e in expectations) {
+            [e fulfill];
+        }
+    }];
+}
+
+- (void)addOperationFulfillingExpectations:(NSArray<XCTestExpectation*>*)expectations scheduler:(CKKSNearFutureScheduler*)scheduler {
+    NSOperation* op = [self operationFulfillingExpectations:expectations];
+    XCTAssertNotNil(scheduler.operationDependency, "Should be an operation dependency");
+    XCTAssertTrue([scheduler.operationDependency isPending], "operation dependency shouldn't have run yet");
+    [op addDependency:scheduler.operationDependency];
+    [self.operationQueue addOperation:op];
+}
+
+- (void)testOperationOneShot {
+    XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"];
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay:50*NSEC_PER_MSEC keepProcessAlive:true block:^{}];
+    [self addOperationFulfillingExpectations:@[expectation] scheduler:scheduler];
+
+    [scheduler trigger];
+
+    [self waitForExpectationsWithTimeout:1 handler:nil];
+}
+
+- (void)testOperationOneShotDelay {
+    XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
+    toofastexpectation.inverted = YES;
+
+    XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"];
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 200*NSEC_PER_MSEC keepProcessAlive:false block:^{}];
+    [self addOperationFulfillingExpectations:@[expectation,toofastexpectation] scheduler:scheduler];
+
+    [scheduler trigger];
+
+    // Make sure it waits at least 0.1 seconds
+    [self waitForExpectations: @[toofastexpectation] timeout:0.1];
+
+    // But finishes within 1.1s (total)
+    [self waitForExpectations: @[expectation] timeout:1];
+}
+
+- (void)testOperationOneShotManyTrigger {
+    XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
+    toofastexpectation.inverted = YES;
+
+    XCTestExpectation *expectation = [self expectationWithDescription:@"FutureScheduler fired"];
+    expectation.assertForOverFulfill = YES;
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 200*NSEC_PER_MSEC keepProcessAlive:true block:^{}];
+    [self addOperationFulfillingExpectations:@[expectation,toofastexpectation] scheduler:scheduler];
+
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+
+    // Make sure it waits at least 0.1 seconds
+    [self waitForExpectations: @[toofastexpectation] timeout:0.1];
+
+    // But finishes within .6s (total)
+    [self waitForExpectations: @[expectation] timeout:0.5];
+
+    // Ensure we don't get called again in the next 0.3 s
+    XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"];
+    waitmore.inverted = YES;
+    [self waitForExpectations: @[waitmore] timeout: 0.3];
+}
+
+
+- (void)testOperationMultiShot {
+    XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
+
+    XCTestExpectation *second = [self expectationWithDescription:@"FutureScheduler fired (two)"];
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 100*NSEC_PER_MSEC keepProcessAlive:false block:^{}];
+
+    [self addOperationFulfillingExpectations:@[first] scheduler:scheduler];
+
+    [scheduler trigger];
+
+    [self waitForExpectations: @[first] timeout:0.2];
+
+    [self addOperationFulfillingExpectations:@[second] scheduler:scheduler];
+
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+
+    [self waitForExpectations: @[second] timeout:0.2];
+
+    XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"];
+    waitmore.inverted = YES;
+    [self waitForExpectations: @[waitmore] timeout: 0.2];
+}
+
+- (void)testOperationMultiShotDelays {
+    XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
+
+    XCTestExpectation *longdelay = [self expectationWithDescription:@"FutureScheduler fired (long delay expectation)"];
+    longdelay.inverted = YES;
+    XCTestExpectation *second = [self expectationWithDescription:@"FutureScheduler fired (two)"];
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" initialDelay: 50*NSEC_PER_MSEC continuingDelay:300*NSEC_PER_MSEC keepProcessAlive:false block:^{}];
+
+    [self addOperationFulfillingExpectations:@[first] scheduler:scheduler];
+
+    [scheduler trigger];
+
+    [self waitForExpectations: @[first] timeout:0.2];
+
+    [self addOperationFulfillingExpectations:@[second,longdelay] scheduler:scheduler];
+
+    [scheduler trigger];
+    [scheduler trigger];
+    [scheduler trigger];
+
+    // longdelay shouldn't be fulfilled in the first 0.2 seconds
+    [self waitForExpectations: @[longdelay] timeout:0.2];
+
+    // But second should be fulfilled in the next 0.5 seconds
+    [self waitForExpectations: @[second] timeout:0.5];
+
+    XCTestExpectation* waitmore = [self expectationWithDescription:@"waiting"];
+    waitmore.inverted = YES;
+    [self waitForExpectations: @[waitmore] timeout: 0.2];
+}
+
+- (void)testOperationCancel {
+    XCTestExpectation *cancelexpectation = [self expectationWithDescription:@"FutureScheduler fired (after cancel)"];
+    cancelexpectation.inverted = YES;
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 100*NSEC_PER_MSEC keepProcessAlive:true block:^{}];
+
+    [self addOperationFulfillingExpectations:@[cancelexpectation] scheduler:scheduler];
+
+    [scheduler trigger];
+    [scheduler cancel];
+
+    // Make sure it does not fire in 0.5 s
+    [self waitForExpectations: @[cancelexpectation] timeout:0.2];
+}
+
+- (void)testOperationDelayedNoShot {
+    XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
+    toofastexpectation.inverted = YES;
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 10*NSEC_PER_MSEC keepProcessAlive:false block:^{}];
+    [self addOperationFulfillingExpectations:@[toofastexpectation] scheduler:scheduler];
+
+    // Tell the scheduler to wait, but don't trigger it. It shouldn't fire.
+    [scheduler waitUntil: 50*NSEC_PER_MSEC];
+
+    [self waitForExpectations: @[toofastexpectation] timeout:0.1];
+}
+
+- (void)testOperationDelayedOneShot {
+    XCTestExpectation *first = [self expectationWithDescription:@"FutureScheduler fired (one)"];
+    first.assertForOverFulfill = NO;
+
+    XCTestExpectation *toofastexpectation = [self expectationWithDescription:@"FutureScheduler fired (too soon)"];
+    toofastexpectation.inverted = YES;
+
+    CKKSNearFutureScheduler* scheduler = [[CKKSNearFutureScheduler alloc] initWithName: @"test" delay: 10*NSEC_PER_MSEC keepProcessAlive:false block:^{}];
+    [self addOperationFulfillingExpectations:@[first,toofastexpectation] scheduler:scheduler];
+
+    [scheduler waitUntil: 150*NSEC_PER_MSEC];
+    [scheduler trigger];
+
+    [self waitForExpectations: @[toofastexpectation] timeout:0.1];
+    [self waitForExpectations: @[first] timeout:0.5];
+}
+
 @end
index 0538f6707bcd74ebb183d0e2e64aa766a91bd05e..819bbf4416cffed176d0e11aa5d85a614817373f 100644 (file)
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
 #include <Security/SecureObjectSync/SOSCloudCircle.h>
+#include <Security/SecureObjectSync/SOSAccountPriv.h>
 #include <Security/SecureObjectSync/SOSAccount.h>
 #include <Security/SecureObjectSync/SOSInternal.h>
 #include <Security/SecureObjectSync/SOSFullPeerInfo.h>
+#pragma clang diagnostic pop
+
 #include <Security/SecKey.h>
 #include <Security/SecKeyPriv.h>
 #pragma clang diagnostic pop
     self.zones[self.engramZoneID] = self.engramZone;
     self.engramView = [[CKKSViewManager manager] findView:@"Engram"];
     XCTAssertNotNil(self.engramView, "CKKSViewManager created the Engram view");
+    [self.ckksZones addObject:self.engramZoneID];
 
     self.manateeZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Manatee" ownerName:CKCurrentUserDefaultName];
     self.manateeZone = [[FakeCKZone alloc] initZone: self.manateeZoneID];
     self.zones[self.manateeZoneID] = self.manateeZone;
     self.manateeView = [[CKKSViewManager manager] findView:@"Manatee"];
     XCTAssertNotNil(self.manateeView, "CKKSViewManager created the Manatee view");
+    [self.ckksZones addObject:self.manateeZoneID];
 
     self.autoUnlockZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"AutoUnlock" ownerName:CKCurrentUserDefaultName];
     self.autoUnlockZone = [[FakeCKZone alloc] initZone: self.autoUnlockZoneID];
     self.zones[self.autoUnlockZoneID] = self.autoUnlockZone;
     self.autoUnlockView = [[CKKSViewManager manager] findView:@"AutoUnlock"];
     XCTAssertNotNil(self.autoUnlockView, "CKKSViewManager created the AutoUnlock view");
+    [self.ckksZones addObject:self.autoUnlockZoneID];
 
     self.healthZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"Health" ownerName:CKCurrentUserDefaultName];
     self.healthZone = [[FakeCKZone alloc] initZone: self.healthZoneID];
     self.zones[self.healthZoneID] = self.healthZone;
     self.healthView = [[CKKSViewManager manager] findView:@"Health"];
     XCTAssertNotNil(self.healthView, "CKKSViewManager created the Health view");
+    [self.ckksZones addObject:self.healthZoneID];
 
     self.applepayZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"ApplePay" ownerName:CKCurrentUserDefaultName];
     self.applepayZone = [[FakeCKZone alloc] initZone: self.healthZoneID];
     self.zones[self.applepayZoneID] = self.applepayZone;
     self.applepayView = [[CKKSViewManager manager] findView:@"ApplePay"];
     XCTAssertNotNil(self.applepayView, "CKKSViewManager created the ApplePay view");
+    [self.ckksZones addObject:self.applepayZoneID];
 }
 
 + (void)tearDown {
     self.accountStatus = CKAccountStatusNoAccount;
     [self startCKKSSubsystem];
 
-    [self.engramView cancelAllOperations];
+    [self.engramView halt];
     [self.engramView waitUntilAllOperationsAreFinished];
     self.engramView = nil;
 
-    [self.manateeView cancelAllOperations];
+    [self.manateeView halt];
     [self.manateeView waitUntilAllOperationsAreFinished];
     self.manateeView = nil;
 
-    [self.autoUnlockView cancelAllOperations];
+    [self.autoUnlockView halt];
     [self.autoUnlockView waitUntilAllOperationsAreFinished];
     self.autoUnlockView = nil;
 
-    [self.healthView cancelAllOperations];
+    [self.healthView halt];
     [self.healthView waitUntilAllOperationsAreFinished];
     self.healthView = nil;
 
-    [self.applepayView cancelAllOperations];
+    [self.applepayView halt];
     [self.applepayView waitUntilAllOperationsAreFinished];
     self.applepayView = nil;
 
 }
 
 -(void)saveFakeKeyHierarchiesToLocalDatabase {
-    [self createAndSaveFakeKeyHierarchy: self.engramZoneID];
-    [self createAndSaveFakeKeyHierarchy: self.manateeZoneID];
-    [self createAndSaveFakeKeyHierarchy: self.autoUnlockZoneID];
-    [self createAndSaveFakeKeyHierarchy: self.healthZoneID];
-    [self createAndSaveFakeKeyHierarchy: self.applepayZoneID];
+    for(CKRecordZoneID* zoneID in self.ckksZones) {
+        [self createAndSaveFakeKeyHierarchy: zoneID];
+    }
 }
 
 -(void)testAddEngramManateeItems {
     [self saveFakeKeyHierarchiesToLocalDatabase]; // Make life easy for this test.
     [self startCKKSSubsystem];
 
+    for(CKRecordZoneID* zoneID in self.ckksZones) {
+        [self expectCKKSTLKSelfShareUpload:zoneID];
+    }
+
     [self waitForKeyHierarchyReadinesses];
 
     [self findGenericPassword:@"account-delete-me" expecting:errSecItemNotFound];
         tempPath = [[[[NSFileManager defaultManager] temporaryDirectory] URLByAppendingPathComponent:@"PiggyPacket"] path];
 
     });
-    NSLog(@"using temp path: %@", tempPath);
     return tempPath;
 }
 
     [self putFakeKeyHierachiesInCloudKit];
     [self saveTLKsToKeychain];
 
+    for(CKRecordZoneID* zoneID in self.ckksZones) {
+        [self expectCKKSTLKSelfShareUpload:zoneID];
+    }
     [self startCKKSSubsystem];
 
     [self waitForKeyHierarchyReadinesses];
     NSArray<NSDictionary *>* sortedTLKs = SOSAccountSortTLKS(tlks);
     XCTAssertNotNil(sortedTLKs, "sortedTLKs not set");
 
-    NSLog(@"TLKs: %@", tlks);
-    NSLog(@"sortedTLKs: %@", sortedTLKs);
-
     NSArray<NSString *> *expectedOrder = @[ @"11111111", @"22222222", @"33333333", @"44444444", @"55555555"];
     [sortedTLKs enumerateObjectsUsingBlock:^(NSDictionary *tlk, NSUInteger idx, BOOL * _Nonnull stop) {
         NSString *uuid = tlk[@"acct"];
     NSDictionary* piggyTLKS = [self SOSPiggyBackCopyFromKeychain];
     [self SOSPiggyBackAddToKeychain:piggyTLKS];
     [self deleteTLKMaterialsFromKeychain];
-    
+
+    // The CKKS subsystem should write a TLK Share for each view
+    for(CKRecordZoneID* zoneID in self.ckksZones) {
+        [self expectCKKSTLKSelfShareUpload:zoneID];
+    }
+
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
-    
-    // The CKKS subsystem should not try to write anything to the CloudKit database while it's accepting the keys
+
     [self.manateeView waitForKeyHierarchyReadiness];
     
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
     [self startCKKSSubsystem];
     
     // The CKKS subsystem should not try to write anything to the CloudKit database.
-    sleep(1);
-    
+    XCTAssertEqual(0, [self.manateeView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:400*NSEC_PER_SEC], "CKKS entered waitfortlk");
+
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
     
-    // Now, save the TLK to the keychain (to simulate it coming in later via piggybacking).
+    // Now, save the TLKs to the keychain (to simulate them coming in later via piggybacking).
+    for(CKRecordZoneID* zoneID in self.ckksZones) {
+        [self expectCKKSTLKSelfShareUpload:zoneID];
+    }
+
     [self SOSPiggyBackAddToKeychain:piggyData];
     [self waitForKeyHierarchyReadinesses];
     
index d3fa83b44ca9ef75309fd47c2d94c946cda00675..408c0772319f9361a8b7e37b8cc085efed2f4928 100644 (file)
@@ -24,6 +24,8 @@
 #if OCTAGON
 
 #import <XCTest/XCTest.h>
+#import <Security/Security.h>
+#import <Security/SecItemPriv.h>
 #import "CloudKitMockXCTest.h"
 
 #import "keychain/ckks/CKKS.h"
 
     attrs = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
     CFDictionarySetValue( attrs, kSecClass, kSecClassGenericPassword );
-    CFDictionarySetValue( attrs, kSecAttrAccessible, kSecAttrAccessibleAlways );
+    CFDictionarySetValue( attrs, kSecAttrAccessible, kSecAttrAccessibleAlwaysPrivate );
     CFDictionarySetValue( attrs, kSecAttrLabel, CFSTR( "TestLabel" ) );
     CFDictionarySetValue( attrs, kSecAttrDescription, CFSTR( "TestDescription" ) );
     CFDictionarySetValue( attrs, kSecAttrAccount, CFSTR( "TestAccount" ) );
index 4dc08d1e001e4fccc3e01fd52788e83927dfa53c..ad867df6e6dc9ef5f4771f51107f7cb5641d5a12 100644 (file)
@@ -61,8 +61,8 @@
     self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassAPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey] = oldClassAKey;
     self.keychainZoneKeys.currentClassAPointer.currentKeyUUID = oldClassAKey.recordID.recordName;
 
-    // CKKS should then fix the pointers, but not update any keys
-    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 zoneID:self.keychainZoneID];
+    // CKKS should then fix the pointers and give itself a TLK share, but not update any keys
+    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 1 zoneID:self.keychainZoneID];
 
     // And then upload the record as normal
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
@@ -99,8 +99,8 @@
     self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey] = oldClassCKey;
     self.keychainZoneKeys.currentClassCPointer.currentKeyUUID = oldClassCKey.recordID.recordName;
 
-    // CKKS should then fix the pointers, but not update any keys
-    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 zoneID:self.keychainZoneID];
+    // CKKS should then fix the pointers and its TLK shares, but not update any keys
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:1 tlkShareRecords:1 zoneID:self.keychainZoneID];
 
     // And then upload the record as normal
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
     // Test starts with a good key hierarchy in our fake CloudKit, and the TLK already arrived.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
 
     CKReference* oldClassCKey = self.keychainZone.currentDatabase[self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID][SecCKRecordParentKeyRefKey];
 
 
     [self.keychainView notifyZoneChange:nil];
 
-    // CKKS should then fix the pointers, but not update any keys
-    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 1 tlkShareRecords: 0 zoneID:self.keychainZoneID];
+    // CKKS should then fix the pointers and give itself a new TLK share record, but not update any keys
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:1 tlkShareRecords:1 zoneID:self.keychainZoneID];
     [self.keychainView notifyZoneChange:nil];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Test starts with a good key hierarchy in our fake CloudKit, and the TLK already arrived.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
 
     CKRecordID* classCPointerRecordID = self.keychainZoneKeys.currentClassCPointer.storedCKRecord.recordID;
 
     }];
 
     // CKKS should try to fix the pointers, but be rejected (since someone else has already fixed them)
-    // It should not try again, because someone already fixed them
+    // It should not try to modify the pointers again, but it should give itself the new TLK
     [self expectCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self.keychainView notifyZoneChange:nil];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     CKReference* oldClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
 
     [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     // Spin up CKKS subsystem.
                           checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
     [self addGenericPassword: @"data" account: @"account-delete-me"];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
 
     // Now, break the class C pointer, but don't tell CKKS
     CKReference* newClassCKey = self.keychainZone.currentDatabase[classCPointerRecordID][SecCKRecordParentKeyRefKey];
index a61d49973c24eca88e9c87889ec4c957efbd1907..665b5e04ac3a377bd58e137069aabf9242b11c2b 100644 (file)
 
     key = [share recoverTLK:self.remotePeer trustedPeers:[NSSet set] error:&error];
     XCTAssertNil(key, "No key should have been extracted when no trusted peers exist");
-    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key");
+    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key with no trusted peers");
     error = nil;
 
     key = [share recoverTLK:self.remotePeer2 trustedPeers:peers error:&error];
     XCTAssertNil(key, "No key should have been extracted when using the wrong key");
-    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key");
+    XCTAssertNotNil(error, "Should have produced an error when failing to extract with the wrong key");
     error = nil;
 
     CKKSTLKShare* shareSignature = [share copy];
     shareSignature.signature = [NSMutableData dataWithLength:shareSignature.signature.length];
     key = [shareSignature recoverTLK:self.remotePeer trustedPeers:peers error:&error];
     XCTAssertNil(key, "No key should have been extracted when signature fails to verify");
-    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key");
+    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key with an invalid signature");
     error = nil;
 
     CKKSTLKShare* shareUUID = [share copy];
     shareUUID.tlkUUID = [[NSUUID UUID] UUIDString];
     key = [shareUUID recoverTLK:self.remotePeer trustedPeers:peers error:&error];
     XCTAssertNil(key, "No key should have been extracted when uuid has changed");
-    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key");
+    XCTAssertNotNil(error, "Should have produced an error when failing to extract a key after uuid has changed");
     error = nil;
 }
 
     [share2 saveToDatabase:&error];
     XCTAssertNil(error, "No error saving share2 to database");
 
-    /*
-     * DISABLE FOR NOW:
-     *  a bad rebase made the implementation of this function not be avilable
-     *  yet
     CKKSTLKShare* loadedShare2 = [CKKSTLKShare tryFromDatabaseFromCKRecordID:record.recordID error:&error];
     XCTAssertNil(error, "No error loading loadedShare2 from database");
     XCTAssertNotNil(loadedShare2, "Should have received a CKKSTLKShare from the database");
 
     XCTAssert([loadedShare2 verifySignature:loadedShare2.signature verifyingPeer:self.localPeer error:&error], "Signature with extra data should verify after save/load");
-    */
 }
 
 @end
index 71ce090d6d175ca607897415ce213a530236e5c9..2639a5a985ec1fdac012c3842c7a8e60cbc8063d 100644 (file)
@@ -50,7 +50,6 @@
 
 - (void)setUp {
     [super setUp];
-    SecCKKSSetShareTLKs(true);
 
     self.remotePeer1 = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"remote-peer1"
                                                     encryptionKey:[[SFECKeyPair alloc] initRandomKeyPairWithSpecifier:[[SFECKeySpecifier alloc] initWithCurve:SFEllipticCurveNistp384]]
@@ -75,8 +74,6 @@
     self.untrustedPeer = nil;
 
     [super tearDown];
-
-    SecCKKSSetShareTLKs(false);
 }
 
 - (void)testAcceptExistingTLKSharedKeyHierarchy {
     // Test also starts with the TLK shared to all trusted peers from peer1
     [self putTLKSharesInCloudKit:self.keychainZoneKeys.tlk from:self.remotePeer1 zoneID:self.keychainZoneID];
 
-    // Because 33710924 didn't make it backwards in time, this test is fragile on Chipmunk/Cinar.
     self.aksLockState = true;
     [self.lockStateTracker recheck];
 
 }
 
 - (void)testUploadTLKSharesForExistingHierarchyOnRestart {
-    // Turn off TLK sharing, and get situated
-    SecCKKSSetShareTLKs(false);
-
+    // Bring up CKKS. It'll upload a few TLK Shares, but we'll delete them to get into state
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
+
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
     [self startCKKSSubsystem];
 
     XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:10*NSEC_PER_SEC], "Key state should become ready");
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+
+    // Now, delete all the TLK Shares, so CKKS will upload them again
+    [self.keychainView dispatchSync:^bool {
+        NSError* error = nil;
+        [CKKSTLKShare deleteAll:self.keychainZoneID error:&error];
+        XCTAssertNil(error, "Shouldn't be an error deleting all TLKShares");
 
-    // Turn TLK sharing back on, and restart. We expect an upload of 3 TLK shares.
-    SecCKKSSetShareTLKs(true);
+        NSArray<CKRecord*>* records = [self.zones[self.keychainZoneID].currentDatabase allValues];
+        for(CKRecord* record in records) {
+            if([record.recordType isEqualToString:SecCKRecordTLKShareType]) {
+                [self.zones[self.keychainZoneID] deleteFromHistory:record.recordID];
+            }
+        }
+
+        return true;
+    }];
+
+    // Restart. We expect an upload of 3 TLK shares.
     [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:3 zoneID:self.keychainZoneID];
     self.keychainView = [self.injectedManager restartZone: self.keychainZoneID.zoneName];
 
     // The CKKS subsystem should now accept the key, and share the TLK back to itself
     [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
 
-    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:500*NSEC_PER_SEC], "Key state should become ready");
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "Key state should become ready");
 
     // And use it as well
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
     [self.keychainView waitUntilAllOperationsAreFinished];
 }
 
+- (void)testFillInMissingPeerSharesAfterUnlock {
+    // step 1: add a new peer; we should share the TLK with them
+    // start with no trusted peers
+    [self.currentPeers removeAllObjects];
+
+    [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
+    [self startCKKSSubsystem];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], @"Key state should become 'ready'");
+
+    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
+                          checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
+    [self addGenericPassword: @"data" account: @"account-delete-me"];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    // Now, lock.
+    self.aksLockState = true;
+    [self.lockStateTracker recheck];
+
+    // New peer arrives! This can't actually happen (since we have to be unlocked to accept a new peer), but this will exercise CKKS
+    [self.currentPeers addObject:self.remotePeer1];
+    [self.injectedManager sendTrustedPeerSetChangedUpdate];
+
+    // CKKS should notice that it has things to do...
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:8*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
+
+    // And do them.
+    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
+    self.aksLockState = false;
+    [self.lockStateTracker recheck];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    // and return to ready
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], @"Key state should become 'ready'");
+}
+
+- (void)testAddItemDuringNewTLKSharesOnTrustSetAddition {
+    // step 1: add a new peer; we should share the TLK with them
+    // start with no trusted peers
+    [self.currentPeers removeAllObjects];
+
+    [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
+    [self startCKKSSubsystem];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+
+    // Hold the TLK share modification
+    [self holdCloudKitModifications];
+
+    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:self.keychainZoneID];
+    [self.currentPeers addObject:self.remotePeer1];
+    [self.injectedManager sendTrustedPeerSetChangedUpdate];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    // While CloudKit is hanging the write, add an item
+    [self addGenericPassword: @"data" account: @"account-delete-me"];
+
+    // After that returns, release the write. CKKS should upload the new item
+    [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID
+                          checkItem:[self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
+    [self releaseCloudKitModificationHold];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+}
+
 - (void)testSendNewTLKSharesOnTrustSetRemoval {
     // Not implemented. Trust set removal demands a key roll, but let's not get ahead of ourselves...
 }
index 506877287eabb023e1a027b6e0dd9c28b6893282..42262a6831d285ecd9e55c4bcb6be96b4291e38e 100644 (file)
 #import <Foundation/Foundation.h>
 #import "keychain/ckks/tests/CKKSTests.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 @interface CloudKitKeychainSyncingTests (APITests)
 
-- (BOOL (^) (CKRecord*)) checkPCSFieldsBlock: (CKRecordZoneID*) zoneID
-                        PCSServiceIdentifier:(NSNumber*)servIdentifier
-                                PCSPublicKey:(NSData*)publicKey
-                           PCSPublicIdentity:(NSData*)publicIdentity;
+- (BOOL (^)(CKRecord*))checkPCSFieldsBlock:(CKRecordZoneID*)zoneID
+                      PCSServiceIdentifier:(NSNumber*)servIdentifier
+                              PCSPublicKey:(NSData*)publicKey
+                         PCSPublicIdentity:(NSData*)publicIdentity;
 
--(NSMutableDictionary*)pcsAddItemQuery:(NSString*)account
-                                  data:(NSData*)data
-                     serviceIdentifier:(NSNumber*)serviceIdentifier
-                             publicKey:(NSData*)publicKey
-                        publicIdentity:(NSData*)publicIdentity;
+- (NSMutableDictionary*)pcsAddItemQuery:(NSString*)account
+                                   data:(NSData*)data
+                      serviceIdentifier:(NSNumber*)serviceIdentifier
+                              publicKey:(NSData*)publicKey
+                         publicIdentity:(NSData*)publicIdentity;
 
--(NSDictionary*)pcsAddItem:(NSString*)account
-                      data:(NSData*)data
-         serviceIdentifier:(NSNumber*)serviceIdentifier
-                 publicKey:(NSData*)publicKey
-            publicIdentity:(NSData*)publicIdentity
-             expectingSync:(bool)expectingSync;
+- (NSDictionary*)pcsAddItem:(NSString*)account
+                       data:(NSData*)data
+          serviceIdentifier:(NSNumber*)serviceIdentifier
+                  publicKey:(NSData*)publicKey
+             publicIdentity:(NSData*)publicIdentity
+              expectingSync:(bool)expectingSync;
 
 
 @end
+
+NS_ASSUME_NONNULL_END
index 2cf4f790af02c651c2bebcaa987b6b8eece817b9..48af3b78d8288b8ef704bd7183e671e90333c19e 100644 (file)
     [self startCKKSSubsystem];
     [self.keychainView waitForFetchAndIncomingQueueProcessing];
 
-    // Due to item UUID selection, this item will be added with UUID DD7C2F9B-B22D-3B90-C299-E3B48174BFA3.
+    // Due to item UUID selection, this item will be added with UUID 50184A35-4480-E8BA-769B-567CF72F1EC0.
     // Add it to CloudKit first!
-    CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3"];
+    CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"50184A35-4480-E8BA-769B-567CF72F1EC0"];
     [self.keychainZone addToZone: ckr];
 
 
                                     (id)kSecAttrAccount : @"testaccount",
                                     (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
                                     (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName, // @ fake view hint for fake view
                                     } mutableCopy];
 
     XCTestExpectation* blockExpectation = [self expectationWithDescription: @"callback occurs"];
     self.silentFetchesAllowed = false;
     [self startCKKSSubsystem];
 
+    XCTAssertEqual(0, [self.keychainView.loggedOut wait:2*NSEC_PER_SEC], "CKKS should positively log out");
+
+    NSMutableDictionary* query = [@{
+                                    (id)kSecClass : (id)kSecClassGenericPassword,
+                                    (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
+                                    (id)kSecAttrAccessible: (id)kSecAttrAccessibleAfterFirstUnlock,
+                                    (id)kSecAttrAccount : @"testaccount",
+                                    (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+                                    (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
+                                    } mutableCopy];
+
+    XCTestExpectation* blockExpectation = [self expectationWithDescription: @"callback occurs"];
+
+    XCTAssertEqual(errSecSuccess, _SecItemAddAndNotifyOnSync((__bridge CFDictionaryRef) query, NULL, ^(bool didSync, CFErrorRef error) {
+        XCTAssertFalse(didSync, "Item did not sync (with no iCloud account)");
+        XCTAssertNotNil((__bridge NSError*)error, "Error exists syncing item while logged out");
+
+        [blockExpectation fulfill];
+    }), @"_SecItemAddAndNotifyOnSync succeeded");
+
+    [self waitForExpectationsWithTimeout:5.0 handler:nil];
+}
+
+- (void)testAddAndNotifyOnSyncAccountStatusUnclear {
+    // Test starts with nothing in database, but CKKS hasn't been told we've logged out yet.
+    // We expect no CKKS operations.
+    self.accountStatus = CKAccountStatusNoAccount;
+    self.silentFetchesAllowed = false;
+
     NSMutableDictionary* query = [@{
                                     (id)kSecClass : (id)kSecClassGenericPassword,
                                     (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
         [blockExpectation fulfill];
     }), @"_SecItemAddAndNotifyOnSync succeeded");
 
+    // And now, allow CKKS to discover we're logged out
+    [self startCKKSSubsystem];
+    XCTAssertEqual(0, [self.keychainView.loggedOut wait:2*NSEC_PER_SEC], "CKKS should positively log out");
+
     [self waitForExpectationsWithTimeout:5.0 handler:nil];
 }
 
     // Test starts with a key hierarchy in cloudkit and the TLK having arrived
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
 
     // But block CloudKit fetches (so the key hierarchy won't be ready when we add this new item)
     [self holdCloudKitFetches];
 
     [self startCKKSSubsystem];
+    XCTAssertEqual(0, [self.keychainView.loggedIn wait:2*NSEC_PER_SEC], "CKKS should log in");
     [self.keychainView.viewSetupOperation waitUntilFinished];
 
     NSMutableDictionary* query = [@{
 }
 
 - (void)testPCSUnencryptedFieldsAdd {
-
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
 
     [self startCKKSSubsystem];
-    [self.keychainView waitUntilAllOperationsAreFinished];
+    [self.keychainView waitForKeyHierarchyReadiness];
 
     NSNumber* servIdentifier = @3;
     NSData* publicKey = [@"asdfasdf" dataUsingEncoding:NSUTF8StringEncoding];
                                     (id)kSecAttrPCSPlaintextServiceIdentifier : servIdentifier,
                                     (id)kSecAttrPCSPlaintextPublicKey : publicKey,
                                     (id)kSecAttrPCSPlaintextPublicIdentity : publicIdentity,
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName, // allows a CKKSScanOperation to find this item
                                     } mutableCopy];
 
     XCTAssertEqual(errSecSuccess, SecItemAdd((__bridge CFDictionaryRef) query, NULL), @"SecItemAdd succeeded");
     XCTAssertEqualObjects(itemAttributes[(id)kSecAttrPCSPlaintextPublicIdentity],    publicIdentity, "public identity exists");
 
     // Find the item record in CloudKit. Since we're using kSecAttrDeriveSyncIDFromItemAttributes,
-    // the record ID is likely DD7C2F9B-B22D-3B90-C299-E3B48174BFA3
+    // the record ID is likely 50184A35-4480-E8BA-769B-567CF72F1EC0
     [self waitForCKModifications];
-    CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID];
+    CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID];
     CKRecord* record = self.keychainZone.currentDatabase[recordID];
     XCTAssertNotNil(record, "Found record in CloudKit at expected UUID");
 
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
 
     [self startCKKSSubsystem];
-    [self.keychainView waitUntilAllOperationsAreFinished];
+    [self.keychainView waitForKeyHierarchyReadiness];
 
     NSNumber* servIdentifier = @3;
     NSData* publicKey = [@"asdfasdf" dataUsingEncoding:NSUTF8StringEncoding];
                                     (id)kSecAttrPCSPlaintextServiceIdentifier : servIdentifier,
                                     (id)kSecAttrPCSPlaintextPublicKey : publicKey,
                                     (id)kSecAttrPCSPlaintextPublicIdentity : publicIdentity,
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName, // allows a CKKSScanOperation to find this item
                                     } mutableCopy];
 
     XCTAssertEqual(errSecSuccess, SecItemAdd((__bridge CFDictionaryRef) query, NULL), @"SecItemAdd succeeded");
     XCTAssertEqualObjects(itemAttributes[(id)kSecAttrPCSPlaintextPublicIdentity],    newPublicIdentity,    "public identity exists");
 
     // Find the item record in CloudKit. Since we're using kSecAttrDeriveSyncIDFromItemAttributes,
-    // the record ID is likely DD7C2F9B-B22D-3B90-C299-E3B48174BFA3
+    // the record ID is likely 50184A35-4480-E8BA-769B-567CF72F1EC0
     [self waitForCKModifications];
-    CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID];
+    CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID];
     CKRecord* record = self.keychainZone.currentDatabase[recordID];
     XCTAssertNotNil(record, "Found record in CloudKit at expected UUID");
 
                                     (id)kSecAttrPCSPlaintextServiceIdentifier : servIdentifier,
                                     (id)kSecAttrPCSPlaintextPublicKey : publicKey,
                                     (id)kSecAttrPCSPlaintextPublicIdentity : publicIdentity,
+                                    (id)kSecAttrSyncViewHint : self.keychainView.zoneName, // fake, for CKKSScanOperation
                                     } mutableCopy];
 
     XCTAssertEqual(errSecSuccess, SecItemAdd((__bridge CFDictionaryRef) query, NULL), @"SecItemAdd succeeded");
     [self waitForCKModifications];
 
     // Find the item record in CloudKit. Since we're using kSecAttrDeriveSyncIDFromItemAttributes,
-    // the record ID is likely DD7C2F9B-B22D-3B90-C299-E3B48174BFA3
-    CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName: @"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID];
+    // the record ID is likely 50184A35-4480-E8BA-769B-567CF72F1EC0
+    CKRecordID* recordID = [[CKRecordID alloc] initWithRecordName: @"50184A35-4480-E8BA-769B-567CF72F1EC0" zoneID:self.keychainZoneID];
     CKRecord* record = self.keychainZone.currentDatabase[recordID];
     XCTAssertNotNil(record, "Found record in CloudKit at expected UUID");
 
 -(void)testResetLocal {
     // Test starts with nothing in database, but one in our fake CloudKit.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // Spin up CKKS subsystem.
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
     self.silentFetchesAllowed = false;
 
-    // Test starts with local TLK and key hierarhcy in our fake cloudkit
+    // Test starts with local TLK and key hierarchy in our fake cloudkit
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
 
+    XCTAssertEqual(0, [self.keychainView.loggedOut wait:500*NSEC_PER_MSEC], "Should have been told of a 'logout' event on startup");
+
     NSData* changeTokenData = [[[NSUUID UUID] UUIDString] dataUsingEncoding:NSUTF8StringEncoding];
     CKServerChangeToken* changeToken = [[CKServerChangeToken alloc] initWithData:changeTokenData];
     [self.keychainView dispatchSync: ^bool{
 
     dispatch_semaphore_t resetSemaphore = dispatch_semaphore_create(0);
     [self.injectedManager rpcResetLocal:nil reply:^(NSError* result) {
-        XCTAssertNil(result, "no error resetting cloudkit");
+        XCTAssertNil(result, "no error resetting local");
         secnotice("ckks", "Received a rpcResetLocal callback");
         dispatch_semaphore_signal(resetSemaphore);
     }];
     }];
 
     // Now log in, and see what happens! It should re-fetch, pick up the old key hierarchy, and use it
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     self.silentFetchesAllowed = true;
     self.circleStatus = kSOSCCInCircle;
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
 -(void)testResetCloudKitZone {
     // Test starts with nothing in database, but one in our fake CloudKit.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // Spin up CKKS subsystem.
 
     // Now, reset everything. The outgoingOp should get cancelled.
     // We expect a key hierarchy upload, and then the class C item upload
-    [self expectCKModifyKeyRecords: 3 currentKeyPointerRecords: 3 tlkShareRecords:3 zoneID:self.keychainZoneID];
+    [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
                           checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
 
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
 
-    [self.keychainView.viewSetupOperation waitUntilFinished];
-    // Reset setup, since that's the most likely state to be in (33866282)
-    [self.keychainView resetSetup];
-
     CKRecord* ckr = [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85"];
     [self.keychainZone addToZone: ckr];
 
     }];
 
     [self waitForExpectations:@[callbackOccurs] timeout:5.0];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
 - (void)testRPCTLKMissingWhenFound {
     // Bring CKKS up in waitfortlk
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self startCKKSSubsystem];
 
     XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "CKKS entered 'ready''");
     }];
 
     [self waitForExpectations:@[callbackOccurs] timeout:5.0];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
 @end
index fe852f6d6023b8d9732286ba4254d06d495ba5e7..4f2aff99e47cb5ae2e45a4d57e79640aa68119e2 100644 (file)
@@ -38,6 +38,7 @@
 #import "keychain/ckks/CKKSMirrorEntry.h"
 
 #import "keychain/ckks/tests/MockCloudKit.h"
+#import "keychain/ckks/tests/AutoreleaseTest.h"
 
 #import "keychain/ckks/tests/CKKSTests.h"
 #import "keychain/ckks/tests/CKKSTests+API.h"
                                             });
     [self waitForExpectationsWithTimeout:8.0 handler:nil];
 }
--(void)fetchCurrentPointerExpectingError:(bool)cached
+-(void)fetchCurrentPointerExpectingError:(bool)fetchCloudValue
 {
     XCTestExpectation* currentExpectation = [self expectationWithDescription: @"callback occurs"];
+    //TEST_API_AUTORELEASE_BEFORE(SecItemFetchCurrentItemAcrossAllDevices);
     SecItemFetchCurrentItemAcrossAllDevices((__bridge CFStringRef)@"com.apple.security.ckks",
                                             (__bridge CFStringRef)@"pcsservice",
                                             (__bridge CFStringRef)@"keychain",
-                                            cached,
+                                            fetchCloudValue,
                                             ^(CFDataRef currentPersistentRef, CFErrorRef cferror) {
                                                 XCTAssertNil((__bridge id)currentPersistentRef, "no current item exists");
                                                 XCTAssertNotNil((__bridge id)cferror, "Error exists when there's a current item");
                                                 [currentExpectation fulfill];
                                             });
+    //TEST_API_AUTORELEASE_AFTER(SecItemFetchCurrentItemAcrossAllDevices);
     [self waitForExpectationsWithTimeout:8.0 handler:nil];
 }
 
@@ -84,6 +87,7 @@
     [self startCKKSSubsystem];
 
     [self.keychainView waitForKeyHierarchyReadiness];
+    [self.keychainView waitUntilAllOperationsAreFinished]; // ensure everything finishes before we disallow fetches
 
     // Ensure that local queries don't hit the server.
     self.silentFetchesAllowed = false;
     // Ensure that setting the current pointer sends a notification
     keychainChanged = [self expectChangeForView:self.keychainZoneID.zoneName];
 
+    //TEST_API_AUTORELEASE_BEFORE(SecItemSetCurrentItemAcrossAllDevices);
     SecItemSetCurrentItemAcrossAllDevices((__bridge CFStringRef)@"com.apple.security.ckks",
                                           (__bridge CFStringRef)@"pcsservice",
                                           (__bridge CFStringRef)@"keychain",
                                               XCTAssertNil(error, "No error setting current item");
                                               [setCurrentExpectation fulfill];
                                           });
+    //TEST_API_AUTORELEASE_AFTER(SecItemSetCurrentItemAcrossAllDevices);
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
     [self waitForExpectations:@[keychainChanged] timeout:1];
     [self waitForCKModifications];
     SecResetLocalSecuritydXPCFakeEntitlements();
 }
 
+-(void)testPCSCurrentSetConflictedItemAsCurrent {
+    SecResetLocalSecuritydXPCFakeEntitlements();
+    SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateCKKSPlaintextFields, kCFBooleanTrue);
+    SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateCKKSWriteCurrentItemPointers, kCFBooleanTrue);
+    SecAddLocalSecuritydXPCFakeEntitlement(kSecEntitlementPrivateCKKSReadCurrentItemPointers, kCFBooleanTrue);
+
+    NSNumber* servIdentifier = @3;
+    NSData* publicKey = [@"asdfasdf" dataUsingEncoding:NSUTF8StringEncoding];
+    NSData* publicIdentity = [@"somedata" dataUsingEncoding:NSUTF8StringEncoding];
+
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
+    [self startCKKSSubsystem];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "Key state should have become ready");
+    [self.keychainView waitUntilAllOperationsAreFinished];
+
+    // Before CKKS can add the item, shove a conflicting one into CloudKit
+
+    NSError* error = nil;
+
+    NSString* account = @"testaccount";
+
+    // Create an item in CloudKit that will conflict in both UUID and primary key
+    NSData* itemdata = [[NSData alloc] initWithBase64EncodedString:@"YnBsaXN0MDDbAQIDBAUGBwgJCgsMDQ4PEBESEhMUFVZ2X0RhdGFUYWNjdFR0b21iVHN2Y2VUc2hhMVRtdXNyVGNkYXRUbWRhdFRwZG1uVGFncnBVY2xhc3NEYXNkZlt0ZXN0YWNjb3VudBAAUE8QFF7OzuEEGWTTwzzSp/rjY6ubHW2rQDNBv7zNQtQUQFJja18QF2NvbS5hcHBsZS5zZWN1cml0eS5ja2tzVGdlbnAIHyYrMDU6P0RJTlNZXmpsbYSFjpGrAAAAAAAAAQEAAAAAAAAAFgAAAAAAAAAAAAAAAAAAALA=" options:0];
+    NSMutableDictionary * item = [[NSPropertyListSerialization propertyListWithData:itemdata
+                                                                           options:0
+                                                                            format:nil
+                                                                             error:&error] mutableCopy];
+    XCTAssertNil(error, "Error should be nil parsing base64 item");
+
+    item[@"v_Data"] = [@"conflictingdata" dataUsingEncoding:NSUTF8StringEncoding];
+    CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID];
+    CKRecord* mismatchedRecord = [self newRecord:ckrid withNewItemData:item];
+    [self.keychainZone addToZone: mismatchedRecord];
+
+    [self expectCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID];
+    NSDictionary* result = [self pcsAddItem:account
+                                       data:[@"asdf" dataUsingEncoding:NSUTF8StringEncoding]
+                          serviceIdentifier:(NSNumber*)servIdentifier
+                                  publicKey:(NSData*)publicKey
+                             publicIdentity:(NSData*)publicIdentity
+                              expectingSync:false];
+    XCTAssertNotNil(result, "Should receive result from adding item");
+
+    NSData* persistentRef = result[(id)kSecValuePersistentRef];
+    NSData* sha1 = result[(id)kSecAttrSHA1];
+
+    // Set the current pointer to the result of adding this item. This should fail.
+    XCTestExpectation* setCurrentExpectation = [self expectationWithDescription: @"callback occurs"];
+    SecItemSetCurrentItemAcrossAllDevices((__bridge CFStringRef)@"com.apple.security.ckks",
+                                          (__bridge CFStringRef)@"pcsservice",
+                                          (__bridge CFStringRef)@"keychain",
+                                          (__bridge CFDataRef)persistentRef,
+                                          (__bridge CFDataRef)sha1, NULL, NULL, ^ (CFErrorRef cferror) {
+                                              XCTAssertNotNil((__bridge NSError*)cferror, "Should error setting current item to hash of item which failed to sync");
+                                              [setCurrentExpectation fulfill];
+                                          });
+
+    [self waitForExpectations:@[setCurrentExpectation] timeout:8.0];
+
+    // Reissue a fetch and find the new persistent ref and sha1 for the item at this UUID
+    [self.keychainView waitForFetchAndIncomingQueueProcessing];
+
+    // The conflicting item update should have won
+    [self checkGenericPassword:@"conflictingdata" account:account];
+
+    NSDictionary *query = @{(id)kSecClass : (id)kSecClassGenericPassword,
+                            (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
+                            (id)kSecAttrAccount : account,
+                            (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
+                            (id)kSecMatchLimit : (id)kSecMatchLimitOne,
+                            (id)kSecReturnAttributes: @YES,
+                            (id)kSecReturnPersistentRef: @YES,
+                            };
+
+    CFTypeRef cfresult = NULL;
+    XCTAssertEqual(errSecSuccess, SecItemCopyMatching((__bridge CFDictionaryRef) query, &cfresult), "Finding item %@", account);
+    NSDictionary* newResult = CFBridgingRelease(cfresult);
+    XCTAssertNotNil(newResult, "Received an item");
+
+    NSData* newPersistentRef = newResult[(id)kSecValuePersistentRef];
+    NSData* newSha1 = newResult[(id)kSecAttrSHA1];
+
+    [self expectCKModifyRecords:@{SecCKRecordCurrentItemType: [NSNumber numberWithUnsignedInteger: 1]}
+        deletedRecordTypeCounts:nil
+                         zoneID:self.keychainZoneID
+            checkModifiedRecord:nil
+           runAfterModification:nil];
+
+    XCTestExpectation* newSetCurrentExpectation = [self expectationWithDescription: @"callback occurs"];
+    SecItemSetCurrentItemAcrossAllDevices((__bridge CFStringRef)@"com.apple.security.ckks",
+                                          (__bridge CFStringRef)@"pcsservice",
+                                          (__bridge CFStringRef)@"keychain",
+                                          (__bridge CFDataRef)newPersistentRef,
+                                          (__bridge CFDataRef)newSha1, NULL, NULL, ^ (CFErrorRef cferror) {
+                                              XCTAssertNil((__bridge NSError*)cferror, "Shouldn't error setting current item");
+                                              [newSetCurrentExpectation fulfill];
+                                          });
+
+    [self waitForExpectations:@[newSetCurrentExpectation] timeout:8.0];
+
+    SecResetLocalSecuritydXPCFakeEntitlements();
+}
+
 @end
 
 #endif // OCTAGON
index 8fe3be4f7e3e1fa919f0b7317d83b45ecea9f49c..87f694b5931cc01c86253e7a7ad0e1403cd3bfda 100644 (file)
  */
 
 #import <CloudKit/CloudKit.h>
-#import <XCTest/XCTest.h>
 #import <OCMock/OCMock.h>
+#import <XCTest/XCTest.h>
 
 #include <Security/SecItemPriv.h>
 
-#import "keychain/ckks/tests/CloudKitMockXCTest.h"
-#import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
-#import "keychain/ckks/CKKSManifest.h"
 #import "keychain/ckks/CKKS.h"
 #import "keychain/ckks/CKKSKeychainView.h"
+#import "keychain/ckks/CKKSManifest.h"
+#import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
+#import "keychain/ckks/tests/CloudKitMockXCTest.h"
 #import "keychain/ckks/tests/MockCloudKit.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 // 1 master manifest, 72 manifest leaf nodes = 73
 // 3 keys, 3 current keys, and 1 device state entry
 #define SYSTEM_DB_RECORD_COUNT (7 + ([CKKSManifest shouldSyncManifests] ? 73 : 0))
 
 @interface CloudKitKeychainSyncingTestsBase : CloudKitKeychainSyncingMockXCTest
-@property CKRecordZoneID* keychainZoneID;
-@property CKKSKeychainView* keychainView;
-@property FakeCKZone* keychainZone;
+@property (nullable) CKRecordZoneID* keychainZoneID;
+@property (nullable) CKKSKeychainView* keychainView;
+@property (nullable) FakeCKZone* keychainZone;
 
-@property (readonly) ZoneKeys* keychainZoneKeys;
+@property (nullable, readonly) ZoneKeys* keychainZoneKeys;
 
 - (ZoneKeys*)keychainZoneKeys;
 @end
@@ -51,3 +53,4 @@
 @interface CloudKitKeychainSyncingTests : CloudKitKeychainSyncingTestsBase
 @end
 
+NS_ASSUME_NONNULL_END
index d0d7b3b0d12b797153cd900617bad9ddbc2021c0..c1893a40bd586adf012936a057dae0b8d28edda4 100644 (file)
@@ -30,6 +30,8 @@
 #import <OCMock/OCMock.h>
 
 #include <Security/SecItemPriv.h>
+#include <securityd/SecItemDb.h>
+#include <securityd/SecItemServer.h>
 
 #import "keychain/ckks/tests/CloudKitMockXCTest.h"
 #import "keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h"
@@ -75,6 +77,8 @@
     self.keychainZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"keychain" ownerName:CKCurrentUserDefaultName];
     self.keychainZone = [[FakeCKZone alloc] initZone: self.keychainZoneID];
 
+    [self.ckksZones addObject:self.keychainZoneID];
+
     // Wait for the ViewManager to be brought up
     XCTAssertEqual(0, [self.injectedManager.completedSecCKKSInitialize wait:4*NSEC_PER_SEC], "No timeout waiting for SecCKKSInitialize");
 
     NSDictionary* status = [self.keychainView status];
     (void)status;
 
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
     [self.keychainView waitUntilAllOperationsAreFinished];
 
     self.keychainView = nil;
 
 #pragma mark - Tests
 
+- (void)testBringupToKeyStateReady {
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
+    [self startCKKSSubsystem];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:4*NSEC_PER_SEC], @"Key state should have arrived at ready");
+}
+
 - (void)testAddItem {
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
 
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
 
     [self startCKKSSubsystem];
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:4*NSEC_PER_SEC], @"Key state should have arrived at ready");
 
     [self addGenericPassword: @"data" account: @"account-delete-me"];
 
 - (void)testAddItemWithoutUUID {
     // Test starts with no keys in database, a key hierarchy in our fake CloudKit, and the TLK safely in the local keychain.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     [self startCKKSSubsystem];
     // Right now, the write in CloudKit is pending. Make the local modification...
     [self updateGenericPassword: @"otherdata" account:account];
 
-    // And then schedule the update, but for the final version of the password
+    // And then schedule the update
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
-                          checkItem:[self checkPasswordBlock:self.keychainZoneID account:account password:@"third"]];
+                          checkItem:[self checkPasswordBlock:self.keychainZoneID account:account password:@"otherdata"]];
 
     // Stop the reencrypt operation from happening
     self.keychainView.holdReencryptOutgoingItemsOperation = [CKKSGroupOperation named:@"reencrypt-hold" withBlock: ^{
         secnotice("ckks", "releasing outgoing-queue hold");
     }];
 
-    [self updateGenericPassword: @"third" account:account];
-
     // Run the reencrypt items operation to completion.
     [self.operationQueue addOperation: self.keychainView.holdReencryptOutgoingItemsOperation];
     [self.keychainView waitForOperationsOfClass:[CKKSReencryptOutgoingItemsOperation class]];
     [self waitForCKModifications];
 }
 
+- (void)testOutgoingQueueRecoverFromStaleInflightEntry {
+    // CKKS is restarting with an existing in-flight OQE
+    // Note that this test is incomplete, and doesn't re-add the item to the local keychain
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
+    NSString* account = @"fake-account";
+
+    [self.keychainView dispatchSync:^bool {
+        NSError* error = nil;
+
+        CKRecordID* ckrid = [[CKRecordID alloc] initWithRecordName:@"DD7C2F9B-B22D-3B90-C299-E3B48174BFA3" zoneID:self.keychainZoneID];
+
+        CKKSItem* item = [self newItem:ckrid withNewItemData:[self fakeRecordDictionary:account zoneID:self.keychainZoneID] key:self.keychainZoneKeys.classC];
+        XCTAssertNotNil(item, "Should be able to create a new fake item");
+
+        CKKSOutgoingQueueEntry* oqe = [[CKKSOutgoingQueueEntry alloc] initWithCKKSItem:item action:SecCKKSActionAdd state:SecCKKSStateInFlight waitUntil:nil accessGroup:@"ckks"];
+        XCTAssertNotNil(oqe, "Should be able to create a new fake OQE");
+        [oqe saveToDatabase:&error];
+
+        XCTAssertNil(error, "Shouldn't error saving new OQE to database");
+        return true;
+    }];
+
+    // When CKKS restarts, it should find and re-upload this item
+    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
+                          checkItem:[self checkPasswordBlock:self.keychainZoneID account:account password:@"data"]];
+
+    [self startCKKSSubsystem];
+    [self.keychainView waitForFetchAndIncomingQueueProcessing];
+
+    self.keychainView = [[CKKSViewManager manager] restartZone: self.keychainZoneID.zoneName];
+    [self.keychainView waitForKeyHierarchyReadiness];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+}
+
+- (void)testOutgoingQueueRecoverFromNetworkFailure {
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
+    NSString* account = @"account-delete-me";
+
+    [self startCKKSSubsystem];
+    [self holdCloudKitModifications];
+
+    // We expect a single record to be uploaded, but need to hold the operation from finishing until we can modify the item locally
+
+    NSError* greyMode = [[CKPrettyError alloc] initWithDomain:CKErrorDomain code:CKErrorNotAuthenticated userInfo:@{}];
+    [self failNextCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID blockAfterReject:nil withError:greyMode];
+
+    [self addGenericPassword: @"data" account: account];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    // And then schedule the retried update
+    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
+                          checkItem:[self checkPasswordBlock:self.keychainZoneID account:account password:@"data"]];
+
+    // The cloudkit operation finishes, letting the next OQO proceed (and set up uploading the new item)
+    [self releaseCloudKitModificationHold];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    [self.keychainView waitUntilAllOperationsAreFinished];
+    [self waitForCKModifications];
+}
+
 - (void)testDeleteItem {
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
 
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
     [self startCKKSSubsystem];
 
+    [self.keychainView waitForKeyHierarchyReadiness];
     [self.keychainView waitUntilAllOperationsAreFinished];
 
     // Place a hold on processing the outgoing queue.
     __block NSString* itemUUID = nil;
     [self.keychainView dispatchSync:^bool {
         NSError* error = nil;
-        NSArray<NSString*>* uuids = [CKKSOutgoingQueueEntry allUUIDs:&error];
+        NSArray<NSString*>* uuids = [CKKSOutgoingQueueEntry allUUIDs:self.keychainZoneID ?: [[CKRecordZoneID alloc] initWithZoneName:@"keychain"
+                                                                                                                           ownerName:CKCurrentUserDefaultName]
+                                                               error:&error];
         XCTAssertNil(error, "no error fetching uuids");
         XCTAssertEqual(uuids.count, 1u, "There's exactly one outgoing queue entry");
         itemUUID = uuids[0];
     [self startCKKSSubsystem];
 
     // Wait for the key hierarchy state machine to get stuck waiting for the unlock dependency. No uploads should occur.
-    while(!([self.keychainView.keyStateMachineOperation isPending] && [self.keychainView.keyStateMachineOperation.dependencies containsObject:self.lockStateTracker.unlockDependency])) {
-        sleep(0.1);
-    }
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForUnlock] wait:8*NSEC_PER_SEC], @"Key state should get stuck in waitforunlock");
 
     // After unlock, the key hierarchy should be created.
     [self expectCKModifyKeyRecords: 3 currentKeyPointerRecords: 3 tlkShareRecords: 1 zoneID:self.keychainZoneID];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
+- (void)testLockImmediatelyAfterUploadingInitialKeyHierarchy {
+
+    // Upon upload, block fetches
+    __weak __typeof(self) weakSelf = self;
+    [self expectCKModifyRecords: @{
+                                   SecCKRecordIntermediateKeyType: [NSNumber numberWithUnsignedInteger: 3],
+                                   SecCKRecordCurrentKeyType: [NSNumber numberWithUnsignedInteger: 3],
+                                   SecCKRecordTLKShareType: [NSNumber numberWithUnsignedInteger: 1],
+                                   }
+        deletedRecordTypeCounts:nil
+                         zoneID:self.keychainZoneID
+            checkModifiedRecord:nil
+           runAfterModification:^{
+               __strong __typeof(self) strongSelf = weakSelf;
+               [strongSelf holdCloudKitFetches];
+           }];
+
+    [self startCKKSSubsystem];
+
+    // Should enter 'ready'
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:180*NSEC_PER_SEC], @"Key state should become 'ready'");
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    // Now, lock and allow fetches again
+    self.aksLockState = true;
+    [self.lockStateTracker recheck];
+    [self releaseCloudKitFetchHold];
+
+    CKKSResultOperation* op = [self.keychainView.zoneChangeFetcher requestSuccessfulFetch:CKKSFetchBecauseTesting];
+    [op waitUntilFinished];
+
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+
+    // Wait for CKKS to shake itself out...
+    [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
+
+    // Should  be in ReadyPendingUnlock
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:8*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
+
+    // We expect a single class C record to be uploaded.
+    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID
+                          checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
+
+    [self addGenericPassword: @"data" account: @"account-delete-me"];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+}
+
 - (void)testReceiveKeyHierarchyAfterLockedStart {
     // 'Lock' the keybag
     self.aksLockState = true;
     [self startCKKSSubsystem];
 
     // Wait for the key hierarchy state machine to get stuck waiting for the unlock dependency. No uploads should occur.
-    while(!([self.keychainView.keyStateMachineOperation isPending] && [self.keychainView.keyStateMachineOperation.dependencies containsObject:self.lockStateTracker.unlockDependency])) {
-        sleep(0.1);
-    }
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateFetchComplete] wait:8*NSEC_PER_SEC], @"Key state should get stuck in fetchcomplete");
 
     // Now, another device comes along and creates the hierarchy; we download it; and it and sends us the TLK
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self.lockStateTracker recheck];
 
     // After unlock, the TLK arrives
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     // We expect a single class C record to be uploaded.
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
+- (void)testLoadKeyHierarchyAfterLockedStart {
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID];
+
+    // 'Lock' the keybag
+    self.aksLockState = true;
+    [self.lockStateTracker recheck];
+
+    [self startCKKSSubsystem];
+
+    // Wait for the key hierarchy state machine to get stuck waiting for the unlock dependency. No uploads should occur.
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:8*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
+
+    self.aksLockState = false;
+    [self.lockStateTracker recheck];
+
+    // We expect a single class C record to be uploaded.
+    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
+
+    [self addGenericPassword: @"data" account: @"account-delete-me"];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+}
+
 - (void)testUploadAndUseKeyHierarchy {
     // Test starts with nothing in database. We expect some sort of TLK/key hierarchy upload.
     [self expectCKModifyKeyRecords: 3 currentKeyPointerRecords: 3 tlkShareRecords: 1 zoneID:self.keychainZoneID];
     CFTypeRef item = NULL;
     XCTAssertEqual(errSecItemNotFound, SecItemCopyMatching((__bridge CFDictionaryRef) query, &item), "item should not exist");
 
-    OCMVerifyAllWithDelay(self.mockDatabase, 1);
-
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
     [self waitForCKModifications];
 
     // We expect a single class C record to be uploaded.
     // Test also begins with the TLK having arrived in the local keychain (via SOS)
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
 
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
 
-    // The CKKS subsystem should not try to write anything to the CloudKit database while it's accepting the keys
-    [self.keychainView waitForKeyHierarchyReadiness];
+    // The CKKS subsystem should only upload its TLK share
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:5*NSEC_PER_SEC], "Key state should have become ready");
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Test starts with nothing in database, but one in our fake CloudKit.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
 
-    // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:5*NSEC_PER_SEC], "Key state should have become waitfortlk");
 
-    // The CKKS subsystem should not try to write anything to the CloudKit database.
-    sleep(1);
-
-    OCMVerifyAllWithDelay(self.mockDatabase, 8);
-
-    // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    // Now, save the TLK to the keychain (to simulate it coming in later via SOS). We'll create a TLK share for ourselves.
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // Wait for the key hierarchy to sort itself out, to make it easier on this test; see testOnboardOldItemsWithExistingKeyHierarchy for the other test.
-    [self.keychainView waitForKeyHierarchyReadiness];
+    // The CKKS subsystem should write its TLK share, but nothing else
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:5*NSEC_PER_SEC], "Key state should have become ready");
 
     // We expect a single record to be uploaded for each key class
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under class C key in hierarchy"]];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID checkItem: [self checkClassABlock:self.keychainZoneID message:@"Object was encrypted under class A key in hierarchy"]];
-    XCTAssertEqual(errSecSuccess, SecItemAdd((__bridge CFDictionaryRef)@{
-                                    (id)kSecClass : (id)kSecClassGenericPassword,
-                                    (id)kSecAttrAccessGroup : @"com.apple.security.sos",
-                                    (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
-                                    (id)kSecAttrAccount : @"account-class-A",
-                                    (id)kSecAttrSynchronizable : (id)kCFBooleanTrue,
-                                    (id)kSecValueData : (id) [@"asdf" dataUsingEncoding:NSUTF8StringEncoding],
-                                    }, NULL), @"Adding class A item");
+    [self addGenericPassword:@"asdf"
+                     account:@"account-class-A"
+                    viewHint:nil
+                      access:(id)kSecAttrAccessibleWhenUnlocked
+                   expecting:errSecSuccess
+                     message:@"Adding class A item"];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
-
-
 - (void)testAcceptExistingKeyHierarchyDespiteLocked {
     // Test starts with no keys in CKKS database, but one in our fake CloudKit.
     // Test also begins with the TLK having arrived in the local keychain (via SOS)
 
     OCMVerifyAllWithDelay(partialKVMock, 4);
 
+    // CKKS will give itself a TLK Share
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
+
     // Now that all operations are complete, 'unlock' AKS
     self.aksLockState = false;
     [self.lockStateTracker recheck];
 
-    [self.keychainView waitForKeyHierarchyReadiness];
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:5*NSEC_PER_SEC], "Key state should have become ready");
     OCMVerifyAllWithDelay(self.mockDatabase, 4);
 
     // Verify that there are three local keys, and three local current key records
 - (void)testReceiveClassCWhileALocked {
     // Test starts with a key hierarchy already existing.
     [self createAndSaveFakeKeyHierarchy:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self startCKKSSubsystem];
 
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], @"Key state should become 'ready'");
     [self.keychainView waitForFetchAndIncomingQueueProcessing];
-    [self.keychainView waitForKeyHierarchyReadiness];
 
     [self findGenericPassword:@"classCItem" expecting:errSecItemNotFound];
     [self findGenericPassword:@"classAItem" expecting:errSecItemNotFound];
         [self.keychainView _onqueueKeyStateMachineRequestProcess];
         return true;
     }];
-    // And ensure we end up back in 'ready': we have the keys, we're just locked now
+    // And ensure we end up back in 'readypendingunlock': we have the keys, we're just locked now
     [self.keychainView waitForOperationsOfClass:[CKKSProcessReceivedKeysOperation class]];
-    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:5*NSEC_PER_SEC], "Key state should have returned to ready");
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:8*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
 
     [self.keychainZone addToZone: [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-F9C5-481E-98AC-5A507ACB2D85" withAccount:@"classCItem" key:self.keychainZoneKeys.classC]];
     [self.keychainZone addToZone: [self createFakeRecord: self.keychainZoneID recordName:@"7B598D31-FFFF-FFFF-FFFF-5A507ACB2D85" withAccount:@"classAItem" key:self.keychainZoneKeys.classA]];
     [self findGenericPassword:@"classAItem" expecting:errSecSuccess];
 }
 
+- (void)testRestartWhileLocked {
+    [self expectCKModifyKeyRecords: 3 currentKeyPointerRecords: 3 tlkShareRecords: 1 zoneID:self.keychainZoneID];
+    [self startCKKSSubsystem];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], @"Key state should become 'ready'");
+
+    // 'Lock' the keybag
+    self.aksLockState = true;
+    [self.lockStateTracker recheck];
+
+    [self.keychainView halt];
+    self.keychainView = [[CKKSViewManager manager] restartZone: self.keychainZoneID.zoneName];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReadyPendingUnlock] wait:8*NSEC_PER_SEC], @"Key state should become 'readypendingunlock'");
+
+    self.aksLockState = false;
+    [self.lockStateTracker recheck];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], @"Key state should become 'ready'");
+}
+
 - (void)testExternalKeyRoll {
     // Test starts with no keys in database, a key hierarchy in our fake CloudKit, and the TLK safely in the local keychain.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     // Spin up CKKS subsystem.
     [self waitForCKModifications];
 
     [self rollFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     // Trigger a notification
 - (void)testAcceptKeyConflictAndUploadReencryptedItem {
     // Test starts with no keys in database, a key hierarchy in our fake CloudKit, and the TLK safely in the local keychain.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     [self startCKKSSubsystem];
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID checkItem: [self checkClassCBlock:self.keychainZoneID message:@"Object was encrypted under rolled class C key in hierarchy"]];
 
     // New key arrives via SOS!
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
+    [self.keychainView waitForFetchAndIncomingQueueProcessing]; // just to be sure it's fetched
 
     // Items should upload.
     [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
-- (void)testOnboardOldItems {
+- (void)testOnboardOldItemsCreatingKeyHierarchy {
     // In this test, we'll check if the CKKS subsystem will pick up a keychain item which existed before the key hierarchy, both with and without a UUID attached at item creation
 
     // Test starts with nothing in CloudKit, and CKKS blocked. Add one item without a UUID...
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
+- (void)testOnboardOldItemsWithExistingKeyHierarchy {
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
+
+    [self expectCKModifyItemRecords: 1 currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
+    [self addGenericPassword: @"data" account: @"account-delete-me"];
+
+    [self startCKKSSubsystem];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+}
 
 - (void)testOnboardOldItemsWithExistingKeyHierarchyExtantTLK {
     // Test starts key hierarchy in our fake CloudKit, the TLK arrived in the local keychain, and CKKS blocked.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     // Add one item without a UUID...
 
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
-
-    // No write yet...
-    sleep(0.5);
-    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:5*NSEC_PER_SEC], "Key state should have become waitfortlk");
 
     // Now, save the TLK to the keychain (to simulate it coming in via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded.
 
     // Test starts with keys in CloudKit (so we can create items later)
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychain:self.keychainZoneID];
 
     [self addGenericPassword: @"data" account: @"first"];
     // Wait for uploads to happen
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
     [self waitForCKModifications];
-    XCTAssertEqual(self.keychainZone.currentDatabase.count, SYSTEM_DB_RECORD_COUNT+passwordCount, "Have 6+passwordCount objects in cloudkit");
+    // One TLK share record
+    XCTAssertEqual(self.keychainZone.currentDatabase.count, SYSTEM_DB_RECORD_COUNT+passwordCount+1, "Have 6+passwordCount objects in cloudkit");
 
     // Now, corrupt away!
     // Extract all passwordCount items for Corruption
         return true;
     }];
 }
+- (void)testResyncLocal {
+    [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
+    [self saveTLKMaterialToKeychain:self.keychainZoneID];
+
+    [self addGenericPassword: @"data" account: @"first"];
+    [self addGenericPassword: @"data" account: @"second"];
+    NSUInteger passwordCount = 2u;
+
+    [self expectCKModifyItemRecords: passwordCount currentKeyPointerRecords: 1 zoneID:self.keychainZoneID];
+    [self startCKKSSubsystem];
+
+    // Wait for uploads to happen
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+
+    // Local resyncs shouldn't fetch clouds.
+    self.silentFetchesAllowed = false;
+    SecCKKSDisable();
+    [self deleteGenericPassword:@"first"];
+    [self deleteGenericPassword:@"second"];
+    SecCKKSEnable();
+
+    // And they're gone!
+    [self findGenericPassword:@"first" expecting:errSecItemNotFound];
+    [self findGenericPassword:@"second" expecting:errSecItemNotFound];
+
+    CKKSLocalSynchronizeOperation* op = [self.keychainView resyncLocal];
+    [op waitUntilFinished];
+    XCTAssertNil(op.error, "Shouldn't be an error resyncing locally");
+
+    // And they're back!
+    [self checkGenericPassword: @"data" account: @"first"];
+    [self checkGenericPassword: @"data" account: @"second"];
+}
+
+- (void)testPlistRestoreResyncsLocal {
+    [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
+    [self saveTLKMaterialToKeychain:self.keychainZoneID];
+
+    [self addGenericPassword: @"data" account: @"first"];
+    [self addGenericPassword: @"data" account: @"second"];
+    NSUInteger passwordCount = 2u;
+
+    [self checkGenericPassword: @"data" account: @"first"];
+    [self checkGenericPassword: @"data" account: @"second"];
+
+    [self expectCKModifyItemRecords:passwordCount currentKeyPointerRecords:1 zoneID:self.keychainZoneID];
+    [self startCKKSSubsystem];
+
+    // Wait for uploads to happen
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+
+    // o no
+    // This 'restores' a plist keychain backup
+    // That will kick off a local resync in CKKS, so hold that until we're ready...
+    self.keychainView.holdLocalSynchronizeOperation = [CKKSResultOperation named:@"hold-local-synchronize" withBlock:^{}];
+
+    // Local resyncs shouldn't fetch clouds.
+    self.silentFetchesAllowed = false;
+
+    CFErrorRef cferror = NULL;
+    kc_with_dbt(true, &cferror, ^bool (SecDbConnectionRef dbt) {
+        CFErrorRef cfcferror = NULL;
+
+        bool ret = SecServerImportKeychainInPlist(dbt, SecSecurityClientGet(), KEYBAG_NONE, KEYBAG_NONE,
+                                                  (__bridge CFDictionaryRef)@{}, kSecBackupableItemFilter, &cfcferror);
+
+        XCTAssertNil(CFBridgingRelease(cfcferror), "Shouldn't error importing a 'backup'");
+        XCTAssert(ret, "Importing a 'backup' should have succeeded");
+        return true;
+    });
+    XCTAssertNil(CFBridgingRelease(cferror), "Shouldn't error mucking about in the db");
+
+    // And they're gone!
+    [self findGenericPassword:@"first" expecting:errSecItemNotFound];
+    [self findGenericPassword:@"second" expecting:errSecItemNotFound];
+
+    // Allow the local resync to continue...
+    [self.operationQueue addOperation:self.keychainView.holdLocalSynchronizeOperation];
+    [self.keychainView waitForOperationsOfClass:[CKKSLocalSynchronizeOperation class]];
+
+    // And they're back!
+    [self checkGenericPassword: @"data" account: @"first"];
+    [self checkGenericPassword: @"data" account: @"second"];
+}
 
 - (void)testMultipleZoneAdd {
     // Test starts with nothing in database. We expect some sort of TLK/key hierarchy upload.
     [self waitForCKModifications];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
-    // Tear down the CKKS object and disallos fetches
-    [self.keychainView cancelAllOperations];
+    // Tear down the CKKS object and disallow fetches
+    [self.keychainView halt];
     self.silentFetchesAllowed = false;
 
     self.keychainView = [[CKKSViewManager manager] restartZone: self.keychainZoneID.zoneName];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Okay, cool, rad, now let's set the date to be very long ago and check that there's positively a fetch
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
     self.silentFetchesAllowed = false;
 
     [self.keychainView dispatchSync: ^bool {
 
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
     NSError* error = nil;
+
+    // Stash the TLKs.
+    [self.keychainZoneKeys.tlk saveKeyMaterialToKeychain:true error:&error];
+    XCTAssertNil(error, "Should have received no error stashing the new TLK in the keychain");
+
+    // And delete the non-stashed version
     [self.keychainZoneKeys.tlk deleteKeyMaterialFromKeychain:&error];
     XCTAssertNil(error, "Should have received no error deleting the new TLK from the keychain");
 
     [self.keychainView waitForKeyHierarchyReadiness];
 
     // Tear down the CKKS object
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
 
     NSError* error = nil;
+
+    // Stash the TLKs.
+    [self.keychainZoneKeys.tlk saveKeyMaterialToKeychain:true error:&error];
+    XCTAssertNil(error, "Should have received no error stashing the new TLK in the keychain");
+
     [self.keychainZoneKeys.tlk deleteKeyMaterialFromKeychain:&error];
     XCTAssertNil(error, "Should have received no error deleting the new TLK from the keychain");
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Now the TLKs arrive from the other device...
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
     [self.keychainView waitForKeyHierarchyReadiness];
 
     [self startCKKSSubsystem];
 
     // The CKKS subsystem should figure out the issue, and fix it.
-    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 3 tlkShareRecords: 0 zoneID:self.keychainZoneID];
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
 
     [self.keychainView waitForKeyHierarchyReadiness];
 
     [self startCKKSSubsystem];
 
     // The CKKS subsystem should figure out the issue, and fix it.
-    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 3 tlkShareRecords: 0 zoneID:self.keychainZoneID];
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
 
-    [self.keychainView waitForKeyHierarchyReadiness];
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "Key state should have become ready");
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
         return true;
     }];
 
+    // The CKKS subsystem should try to write TLKs, but fail. It'll then upload a TLK share for the keys already in CloudKit
+    [self expectCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
+
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
-
-    // The CKKS subsystem should try to write TLKs, but fail
-    [self expectCKAtomicModifyItemRecordsUpdateFailure:self.keychainZoneID];
     OCMVerifyAllWithDelay(self.mockDatabase, 16);
 
     // CKKS should then happily use the keys in CloudKit
     // Spin up CKKS subsystem.
     [self startCKKSSubsystem];
 
-    // The CKKS subsystem should figure out the issue, and fix it.
-    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 3 tlkShareRecords: 0 zoneID:self.keychainZoneID];
+    // The CKKS subsystem should figure out the issue, and fix it (while uploading itself a TLK Share)
+    [self expectCKModifyKeyRecords: 0 currentKeyPointerRecords: 3 tlkShareRecords: 1 zoneID:self.keychainZoneID];
 
-    [self.keychainView waitForKeyHierarchyReadiness];
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "Key state should have become ready");
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
     [self startCKKSSubsystem];
 
     // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded
     [self expectCKFetch];
 
     // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded
     [self startCKKSSubsystem];
 
     // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded
 - (void)testRecoverFromCloudKitUnknownDeviceStateRecord {
     // Test starts with nothing in database, but one in our fake CloudKit.
     [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID];
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // Save a new device state record with some fake etag
     [self startCKKSSubsystem];
 
     // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded
     [self startCKKSSubsystem];
 
     // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded
     [self startCKKSSubsystem];
 
     // Now, save the TLK to the keychain (to simulate it coming in later via SOS).
+    [self expectCKKSTLKSelfShareUpload:self.keychainZoneID];
     [self saveTLKMaterialToKeychainSimulatingSOS:self.keychainZoneID];
 
     // We expect a single record to be uploaded
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
     [self startCKKSSubsystem];
 
+    XCTAssertEqual(0,    [self.keychainView.loggedOut wait:500*NSEC_PER_MSEC], "Should have been told of a 'logout' event on startup");
+    XCTAssertNotEqual(0, [self.keychainView.loggedIn wait:100*NSEC_PER_MSEC], "'login' event shouldn't have happened");
+
     [self.keychainView waitUntilAllOperationsAreFinished];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     self.circleStatus = kSOSCCInCircle;
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
 
+    XCTAssertEqual(0,    [self.keychainView.loggedIn wait:2000*NSEC_PER_MSEC], "Should have been told of a 'login'");
+    XCTAssertNotEqual(0, [self.keychainView.loggedOut wait:100*NSEC_PER_MSEC], "'logout' event should be reset");
+
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
     [self waitForCKModifications];
 
     [self expectCKModifyKeyRecords: 3 currentKeyPointerRecords: 3 tlkShareRecords: 1 zoneID:self.keychainZoneID];
 
     [self startCKKSSubsystem];
+    XCTAssertEqual(0,    [self.keychainView.loggedIn wait:2000*NSEC_PER_MSEC], "Should have been told of a 'login'");
+    XCTAssertNotEqual(0, [self.keychainView.loggedOut wait:100*NSEC_PER_MSEC], "'logout' event should be reset");
 
     OCMVerifyAllWithDelay(self.mockDatabase, 20);
     [self waitForCKModifications];
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
 
     // Test that there are no items in the database after logout
-    [self.keychainView waitUntilAllOperationsAreFinished];
+    XCTAssertEqual(0,    [self.keychainView.loggedOut wait:2000*NSEC_PER_MSEC], "Should have been told of a 'logout'");
+    XCTAssertNotEqual(0, [self.keychainView.loggedIn wait:100*NSEC_PER_MSEC], "'login' event should be reset");
     [self checkNoCKKSData: self.keychainView];
 
     // There should be no further uploads, even when we save keychain items
     self.circleStatus = kSOSCCInCircle;
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
 
+    XCTAssertEqual(0,    [self.keychainView.loggedIn wait:2000*NSEC_PER_MSEC], "Should have been told of a 'login'");
+    XCTAssertNotEqual(0, [self.keychainView.loggedOut wait:100*NSEC_PER_MSEC], "'logout' event should be reset");
+
     OCMVerifyAllWithDelay(self.mockDatabase, 20);
 
     // Let everything settle...
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
 
     // Test that there are no items in the database after logout
-    [self.keychainView waitUntilAllOperationsAreFinished];
+    XCTAssertEqual(0,    [self.keychainView.loggedOut wait:2000*NSEC_PER_MSEC], "Should have been told of a 'logout'");
+    XCTAssertNotEqual(0, [self.keychainView.loggedIn wait:100*NSEC_PER_MSEC], "'login' event should be reset");
     [self checkNoCKKSData: self.keychainView];
 
     // There should be no further uploads, even when we save keychain items
     self.circleStatus = kSOSCCInCircle;
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
 
+    XCTAssertEqual(0,    [self.keychainView.loggedIn wait:2000*NSEC_PER_MSEC], "Should have been told of a 'login'");
+    XCTAssertNotEqual(0, [self.keychainView.loggedOut wait:100*NSEC_PER_MSEC], "'logout' event should be reset");
+
     OCMVerifyAllWithDelay(self.mockDatabase, 20);
 }
 
 
     id partialKVMock = OCMPartialMock(self.keychainView);
     OCMReject([partialKVMock handleCKLogout]);
+    // note: don't unblock the ck account state object yet...
 
     self.circleStatus = kSOSCCInCircle;
     [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
-    [self startCKKSSubsystemOnly]; // note: don't unblock the ck account state object yet...
 
     // Add a keychain item, but make sure it doesn't upload yet.
     [self addGenericPassword: @"data" account: @"account-delete-me"];
 
     [self.keychainView waitUntilAllOperationsAreFinished];
     [self waitForCKModifications];
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
 
     [partialKVMock stopMocking];
 }
                     ZoneKeys* zoneKeys = strongSelf.keys[strongSelf.keychainZoneID];
                     XCTAssertNotNil(zoneKeys, "Have zone keys for %@", strongSelf.keychainZoneID);
 
-                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], @"fake-circle-id", "peer ID matches what we gave it");
+                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it");
                     XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle");
                     XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateReady), "Device is in ready");
 
                     ZoneKeys* zoneKeys = strongSelf.keys[strongSelf.keychainZoneID];
                     XCTAssertNotNil(zoneKeys, "Have zone keys for %@", strongSelf.keychainZoneID);
 
-                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], @"fake-circle-id", "peer ID matches what we gave it");
+                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it");
                     XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle");
                     XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateReady), "Device is in ready");
 
 - (void)testDeviceStateUploadWaitsForKeyHierarchy {
     [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
 
+    // Ask to wait for quite a while if we don't become ready
+    [self.keychainView updateDeviceState:false waitForKeyHierarchyInitialization:20*NSEC_PER_SEC ckoperationGroup:nil];
+
     __weak __typeof(self) weakSelf = self;
     // Expect a ready upload
     [self expectCKModifyRecords: @{SecCKRecordDeviceStateType: [NSNumber numberWithInt:1]}
                     ZoneKeys* zoneKeys = strongSelf.keys[strongSelf.keychainZoneID];
                     XCTAssertNotNil(zoneKeys, "Have zone keys for %@", strongSelf.keychainZoneID);
 
-                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], @"fake-circle-id", "peer ID matches what we gave it");
+                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it");
                     XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle");
                     XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateReady), "Device is in ready");
 
             }
            runAfterModification:nil];
 
-    // Ensure we'll wait for quite a while if we don't become ready
-    [self.keychainView updateDeviceState:false waitForKeyHierarchyInitialization:20*NSEC_PER_SEC ckoperationGroup:nil];
-
-    // But don't allow the key state to progress until now
+    // And allow the key state to progress
     [self startCKKSSubsystem];
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
                     __strong __typeof(weakSelf) strongSelf = weakSelf;
                     XCTAssertNotNil(strongSelf, "self exists");
 
-                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], @"fake-circle-id", "peer ID matches what we gave it");
+                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it");
                     XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle");
                     XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateWaitForTLK), "Device is in waitfortlk");
 
                     __strong __typeof(weakSelf) strongSelf = weakSelf;
                     XCTAssertNotNil(strongSelf, "self exists");
 
-                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], @"fake-circle-id", "peer ID matches what we gave it");
+                    XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it");
                     XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle");
                     XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateWaitForTLK), "Device is in waitfortlk");
 
 
     [self startCKKSSubsystem];
 
-    // Since CKKS should start up enough to get back into the error state and then back into initializing state, wait for that to happen.
-    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateError] wait:8*NSEC_PER_SEC], "CKKS entered error");
     XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateInitializing] wait:8*NSEC_PER_SEC], "CKKS entered initializing");
     XCTAssertEqualObjects(self.keychainView.keyHierarchyState, SecCKKSZoneKeyStateInitializing, "CKKS entered intializing");
 
     XCTAssertNil(op.error, "No error uploading 'out of circle' device state");
 }
 
+- (void)testNotStuckAfterReset {
+    [self createAndSaveFakeKeyHierarchy: self.keychainZoneID]; // Make life easy for this test.
+
+    XCTestExpectation *operationRun = [self expectationWithDescription:@"operation run"];
+    NSOperation* op = [NSBlockOperation named:@"test" withBlock:^{
+        [operationRun fulfill];
+    }];
+
+    [op addDependency:self.keychainView.keyStateReadyDependency];
+    [self.operationQueue addOperation:op];
+
+    // And handle a spurious logout
+    [self.keychainView handleCKLogout];
+
+    [self startCKKSSubsystem];
+
+    [self waitForExpectations: @[operationRun] timeout:80];
+}
+
 - (void)testCKKSControlBringup {
     xpc_endpoint_t endpoint = SecServerCreateCKKSEndpoint();
     XCTAssertNotNil(endpoint, "Received endpoint");
index 33004bf79d6940f89bd49a32aa26256b912cf464..a8f82b94b9dacb9da03e4eae6954cce0ac846896 100644 (file)
@@ -74,7 +74,7 @@
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Tear down the CKKS object
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
 
     self.keychainView = [[CKKSViewManager manager] restartZone:self.keychainZoneID.zoneName];
     [self.keychainView waitForKeyHierarchyReadiness];
     [self.keychainView waitForFetchAndIncomingQueueProcessing];
 
     // Tear down the CKKS object
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
 
     [self.keychainView dispatchSync: ^bool {
         // Edit the zone state entry to have no fixups
 
     self.silentFetchesAllowed = false;
     [self expectCKFetchByRecordID];
-    if(SecCKKSShareTLKs()) {
-        [self expectCKFetchByQuery]; // and one for the TLKShare fixup
-    }
+    [self expectCKFetchByQuery]; // and one for the TLKShare fixup
 
     // Change one of the CIPs while CKKS is offline
     cip2.currentItemUUID = @"changed-by-cloudkit";
 }
 
 - (void)testFixupFetchAllTLKShareRecords {
-    SecCKKSSetShareTLKs(true);
     // In <rdar://problem/34901306> CKKSTLK: TLKShare CloudKit upload/download on TLK change, trust set addition,
     // we added the TLKShare CKRecord type. Upgrading devices must fetch all such records when they come online for the first time.
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
     // Tear down the CKKS object
-    [self.keychainView cancelAllOperations];
+    [self.keychainView halt];
     [self setFixupNumber:CKKSFixupRefetchCurrentItemPointers ckks:self.keychainView];
 
     // Also, create a TLK share record that CKKS should find
 
     self.keychainView = [[CKKSViewManager manager] restartZone:self.keychainZoneID.zoneName];
 
-    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForFixupOperation] wait:500*NSEC_PER_SEC], "Key state should become waitforfixup");
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForFixupOperation] wait:8*NSEC_PER_SEC], "Key state should become waitforfixup");
     [self releaseCloudKitFetchHold];
-    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:500*NSEC_PER_SEC], "Key state should become ready");
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "Key state should become ready");
 
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 
         XCTAssertNotNil(localshare, "Should be able to find a new TLKShare record in database");
         return true;
     }];
+}
+
+- (void)testFixupLocalReload {
+    // In <rdar://problem/35540228> Server Generated CloudKit "Manatee Identity Lost"
+    // items could be deleted from the local keychain after CKKS believed they were already synced, and therefore wouldn't resync
+
+    [self expectCKModifyKeyRecords:3 currentKeyPointerRecords:3 tlkShareRecords:1 zoneID:self.keychainZoneID];
+    [self startCKKSSubsystem];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], @"Key state should become 'ready'");
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+
+    [self addGenericPassword: @"data" account: @"first"];
+    [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+
+    // Add another record to mock up early CKKS record saving
+    __block CKRecordID* secondRecordID = nil;
+    CKKSCondition* secondRecordIDFilled = [[CKKSCondition alloc] init];
+    [self addGenericPassword: @"data" account: @"second"];
+    [self expectCKModifyItemRecords:1 currentKeyPointerRecords:1 zoneID:self.keychainZoneID checkItem:^BOOL(CKRecord * _Nonnull record) {
+        secondRecordID = record.recordID;
+        [secondRecordIDFilled fulfill];
+        return TRUE;
+    }];
+    OCMVerifyAllWithDelay(self.mockDatabase, 8);
+    [self waitForCKModifications];
+    XCTAssertNotNil(secondRecordID, "Should have filled in secondRecordID");
+    XCTAssertEqual(0, [secondRecordIDFilled wait:8*NSEC_PER_SEC], "Should have filled in secondRecordID within enough time");
+
+    // Tear down the CKKS object
+    [self.keychainView halt];
+    [self setFixupNumber:CKKSFixupFetchTLKShares ckks:self.keychainView];
+
+    // Delete items from keychain
+    [self deleteGenericPassword:@"first"];
+
+    // Corrupt the second item's CKMirror entry to only contain system fields in the CKRecord portion (to emulate early CKKS behavior)
+    [self.keychainView dispatchSync:^bool {
+        NSError* error = nil;
+        CKKSMirrorEntry* ckme = [CKKSMirrorEntry fromDatabase:secondRecordID.recordName zoneID:self.keychainZoneID error:&error];
+        XCTAssertNil(error, "Should have no error pulling second CKKSMirrorEntry from database");
+
+        NSMutableData* data = [NSMutableData data];
+        NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
+        [ckme.item.storedCKRecord encodeSystemFieldsWithCoder:archiver];
+        [archiver finishEncoding];
+        ckme.item.encodedCKRecord = data;
+
+        [ckme saveToDatabase:&error];
+        XCTAssertNil(error, "No error saving system-fielded CKME back to database");
+        return true;
+    }];
+
+    // Now, restart CKKS, but place a hold on the fixup operation
+    self.silentFetchesAllowed = false;
+    self.accountStatus = CKAccountStatusCouldNotDetermine;
+    [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
+
+    self.keychainView = [[CKKSViewManager manager] restartZone:self.keychainZoneID.zoneName];
+    self.keychainView.holdFixupOperation = [CKKSResultOperation named:@"hold-fixup" withBlock:^{}];
+
+    self.accountStatus = CKAccountStatusAvailable;
+    [self.accountStateTracker notifyCircleStatusChangeAndWaitForSignal];
+
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForFixupOperation] wait:8*NSEC_PER_SEC], "Key state should become waitforfixup");
+    [self.operationQueue addOperation: self.keychainView.holdFixupOperation];
+    XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateReady] wait:8*NSEC_PER_SEC], "Key state should become ready");
+
+    [self.keychainView.lastFixupOperation waitUntilFinished];
+    XCTAssertNil(self.keychainView.lastFixupOperation.error, "Shouldn't have been any error performing fixup");
+
+    [self.keychainView waitForOperationsOfClass:[CKKSIncomingQueueOperation class]];
 
-    SecCKKSSetShareTLKs(false);
+    // And the item should be back!
+    [self checkGenericPassword: @"data" account: @"first"];
 }
 
 @end
index 65a39adcefb92679a51b62badd94521d910ddff2..b6992f5854d2e7e75a42cc237e2101abaa6e39f3 100644 (file)
@@ -24,6 +24,9 @@
 #import "keychain/ckks/CKKS.h"
 #import "keychain/ckks/CKKSControl.h"
 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
+#import "keychain/ckks/CKKSItem.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 @class CKKSKey;
 @class CKKSCurrentKeyPointer;
 
 @property CKKSControl* ckksControl;
 
-@property id mockCKKSKey;
+@property (nullable) id mockCKKSKey;
 
-@property id<CKKSSelfPeer> currentSelfPeer;
-@property NSMutableSet<id<CKKSPeer>>* currentPeers;
+@property (nullable) id<CKKSSelfPeer> currentSelfPeer;
+@property (nullable) NSMutableSet<id<CKKSPeer>>* currentPeers;
 
-@property NSMutableDictionary<CKRecordZoneID*, ZoneKeys*>* keys;
+@property NSMutableSet<CKRecordZoneID*>* ckksZones;
+@property (nullable) NSMutableDictionary<CKRecordZoneID*, ZoneKeys*>* keys;
 
 // Pass in an oldTLK to wrap it to the new TLK; otherwise, pass nil
-- (ZoneKeys*)createFakeKeyHierarchy: (CKRecordZoneID*)zoneID oldTLK:(CKKSKey*) oldTLK;
-- (void)saveFakeKeyHierarchyToLocalDatabase: (CKRecordZoneID*)zoneID;
-- (void)putFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID;
-- (void)saveTLKMaterialToKeychain: (CKRecordZoneID*)zoneID;
-- (void)deleteTLKMaterialFromKeychain: (CKRecordZoneID*)zoneID;
-- (void)saveTLKMaterialToKeychainSimulatingSOS: (CKRecordZoneID*)zoneID;
+- (ZoneKeys*)createFakeKeyHierarchy:(CKRecordZoneID*)zoneID oldTLK:(CKKSKey* _Nullable)oldTLK;
+- (void)saveFakeKeyHierarchyToLocalDatabase:(CKRecordZoneID*)zoneID;
+- (void)putFakeKeyHierarchyInCloudKit:(CKRecordZoneID*)zoneID;
+- (void)saveTLKMaterialToKeychain:(CKRecordZoneID*)zoneID;
+- (void)deleteTLKMaterialFromKeychain:(CKRecordZoneID*)zoneID;
+- (void)saveTLKMaterialToKeychainSimulatingSOS:(CKRecordZoneID*)zoneID;
 - (void)SOSPiggyBackAddToKeychain:(NSDictionary*)piggydata;
 - (NSMutableDictionary*)SOSPiggyBackCopyFromKeychain;
-- (NSMutableArray<NSData *>*) SOSPiggyICloudIdentities;
+- (NSMutableArray<NSData*>*)SOSPiggyICloudIdentities;
 
 - (void)putTLKShareInCloudKit:(CKKSKey*)key
                          from:(CKKSSOSSelfPeer*)sharingPeer
                            to:(id<CKKSPeer>)receivingPeer
                        zoneID:(CKRecordZoneID*)zoneID;
-- (void)putTLKSharesInCloudKit:(CKKSKey*)key
-                          from:(CKKSSOSSelfPeer*)sharingPeer
-                        zoneID:(CKRecordZoneID*)zoneID;
+- (void)putTLKSharesInCloudKit:(CKKSKey*)key from:(CKKSSOSSelfPeer*)sharingPeer zoneID:(CKRecordZoneID*)zoneID;
 - (void)putSelfTLKSharesInCloudKit:(CKRecordZoneID*)zoneID;
 - (void)saveTLKSharesInLocalDatabase:(CKRecordZoneID*)zoneID;
 
-- (void)saveClassKeyMaterialToKeychain: (CKRecordZoneID*)zoneID;
+- (void)saveClassKeyMaterialToKeychain:(CKRecordZoneID*)zoneID;
 
 // Call this to fake out your test: all keys are created, saved in cloudkit, and saved locally (as if the key state machine had processed them)
-- (void)createAndSaveFakeKeyHierarchy: (CKRecordZoneID*)zoneID;
+- (void)createAndSaveFakeKeyHierarchy:(CKRecordZoneID*)zoneID;
 
-- (void)rollFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID;
+- (void)rollFakeKeyHierarchyInCloudKit:(CKRecordZoneID*)zoneID;
 
-- (NSDictionary*)fakeRecordDictionary:(NSString*) account zoneID:(CKRecordZoneID*)zoneID;
-- (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName ;
-- (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount: (NSString*) account;
-- (CKRecord*)createFakeRecord: (CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount: (NSString*) account key:(CKKSKey*)key;
+- (NSDictionary*)fakeRecordDictionary:(NSString*)account zoneID:(CKRecordZoneID*)zoneID;
+- (CKRecord*)createFakeRecord:(CKRecordZoneID*)zoneID recordName:(NSString*)recordName;
+- (CKRecord*)createFakeRecord:(CKRecordZoneID*)zoneID recordName:(NSString*)recordName withAccount:(NSString* _Nullable)account;
+- (CKRecord*)createFakeRecord:(CKRecordZoneID*)zoneID
+                   recordName:(NSString*)recordName
+                  withAccount:(NSString* _Nullable)account
+                          key:(CKKSKey* _Nullable)key;
 
-- (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary;
-- (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary key:(CKKSKey*)key;
-- (NSDictionary*)decryptRecord: (CKRecord*) record;
+- (CKKSItem*)newItem:(CKRecordID*)recordID withNewItemData:(NSDictionary*) dictionary key:(CKKSKey*)key;
+- (CKRecord*)newRecord:(CKRecordID*)recordID withNewItemData:(NSDictionary*)dictionary;
+- (CKRecord*)newRecord:(CKRecordID*)recordID withNewItemData:(NSDictionary*)dictionary key:(CKKSKey*)key;
+- (NSDictionary*)decryptRecord:(CKRecord*)record;
 
 // Do keychain things:
-- (void)addGenericPassword: (NSString*) password account: (NSString*) account;
-- (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint:(NSString*)viewHint;
-- (void)addGenericPassword: (NSString*) password account: (NSString*) account viewHint: (NSString*) viewHint access:(NSString*)access expecting: (OSStatus) status message: (NSString*) message;
-- (void)addGenericPassword: (NSString*) password account: (NSString*) account expecting: (OSStatus) status message: (NSString*) message;
-
-- (void)updateGenericPassword: (NSString*) newPassword account: (NSString*)account;
+- (void)addGenericPassword:(NSString*)password account:(NSString*)account;
+- (void)addGenericPassword:(NSString*)password account:(NSString*)account viewHint:(NSString* _Nullable)viewHint;
+- (void)addGenericPassword:(NSString*)password
+                   account:(NSString*)account
+                  viewHint:(NSString* _Nullable)viewHint
+                    access:(NSString*)access
+                 expecting:(OSStatus)status
+                   message:(NSString*)message;
+- (void)addGenericPassword:(NSString*)password account:(NSString*)account expecting:(OSStatus)status message:(NSString*)message;
+
+- (void)updateGenericPassword:(NSString*)newPassword account:(NSString*)account;
 - (void)updateAccountOfGenericPassword:(NSString*)newAccount account:(NSString*)account;
 
-- (void)checkNoCKKSData: (CKKSKeychainView*) view;
+- (void)checkNoCKKSData:(CKKSKeychainView*)view;
 
-- (void)deleteGenericPassword: (NSString*) account;
+- (void)deleteGenericPassword:(NSString*)account;
 
-- (void)findGenericPassword: (NSString*) account expecting: (OSStatus) status;
-- (void)checkGenericPassword: (NSString*) password account: (NSString*) account;
+- (void)findGenericPassword:(NSString*)account expecting:(OSStatus)status;
+- (void)checkGenericPassword:(NSString*)password account:(NSString*)account;
 
 - (void)createClassCItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account;
 - (void)createClassAItemAndWaitForUpload:(CKRecordZoneID*)zoneID account:(NSString*)account;
 
 // Pass the blocks created with these to expectCKModifyItemRecords to check if all items were encrypted with a particular class key
-- (BOOL (^) (CKRecord*)) checkClassABlock: (CKRecordZoneID*) zoneID message:(NSString*) message;
-- (BOOL (^) (CKRecord*)) checkClassCBlock: (CKRecordZoneID*) zoneID message:(NSString*) message;
+- (BOOL (^)(CKRecord*))checkClassABlock:(CKRecordZoneID*)zoneID message:(NSString*)message;
+- (BOOL (^)(CKRecord*))checkClassCBlock:(CKRecordZoneID*)zoneID message:(NSString*)message;
 
-- (BOOL (^) (CKRecord*)) checkPasswordBlock:(CKRecordZoneID*)zoneID
-                                    account:(NSString*)account
-                                   password:(NSString*)password;
+- (BOOL (^)(CKRecord*))checkPasswordBlock:(CKRecordZoneID*)zoneID account:(NSString*)account password:(NSString*)password;
 
 - (void)checkNSyncableTLKsInKeychain:(size_t)n;
 
 // Returns an expectation that someone will send an NSNotification that this view changed
--(XCTestExpectation*)expectChangeForView:(NSString*)view;
+- (XCTestExpectation*)expectChangeForView:(NSString*)view;
 
 // Establish an assertion that CKKS will cause a server extension error soon.
 - (void)expectCKReceiveSyncKeyHierarchyError:(CKRecordZoneID*)zoneID;
+
+// Add expectations that CKKS will upload a single TLK share
+- (void)expectCKKSTLKSelfShareUpload:(CKRecordZoneID*)zoneID;
 @end
+
+NS_ASSUME_NONNULL_END
index 1aaeecce66c67fda1db3b5dc82d70f3c1b01c4a8..63ba5409e980f65a95264eceb5d81729b07de475 100644 (file)
     XCTAssertFalse([CKKSManifest shouldEnforceManifests], "Manifests enforcement is disabled");
 
     [super setUp];
+    self.ckksZones = [NSMutableSet set];
     self.keys = [[NSMutableDictionary alloc] init];
 
     // Fake out whether class A keys can be loaded from the keychain.
     self.mockCKKSKey = OCMClassMock([CKKSKey class]);
     __weak __typeof(self) weakSelf = self;
-    OCMStub([self.mockCKKSKey loadKeyMaterialFromKeychain:[OCMArg checkWithBlock:^BOOL(CKKSKey* key) {
+    BOOL (^shouldFailKeychainQuery)(NSDictionary* query) = ^BOOL(NSDictionary* query) {
         __strong __typeof(self) strongSelf = weakSelf;
-        return ([key.keyclass isEqualToString: SecCKKSKeyClassA] || [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) && strongSelf.aksLockState;
-    }]
-                                                   resave:[OCMArg anyPointer]
-                                                    error:[OCMArg anyObjectRef]]).andCall(self, @selector(handleLockLoadKeyMaterialFromKeychain:resave:error:));
+        NSString* description = query[(id)kSecAttrDescription];
+        bool isTLK = [description isEqualToString: SecCKKSKeyClassTLK] ||
+                    [description isEqualToString: [SecCKKSKeyClassTLK stringByAppendingString: @"-nonsync"]] ||
+                    [description isEqualToString: [SecCKKSKeyClassTLK stringByAppendingString: @"-piggy"]];
+        bool isClassA = [description isEqualToString: SecCKKSKeyClassA];
 
-    OCMStub([self.mockCKKSKey saveKeyMaterialToKeychain:[OCMArg checkWithBlock:^BOOL(CKKSKey* key) {
-        __strong __typeof(self) strongSelf = weakSelf;
-        return ([key.keyclass isEqualToString: SecCKKSKeyClassA] || [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) && strongSelf.aksLockState;
-    }]
-                                               stashTLK:[OCMArg anyObjectRef]
-                                                  error:[OCMArg anyObjectRef]]
-             ).andCall(self, @selector(handleLockSaveKeyMaterialToKeychain:stashTLK:error:));
+        return (isTLK || isClassA) && strongSelf.aksLockState;
+    };
+
+    OCMStub([self.mockCKKSKey setKeyMaterialInKeychain:[OCMArg checkWithBlock:shouldFailKeychainQuery] error:[OCMArg anyObjectRef]]
+            ).andCall(self, @selector(handleLockSetKeyMaterialInKeychain:error:));
+
+    OCMStub([self.mockCKKSKey queryKeyMaterialInKeychain:[OCMArg checkWithBlock:shouldFailKeychainQuery] error:[OCMArg anyObjectRef]]
+            ).andCall(self, @selector(handleLockLoadKeyMaterialFromKeychain:error:));
 
     // Fake out SOS peers
     self.currentSelfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:@"local-peer"
     OCMVerifyAllWithDelay(self.mockDatabase, 8);
 }
 
-// Helpers to handle keychain 'locked' loading
--(bool)handleLockLoadKeyMaterialFromKeychain:(CKKSKey*)key resave:(bool*)resavePtr error:(NSError * __autoreleasing *) error {
-    XCTAssertTrue(self.aksLockState, "Failing a read only when keychain is locked");
-    if(error) {
-        *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
+// Helpers to handle 'locked' keychain loading and saving
+-(bool)handleLockLoadKeyMaterialFromKeychain:(NSDictionary*)query error:(NSError * __autoreleasing *) error {
+    // I think the behavior is: errSecItemNotFound if the item doesn't exist, otherwise errSecInteractionNotAllowed.
+    XCTAssertTrue(self.aksLockState, "Failing a read when keychain is locked");
+
+    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
+    if(status == errSecItemNotFound) {
+        if(error) {
+            *error = [NSError errorWithDomain:@"securityd" code:status userInfo:nil];
+        }
+    } else {
+        if(error) {
+            *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
+        }
     }
     return false;
 }
 
--(bool)handleLockSaveKeyMaterialToKeychain:(CKKSKey*)key stashTLK:(bool)stashTLK error:(NSError * __autoreleasing *) error {
+-(bool)handleLockSetKeyMaterialInKeychain:(NSDictionary*)query error:(NSError * __autoreleasing *) error {
     XCTAssertTrue(self.aksLockState, "Failing a write only when keychain is locked");
     if(error) {
         *error = [NSError errorWithDomain:@"securityd" code:errSecInteractionNotAllowed userInfo:nil];
@@ -409,7 +421,9 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
 - (void)saveTLKMaterialToKeychain: (CKRecordZoneID*)zoneID {
     NSError* error = nil;
     XCTAssertNotNil(self.keys[zoneID].tlk, "Have a TLK to save for zone %@", zoneID);
-    [self.keys[zoneID].tlk saveKeyMaterialToKeychain:&error];
+
+    // Don't make the stashed local copy of the TLK
+    [self.keys[zoneID].tlk saveKeyMaterialToKeychain:false error:&error];
     XCTAssertNil(error, @"Saved TLK material to keychain");
 }
 
@@ -488,87 +502,88 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
 }
 
 - (void)createAndSaveFakeKeyHierarchy: (CKRecordZoneID*)zoneID {
-    [self saveFakeKeyHierarchyToLocalDatabase: zoneID];
+    // Put in CloudKit first, so the records on-disk will have the right change tags
     [self putFakeKeyHierarchyInCloudKit:       zoneID];
+    [self saveFakeKeyHierarchyToLocalDatabase: zoneID];
     [self saveTLKMaterialToKeychain:           zoneID];
     [self saveClassKeyMaterialToKeychain:      zoneID];
 
-    if(SecCKKSShareTLKs()) {
-        [self putSelfTLKSharesInCloudKit:zoneID];
-        [self saveTLKSharesInLocalDatabase:zoneID];
-    }
+    [self putSelfTLKSharesInCloudKit:zoneID];
+    [self saveTLKSharesInLocalDatabase:zoneID];
 }
 
 // Override our base class here:
-- (void)expectCKModifyKeyRecords:(NSUInteger)expectedNumberOfRecords
-        currentKeyPointerRecords:(NSUInteger)expectedCurrentKeyRecords
-                 tlkShareRecords:(NSUInteger)expectedTLKShareRecords
-                          zoneID:(CKRecordZoneID*) zoneID {
 
+- (void)expectCKModifyRecords:(NSDictionary<NSString*, NSNumber*>*)expectedRecordTypeCounts
+      deletedRecordTypeCounts:(NSDictionary<NSString*, NSNumber*>*)expectedDeletedRecordTypeCounts
+                       zoneID:(CKRecordZoneID*)zoneID
+          checkModifiedRecord:(BOOL (^)(CKRecord*))checkRecord
+         runAfterModification:(void (^) ())afterModification
+{
     __weak __typeof(self) weakSelf = self;
-    [self expectCKModifyRecords: @{
-                                   SecCKRecordIntermediateKeyType: [NSNumber numberWithUnsignedInteger: expectedNumberOfRecords],
-                                   SecCKRecordCurrentKeyType: [NSNumber numberWithUnsignedInteger: expectedCurrentKeyRecords],
-                                   SecCKRecordTLKShareType: [NSNumber numberWithUnsignedInteger:(SecCKKSShareTLKs() ?  expectedTLKShareRecords : 0)],
-                                   }
-        deletedRecordTypeCounts:nil
-                         zoneID:zoneID
-            checkModifiedRecord:nil
-           runAfterModification:^{
-               __strong __typeof(weakSelf) strongSelf = weakSelf;
-               XCTAssertNotNil(strongSelf, "self exists");
-
-               // Reach into our cloudkit database and extract the keys
-               CKRecordID* currentTLKPointerID =    [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassTLK zoneID:zoneID];
-               CKRecordID* currentClassAPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassA   zoneID:zoneID];
-               CKRecordID* currentClassCPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassC   zoneID:zoneID];
-
-               ZoneKeys* zonekeys = strongSelf.keys[zoneID];
-               if(!zonekeys) {
-                   zonekeys = [[ZoneKeys alloc] init];
-                   strongSelf.keys[zoneID] = zonekeys;
-               }
-
-               XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID],    "Have a currentTLKPointer");
-               XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID], "Have a currentClassAPointer");
-               XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID], "Have a currentClassCPointer");
-               XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey],    "Have a currentTLKPointer parent");
-               XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassAPointer parent");
-               XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassCPointer parent");
-               XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey] recordID].recordName,    "Have a currentTLKPointer parent UUID");
-               XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassAPointer parent UUID");
-               XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassCPointer parent UUID");
-
-               zonekeys.currentTLKPointer =    [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID]];
-               zonekeys.currentClassAPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID]];
-               zonekeys.currentClassCPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID]];
-
-               XCTAssertNotNil(zonekeys.currentTLKPointer.currentKeyUUID,    "Have a currentTLKPointer current UUID");
-               XCTAssertNotNil(zonekeys.currentClassAPointer.currentKeyUUID, "Have a currentClassAPointer current UUID");
-               XCTAssertNotNil(zonekeys.currentClassCPointer.currentKeyUUID, "Have a currentClassCPointer current UUID");
-
-               CKRecordID* currentTLKID =    [[CKRecordID alloc] initWithRecordName:zonekeys.currentTLKPointer.currentKeyUUID    zoneID:zoneID];
-               CKRecordID* currentClassAID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassAPointer.currentKeyUUID zoneID:zoneID];
-               CKRecordID* currentClassCID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassCPointer.currentKeyUUID zoneID:zoneID];
-
-               zonekeys.tlk =    [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKID]];
-               zonekeys.classA = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAID]];
-               zonekeys.classC = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCID]];
-
-               XCTAssertNotNil(zonekeys.tlk, "Have the current TLK");
-               XCTAssertNotNil(zonekeys.classA, "Have the current Class A key");
-               XCTAssertNotNil(zonekeys.classC, "Have the current Class C key");
-
-               NSMutableArray<CKKSTLKShare*>* shares = [NSMutableArray array];
-               for(CKRecordID* recordID in strongSelf.zones[zoneID].currentDatabase.allKeys) {
-                   if([recordID.recordName hasPrefix: [CKKSTLKShare ckrecordPrefix]]) {
-                       CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:strongSelf.zones[zoneID].currentDatabase[recordID]];
-                       XCTAssertNotNil(share, "Should be able to parse a CKKSTLKShare CKRecord into a CKKSTLKShare");
-                       [shares addObject:share];
-                   }
-               }
-               zonekeys.tlkShares = shares;
-           }];
+    [super expectCKModifyRecords:expectedRecordTypeCounts
+         deletedRecordTypeCounts:expectedDeletedRecordTypeCounts
+                          zoneID:zoneID
+             checkModifiedRecord:checkRecord
+            runAfterModification:^{
+                __strong __typeof(weakSelf) strongSelf = weakSelf;
+                XCTAssertNotNil(strongSelf, "self exists");
+
+                // Reach into our cloudkit database and extract the keys
+                CKRecordID* currentTLKPointerID =    [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassTLK zoneID:zoneID];
+                CKRecordID* currentClassAPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassA   zoneID:zoneID];
+                CKRecordID* currentClassCPointerID = [[CKRecordID alloc] initWithRecordName:SecCKKSKeyClassC   zoneID:zoneID];
+
+                ZoneKeys* zonekeys = strongSelf.keys[zoneID];
+                if(!zonekeys) {
+                    zonekeys = [[ZoneKeys alloc] init];
+                    strongSelf.keys[zoneID] = zonekeys;
+                }
+
+                XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID],    "Have a currentTLKPointer");
+                XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID], "Have a currentClassAPointer");
+                XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID], "Have a currentClassCPointer");
+                XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey],    "Have a currentTLKPointer parent");
+                XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassAPointer parent");
+                XCTAssertNotNil(strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey], "Have a currentClassCPointer parent");
+                XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID][SecCKRecordParentKeyRefKey] recordID].recordName,    "Have a currentTLKPointer parent UUID");
+                XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassAPointer parent UUID");
+                XCTAssertNotNil([strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID][SecCKRecordParentKeyRefKey] recordID].recordName, "Have a currentClassCPointer parent UUID");
+
+                zonekeys.currentTLKPointer =    [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKPointerID]];
+                zonekeys.currentClassAPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAPointerID]];
+                zonekeys.currentClassCPointer = [[CKKSCurrentKeyPointer alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCPointerID]];
+
+                XCTAssertNotNil(zonekeys.currentTLKPointer.currentKeyUUID,    "Have a currentTLKPointer current UUID");
+                XCTAssertNotNil(zonekeys.currentClassAPointer.currentKeyUUID, "Have a currentClassAPointer current UUID");
+                XCTAssertNotNil(zonekeys.currentClassCPointer.currentKeyUUID, "Have a currentClassCPointer current UUID");
+
+                CKRecordID* currentTLKID =    [[CKRecordID alloc] initWithRecordName:zonekeys.currentTLKPointer.currentKeyUUID    zoneID:zoneID];
+                CKRecordID* currentClassAID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassAPointer.currentKeyUUID zoneID:zoneID];
+                CKRecordID* currentClassCID = [[CKRecordID alloc] initWithRecordName:zonekeys.currentClassCPointer.currentKeyUUID zoneID:zoneID];
+
+                zonekeys.tlk =    [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentTLKID]];
+                zonekeys.classA = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassAID]];
+                zonekeys.classC = [[CKKSKey alloc] initWithCKRecord: strongSelf.zones[zoneID].currentDatabase[currentClassCID]];
+
+                XCTAssertNotNil(zonekeys.tlk, "Have the current TLK");
+                XCTAssertNotNil(zonekeys.classA, "Have the current Class A key");
+                XCTAssertNotNil(zonekeys.classC, "Have the current Class C key");
+
+                NSMutableArray<CKKSTLKShare*>* shares = [NSMutableArray array];
+                for(CKRecordID* recordID in strongSelf.zones[zoneID].currentDatabase.allKeys) {
+                    if([recordID.recordName hasPrefix: [CKKSTLKShare ckrecordPrefix]]) {
+                        CKKSTLKShare* share = [[CKKSTLKShare alloc] initWithCKRecord:strongSelf.zones[zoneID].currentDatabase[recordID]];
+                        XCTAssertNotNil(share, "Should be able to parse a CKKSTLKShare CKRecord into a CKKSTLKShare");
+                        [shares addObject:share];
+                    }
+                }
+                zonekeys.tlkShares = shares;
+
+                if(afterModification) {
+                    afterModification();
+                }
+            }];
 }
 
 - (void)expectCKReceiveSyncKeyHierarchyError:(CKRecordZoneID*)zoneID {
@@ -806,7 +821,7 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
     return [self newRecord:recordID withNewItemData:dictionary key:zonekeys.classC];
 }
 
-- (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary key:(CKKSKey*)key {
+- (CKKSItem*)newItem:(CKRecordID*)recordID withNewItemData:(NSDictionary*)dictionary key:(CKKSKey*)key {
     NSError* error = nil;
     CKKSItem* cipheritem = [CKKSItemEncrypter encryptCKKSItem:[[CKKSItem alloc] initWithUUID:recordID.recordName
                                                                                parentKeyUUID:key.uuid
@@ -818,7 +833,13 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
     XCTAssertNil(error, "encrypted item with class c key");
     XCTAssertNotNil(cipheritem, "Have an encrypted item");
 
-    CKRecord* ckr = [cipheritem CKRecordWithZoneID: recordID.zoneID];
+    return cipheritem;
+}
+
+- (CKRecord*)newRecord: (CKRecordID*) recordID withNewItemData:(NSDictionary*) dictionary key:(CKKSKey*)key {
+    CKKSItem* item = [self newItem:recordID withNewItemData:dictionary key:key];
+
+    CKRecord* ckr = [item CKRecordWithZoneID: recordID.zoneID];
     XCTAssertNotNil(ckr, "Created a CKRecord");
     return ckr;
 }
@@ -943,6 +964,10 @@ static CFDictionaryRef SOSCreatePeerGestaltFromName(CFStringRef name)
     }];
 }
 
+- (void)expectCKKSTLKSelfShareUpload:(CKRecordZoneID*)zoneID {
+    [self expectCKModifyKeyRecords:0 currentKeyPointerRecords:0 tlkShareRecords:1 zoneID:zoneID];
+}
+
 - (void)checkNSyncableTLKsInKeychain:(size_t)n {
     NSDictionary *query = @{(id)kSecClass : (id)kSecClassInternetPassword,
                             (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
index ea3519f0349238dfd0c9d1fe07bc865b251a2d8b..59dba40a643165d3cc00c869c04dd9363ec9ad1e 100644 (file)
  * @APPLE_LICENSE_HEADER_END@
  */
 
-#import <XCTest/XCTest.h>
 #import <CloudKit/CloudKit.h>
 #import <CloudKit/CloudKit_Private.h>
+#import <XCTest/XCTest.h>
 
 #import <Foundation/Foundation.h>
 
-#import "keychain/ckks/tests/MockCloudKit.h"
 #import "keychain/ckks/CKKSCKAccountStateTracker.h"
+#import "keychain/ckks/tests/MockCloudKit.h"
+
+NS_ASSUME_NONNULL_BEGIN
 
 @class CKKSKey;
 @class CKKSCKRecordHolder;
 
 @interface CloudKitMockXCTest : XCTestCase
 
-#if OCTAGON
-
 @property CKRecordZoneID* testZoneID;
 
-@property id mockDatabase;
-@property id mockContainer;
-@property id mockFakeCKModifyRecordZonesOperation;
-@property id mockFakeCKModifySubscriptionsOperation;
-@property id mockFakeCKFetchRecordZoneChangesOperation;
-@property id mockFakeCKFetchRecordsOperation;
-@property id mockFakeCKQueryOperation;
+@property (nullable) id mockDatabase;
+@property (nullable) id mockDatabaseExceptionCatcher;
+@property (nullable) id mockContainer;
+@property (nullable) id mockFakeCKModifyRecordZonesOperation;
+@property (nullable) id mockFakeCKModifySubscriptionsOperation;
+@property (nullable) id mockFakeCKFetchRecordZoneChangesOperation;
+@property (nullable) id mockFakeCKFetchRecordsOperation;
+@property (nullable) id mockFakeCKQueryOperation;
 
-@property id mockAccountStateTracker;
+@property (nullable) id mockAccountStateTracker;
 
 @property CKAccountStatus accountStatus;
 @property BOOL supportsDeviceToDeviceEncryption;
 
 @property NSString* circlePeerID;
 
-@property bool aksLockState; // The current 'AKS lock state'
+@property bool aksLockState;  // The current 'AKS lock state'
 @property (readonly) CKKSLockStateTracker* lockStateTracker;
-@property id mockLockStateTracker;
+@property (nullable) id mockLockStateTracker;
 
-@property NSMutableDictionary<CKRecordZoneID*, FakeCKZone*>* zones;
+@property (nullable) NSMutableDictionary<CKRecordZoneID*, FakeCKZone*>* zones;
 
-@property NSOperationQueue* operationQueue;
-@property NSBlockOperation* ckksHoldOperation;
-@property NSBlockOperation* ckaccountHoldOperation;
+@property (nullable) NSOperationQueue* operationQueue;
+@property (nullable) NSBlockOperation* ckaccountHoldOperation;
 
-@property NSBlockOperation* ckModifyHoldOperation;
-@property NSBlockOperation* ckFetchHoldOperation;
+@property (nullable) NSBlockOperation* ckModifyHoldOperation;
+@property (nullable) NSBlockOperation* ckFetchHoldOperation;
 
 @property bool silentFetchesAllowed;
 
-@property id mockCKKSViewManager;
-@property CKKSViewManager* injectedManager;
+@property (nullable) id mockCKKSViewManager;
+@property (nullable) CKKSViewManager* injectedManager;
 
-- (CKKSKey*) fakeTLK: (CKRecordZoneID*)zoneID;
+- (CKKSKey*)fakeTLK:(CKRecordZoneID*)zoneID;
 
-- (void)expectCKModifyItemRecords: (NSUInteger) expectedNumberOfRecords
-         currentKeyPointerRecords: (NSUInteger) expectedCurrentKeyRecords
-                           zoneID: (CKRecordZoneID*) zoneID;
-- (void)expectCKModifyItemRecords: (NSUInteger) expectedNumberOfRecords
-         currentKeyPointerRecords: (NSUInteger) expectedCurrentKeyRecords
-                           zoneID: (CKRecordZoneID*) zoneID
-                        checkItem: (BOOL (^)(CKRecord*)) checkItem;
+- (void)expectCKModifyItemRecords:(NSUInteger)expectedNumberOfRecords
+         currentKeyPointerRecords:(NSUInteger)expectedCurrentKeyRecords
+                           zoneID:(CKRecordZoneID*)zoneID;
+- (void)expectCKModifyItemRecords:(NSUInteger)expectedNumberOfRecords
+         currentKeyPointerRecords:(NSUInteger)expectedCurrentKeyRecords
+                           zoneID:(CKRecordZoneID*)zoneID
+                        checkItem:(BOOL (^_Nullable)(CKRecord*))checkItem;
 
 - (void)expectCKModifyItemRecords:(NSUInteger)expectedNumberOfModifiedRecords
                    deletedRecords:(NSUInteger)expectedNumberOfDeletedRecords
          currentKeyPointerRecords:(NSUInteger)expectedCurrentKeyRecords
                            zoneID:(CKRecordZoneID*)zoneID
-                        checkItem:(BOOL (^)(CKRecord*))checkItem;
+                        checkItem:(BOOL (^_Nullable)(CKRecord*))checkItem;
 
-- (void)expectCKDeleteItemRecords: (NSUInteger) expectedNumberOfRecords zoneID: (CKRecordZoneID*) zoneID;
+- (void)expectCKDeleteItemRecords:(NSUInteger)expectedNumberOfRecords zoneID:(CKRecordZoneID*)zoneID;
 
 - (void)expectCKModifyKeyRecords:(NSUInteger)expectedNumberOfRecords
         currentKeyPointerRecords:(NSUInteger)expectedCurrentKeyRecords
                  tlkShareRecords:(NSUInteger)expectedTLKShareRecords
                           zoneID:(CKRecordZoneID*)zoneID;
 
-- (void)expectCKModifyRecords:(NSDictionary<NSString*, NSNumber*>*) expectedRecordTypeCounts
-      deletedRecordTypeCounts:(NSDictionary<NSString*, NSNumber*>*expectedDeletedRecordTypeCounts
-                       zoneID:(CKRecordZoneID*) zoneID
-          checkModifiedRecord:(BOOL (^)(CKRecord*)) checkRecord
-         runAfterModification:(void (^())afterModification;
+- (void)expectCKModifyRecords:(NSDictionary<NSString*, NSNumber*>*)expectedRecordTypeCounts
+      deletedRecordTypeCounts:(NSDictionary<NSString*, NSNumber*>* _Nullable)expectedDeletedRecordTypeCounts
+                       zoneID:(CKRecordZoneID*)zoneID
+          checkModifiedRecord:(BOOL (^_Nullable)(CKRecord*))checkRecord
+         runAfterModification:(void (^_Nullable)())afterModification;
 
 - (void)failNextCKAtomicModifyItemRecordsUpdateFailure:(CKRecordZoneID*)zoneID;
-- (void)failNextCKAtomicModifyItemRecordsUpdateFailure:(CKRecordZoneID*)zoneID blockAfterReject: (void (^)())blockAfterReject;
-- (void)failNextCKAtomicModifyItemRecordsUpdateFailure:(CKRecordZoneID*)zoneID blockAfterReject: (void (^)())blockAfterReject withError:(NSError*)error;
-- (void)expectCKAtomicModifyItemRecordsUpdateFailure: (CKRecordZoneID*) zoneID;
+- (void)failNextCKAtomicModifyItemRecordsUpdateFailure:(CKRecordZoneID*)zoneID
+                                      blockAfterReject:(void (^_Nullable)())blockAfterReject;
+- (void)failNextCKAtomicModifyItemRecordsUpdateFailure:(CKRecordZoneID*)zoneID
+                                      blockAfterReject:(void (^_Nullable)())blockAfterReject
+                                             withError:(NSError* _Nullable)error;
+- (void)expectCKAtomicModifyItemRecordsUpdateFailure:(CKRecordZoneID*)zoneID;
 
 - (void)failNextZoneCreation:(CKRecordZoneID*)zoneID;
 - (void)failNextZoneCreationSilently:(CKRecordZoneID*)zoneID;
 
 // Use this to 1) assert that a fetch occurs and 2) cause a block to run _after_ all changes have been delivered but _before_ the fetch 'completes'.
 // This way, you can modify the CK zone to cause later collisions.
-- (void)expectCKFetchAndRunBeforeFinished: (void (^)())blockAfterFetch;
+- (void)expectCKFetchAndRunBeforeFinished:(void (^_Nullable)())blockAfterFetch;
 
 // Use this to assert that a FakeCKFetchRecordsOperation occurs.
--(void)expectCKFetchByRecordID;
+- (void)expectCKFetchByRecordID;
 
 // Use this to assert that a FakeCKQueryOperation occurs.
 - (void)expectCKFetchByQuery;
 // Wait until all scheduled cloudkit operations are reflected in the currentDatabase
 - (void)waitForCKModifications;
 
-// Unblocks the CKKS subsystem only.
-- (void)startCKKSSubsystemOnly;
-
 // Unblocks the CKAccount mock subsystem. Until this is called, the tests believe cloudd hasn't returned any account status yet.
 - (void)startCKAccountStatusMock;
 
 - (void)startCKKSSubsystem;
 
 // Blocks the completion (partial or full) of CloudKit modifications
--(void)holdCloudKitModifications;
+- (void)holdCloudKitModifications;
 
 // Unblocks the hold you've added with holdCloudKitModifications; CloudKit modifications will finish
--(void)releaseCloudKitModificationHold;
+- (void)releaseCloudKitModificationHold;
 
 // Blocks the CloudKit fetches from beginning (similar to network latency)
--(void)holdCloudKitFetches;
+- (void)holdCloudKitFetches;
 // Unblocks the hold you've added with holdCloudKitFetches; CloudKit fetches will finish
--(void)releaseCloudKitFetchHold;
+- (void)releaseCloudKitFetchHold;
 
 // Make a CK internal server extension error with a given code and description.
 - (NSError*)ckInternalServerExtensionError:(NSInteger)code description:(NSString*)desc;
 
 // Schedule an operation for execution (and failure), with some existing record errors.
 // Other records in the operation but not in failedRecords will have CKErrorBatchRequestFailed errors created.
--(void)rejectWrite:(CKModifyRecordsOperation*)op failedRecords:(NSMutableDictionary<CKRecordID*, NSError*>*)failedRecords;
+- (void)rejectWrite:(CKModifyRecordsOperation*)op failedRecords:(NSMutableDictionary<CKRecordID*, NSError*>*)failedRecords;
 
-#endif // OCTAGON
 @end
+
+NS_ASSUME_NONNULL_END
index e0af138d62753774c49f0502b2cff8618dc1d260..56a59241e95de008c7fb10a19c7154b803ac3d5f 100644 (file)
 - (void)setUp {
     [super setUp];
 
+    NSString* testName = [self.name componentsSeparatedByString:@" "][1];
+    testName = [testName stringByReplacingOccurrencesOfString:@"]" withString:@""];
+    secnotice("ckkstest", "Beginning test %@", testName);
+
     // All tests start with the same flag set.
     SecCKKSTestResetFlags();
     SecCKKSTestSetDisableSOS(true);
@@ -95,6 +99,7 @@
 
     self.zones = [[NSMutableDictionary alloc] init];
 
+    self.mockDatabaseExceptionCatcher = OCMStrictClassMock([CKDatabase class]);
     self.mockDatabase = OCMStrictClassMock([CKDatabase class]);
     self.mockContainer = OCMClassMock([CKContainer class]);
     OCMStub([self.mockContainer containerWithIdentifier:[OCMArg isKindOfClass:[NSString class]]]).andReturn(self.mockContainer);
     OCMStub([self.mockContainer alloc]).andReturn(self.mockContainer);
     OCMStub([self.mockContainer containerIdentifier]).andReturn(SecCKKSContainerName);
     OCMStub([self.mockContainer initWithContainerID: [OCMArg any] options: [OCMArg any]]).andReturn(self.mockContainer);
-    OCMStub([self.mockContainer privateCloudDatabase]).andReturn(self.mockDatabase);
+    OCMStub([self.mockContainer privateCloudDatabase]).andReturn(self.mockDatabaseExceptionCatcher);
     OCMStub([self.mockContainer serverPreferredPushEnvironmentWithCompletionHandler: ([OCMArg invokeBlockWithArgs:@"fake APS push string", [NSNull null], nil])]);
 
+    // Use two layers of mockDatabase here, so we can both add Expectations and catch the exception (instead of crash) when one fails.
+    OCMStub([self.mockDatabaseExceptionCatcher addOperation:[OCMArg any]]).andCall(self, @selector(ckdatabaseAddOperation:));
+
     // If you want to change this, you'll need to update the mock
-    _ckDeviceID = @"fake-cloudkit-device-id";
+    _ckDeviceID = [NSString stringWithFormat:@"fake-cloudkit-device-id-%@", testName];
     OCMStub([self.mockContainer fetchCurrentDeviceIDWithCompletionHandler: ([OCMArg invokeBlockWithArgs:self.ckDeviceID, [NSNull null], nil])]);
 
     self.accountStatus = CKAccountStatusAvailable;
     self.supportsDeviceToDeviceEncryption = YES;
 
-    // Inject a fake operation dependency into the manager object, so that the tests can perform setup and mock expectations before zone setup begins
-    // Also blocks all CK account state retrieval operations (but not circle status ones)
+    // Inject a fake operation dependency so we won't respond with the CloudKit account status immediately
+    // The CKKSCKAccountStateTracker won't send any login/logout calls without that information, so this blocks all CKKS setup
     self.ckaccountHoldOperation = [NSBlockOperation named:@"ckaccount-hold" withBlock:^{
         secnotice("ckks", "CKKS CK account status test hold released");
     }];
     OCMStub([self.mockAccountStateTracker getCircleStatus]).andCall(self, @selector(circleStatus));
 
     // If we're in circle, come up with a fake circle id. Otherwise, return an error.
-    self.circlePeerID = @"fake-circle-id";
+    self.circlePeerID = [NSString stringWithFormat:@"fake-circle-id-%@", testName];
     OCMStub([self.mockAccountStateTracker fetchCirclePeerID:
              [OCMArg checkWithBlock:^BOOL(void (^passedBlock) (NSString* peerID,
                                                                NSError * error)) {
 
     self.testZoneID = [[CKRecordZoneID alloc] initWithZoneName:@"testzone" ownerName:CKCurrentUserDefaultName];
 
-    // Inject a fake operation dependency into the manager object, so that the tests can perform setup and mock expectations before zone setup begins
-    // Also blocks all CK account state retrieval operations (but not circle status ones)
-    self.ckksHoldOperation = [[NSBlockOperation alloc] init];
-    [self.ckksHoldOperation addExecutionBlock:^{
-        secnotice("ckks", "CKKS testing hold released");
-    }];
-    self.ckksHoldOperation.name = @"ckks-hold";
-
-    //self.mockCKKSViewManagerClass = OCMClassMock([CKKSViewManager class]);
-
     // We don't want to use class mocks here, because they don't play well with partial mocks
     self.mockCKKSViewManager = OCMPartialMock(
         [[CKKSViewManager alloc] initWithContainerName:SecCKKSContainerName
                        modifyRecordZonesOperationClass:[FakeCKModifyRecordZonesOperation class]
                                     apsConnectionClass:[FakeAPSConnection class]
                              nsnotificationCenterClass:[FakeNSNotificationCenter class]
-                                         notifierClass:[FakeCKKSNotifier class]
-                                             setupHold:self.ckksHoldOperation]);
+                                         notifierClass:[FakeCKKSNotifier class]]);
 
     OCMStub([self.mockCKKSViewManager viewList]).andCall(self, @selector(managedViewList));
     OCMStub([self.mockCKKSViewManager syncBackupAndNotifyAboutSync]);
     [CKKSViewManager resetManager:false setTo:self.injectedManager];
 
     // Make a new fake keychain
-    NSString* smallName = [self.name componentsSeparatedByString:@" "][1];
-    smallName = [smallName stringByReplacingOccurrencesOfString:@"]" withString:@""];
-
-    NSString* tmp_dir = [NSString stringWithFormat: @"/tmp/%@.%X", smallName, arc4random()];
+    NSString* tmp_dir = [NSString stringWithFormat: @"/tmp/%@.%X", testName, arc4random()];
     [[NSFileManager defaultManager] createDirectoryAtPath:[NSString stringWithFormat: @"%@/Library/Keychains", tmp_dir] withIntermediateDirectories:YES attributes:nil error:NULL];
 
     SetCustomHomeURLString((__bridge CFStringRef) tmp_dir);
     kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; });
 }
 
+- (void)ckdatabaseAddOperation:(NSOperation*)op {
+    @try {
+        [self.mockDatabase addOperation:op];
+    } @catch (NSException *exception) {
+        XCTFail("Received an database exception: %@", exception);
+    }
+}
+
 -(CKKSCKAccountStateTracker*)accountStateTracker {
     return self.injectedManager.accountTracker;
 }
 
 - (void)startCKKSSubsystem {
     [self startCKAccountStatusMock];
-    [self startCKKSSubsystemOnly];
-}
-
-- (void)startCKKSSubsystemOnly {
-    // Note: currently, based on how we're mocking up the zone creation and zone subscription operation,
-    // they will 'fire' before this method is called. It's harmless, since the mocks immediately succeed
-    // and return; it's just a tad confusing.
-    if([self.ckksHoldOperation isPending]) {
-        [self.operationQueue addOperation: self.ckksHoldOperation];
-    }
 }
 
 - (void)startCKAccountStatusMock {
                     return NO;
                 }
 
-                if([zone errorFromSavingRecord: record]) {
-                    secnotice("fakecloudkit", "Record zone rejected record write: %@", record);
+                NSError* recordError = [zone errorFromSavingRecord: record];
+                if(recordError) {
+                    secnotice("fakecloudkit", "Record zone rejected record write: %@ %@", recordError, record);
+                    XCTFail(@"Record zone rejected record write: %@ %@", recordError, record);
                     return NO;
                 }
 
                             [zone deleteCKRecordIDFromZone: recordID];
                         }
 
-                        op.modifyRecordsCompletionBlock(savedRecords, op.recordIDsToDelete, nil);
-
                         if(afterModification) {
                             afterModification();
                         }
 
+                        op.modifyRecordsCompletionBlock(savedRecords, op.recordIDsToDelete, nil);
                         op.isFinished = YES;
                     }
                 }];
 }
 
 - (void)tearDown {
-    // Put teardown code here. This method is called after the invocation of each test method in the class.
+    NSString* testName = [self.name componentsSeparatedByString:@" "][1];
+    testName = [testName stringByReplacingOccurrencesOfString:@"]" withString:@""];
+    secnotice("ckkstest", "Ending test %@", testName);
 
     if(SecCKKSIsEnabled()) {
-        // Ensure we don't have any blocking operations
-        self.accountStatus = CKAccountStatusNoAccount;
-        [self startCKKSSubsystem];
+        self.accountStatus = CKAccountStatusCouldNotDetermine;
+
+        [self.ckaccountHoldOperation cancel];
+        self.ckaccountHoldOperation = nil;
 
+        // Ensure we don't have any blocking operations left
+        [self.operationQueue cancelAllOperations];
         [self waitForCKModifications];
 
         XCTAssertEqual(0, [self.injectedManager.completedSecCKKSInitialize wait:2*NSEC_PER_SEC],
             "Timeout did not occur waiting for SecCKKSInitialize");
 
         // Make sure this happens before teardown.
-        XCTAssertEqual(0, [self.accountStateTracker.finishedInitialCalls wait:1*NSEC_PER_SEC], "Account state tracker initialized itself");
+        XCTAssertEqual(0, [self.accountStateTracker.finishedInitialDispatches wait:1*NSEC_PER_SEC], "Account state tracker initialized itself");
+
+        dispatch_group_t accountChangesDelivered = [self.accountStateTracker checkForAllDeliveries];
+        XCTAssertEqual(0, dispatch_group_wait(accountChangesDelivered, dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC)), "Account state tracker finished delivering everything");
     }
 
     [super tearDown];
     [self.mockDatabase stopMocking];
     self.mockDatabase = nil;
 
+    [self.mockDatabaseExceptionCatcher stopMocking];
+    self.mockDatabaseExceptionCatcher = nil;
+
     [self.mockContainer stopMocking];
     self.mockContainer = nil;
 
     self.zones = nil;
+
     self.operationQueue = nil;
-    self.ckksHoldOperation = nil;
-    self.ckaccountHoldOperation = nil;
 
     SecCKKSTestResetFlags();
 }
index 9b96a083b62aeb48cb1e6839cd26c8c001abc9d0..5bc654fd4b7256ac1317e10aec384cf8b4a782a6 100644 (file)
  */
 
 
-#import <Foundation/Foundation.h>
 #import <CloudKit/CloudKit.h>
+#import <Foundation/Foundation.h>
 
-#import "keychain/ckks/CloudKitDependencies.h"
 #import "keychain/ckks/CKKSNotifier.h"
+#import "keychain/ckks/CloudKitDependencies.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -38,21 +38,20 @@ typedef NSMutableDictionary<CKRecordZoneID*, FakeCKZone*> FakeCKDatabase;
 
 @interface FakeCKModifyRecordZonesOperation : NSBlockOperation <CKKSModifyRecordZonesOperation>
 @property (nullable) NSError* creationError;
-@property (nonatomic, nullable) NSMutableArray<CKRecordZone *>* recordZonesSaved;
-@property (nonatomic, nullable) NSMutableArray<CKRecordZoneID *>* recordZoneIDsDeleted;
-+(FakeCKDatabase*) ckdb;
+@property (nonatomic, nullable) NSMutableArray<CKRecordZone*>* recordZonesSaved;
+@property (nonatomic, nullable) NSMutableArray<CKRecordZoneID*>* recordZoneIDsDeleted;
++ (FakeCKDatabase*)ckdb;
 @end
 
 @interface FakeCKModifySubscriptionsOperation : NSBlockOperation <CKKSModifySubscriptionsOperation>
 @property (nullable) NSError* subscriptionError;
-@property (nonatomic, nullable) NSMutableArray<CKSubscription *> *subscriptionsSaved;
-@property (nonatomic, nullable) NSMutableArray<NSString *> *subscriptionIDsDeleted;
-+(FakeCKDatabase*) ckdb;
+@property (nonatomic, nullable) NSMutableArray<CKSubscription*>* subscriptionsSaved;
+@property (nonatomic, nullable) NSMutableArray<NSString*>* subscriptionIDsDeleted;
++ (FakeCKDatabase*)ckdb;
 @end
 
-
 @interface FakeCKFetchRecordZoneChangesOperation : NSOperation <CKKSFetchRecordZoneChangesOperation>
-+(FakeCKDatabase*) ckdb;
++ (FakeCKDatabase*)ckdb;
 @property (nullable) void (^blockAfterFetch)();
 @end
 
@@ -64,24 +63,21 @@ typedef NSMutableDictionary<CKRecordZoneID*, FakeCKZone*> FakeCKDatabase;
 + (FakeCKDatabase*)ckdb;
 @end
 
-
 @interface FakeAPSConnection : NSObject <CKKSAPSConnection>
 @end
 
-
-@interface FakeNSNotificationCenter : NSObject<CKKSNSNotificationCenter>
+@interface FakeNSNotificationCenter : NSObject <CKKSNSNotificationCenter>
 + (instancetype)defaultCenter;
 - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
 @end
 
-
 @interface FakeCKZone : NSObject
 // Used while mocking: database is the contents of the current current CloudKit database, pastDatabase is the state of the world in the past at different change tokens
 @property CKRecordZoneID* zoneID;
 @property CKServerChangeToken* currentChangeToken;
 @property NSMutableDictionary<CKRecordID*, CKRecord*>* currentDatabase;
-@property NSMutableDictionary<CKServerChangeToken*,  NSMutableDictionary<CKRecordID*, CKRecord*>*>* pastDatabases;
-@property bool flag; // used however you'd like in a test
+@property NSMutableDictionary<CKServerChangeToken*, NSMutableDictionary<CKRecordID*, CKRecord*>*>* pastDatabases;
+@property bool flag;  // used however you'd like in a test
 
 // Usually nil. If set, trying to 'create' this zone should fail.
 @property (nullable) NSError* creationError;
@@ -92,25 +88,28 @@ typedef NSMutableDictionary<CKRecordZoneID*, FakeCKZone*> FakeCKDatabase;
 // Usually nil. If set, trying to subscribe to this zone should fail.
 @property (nullable) NSError* subscriptionError;
 
-- (instancetype)initZone: (CKRecordZoneID*) zoneID;
+- (instancetype)initZone:(CKRecordZoneID*)zoneID;
 
 - (void)rollChangeToken;
 
 // Always Succeed
-- (void)addToZone: (CKKSCKRecordHolder*) item zoneID: (CKRecordZoneID*) zoneID;
-- (void)addToZone: (CKRecord*) record;
+- (void)addToZone:(CKKSCKRecordHolder*)item zoneID:(CKRecordZoneID*)zoneID;
+- (void)addToZone:(CKRecord*)record;
+
+// Removes this record from all versions of the CK database, without changing the change tag
+- (void)deleteFromHistory:(CKRecordID*)recordID;
 
-- (void)addCKRecordToZone: (CKRecord*) record;
-- (NSError* _Nullable)deleteCKRecordIDFromZone:(CKRecordID*) recordID;
+- (void)addCKRecordToZone:(CKRecord*)record;
+- (NSError* _Nullable)deleteCKRecordIDFromZone:(CKRecordID*)recordID;
 
 // Sets up the next fetchChanges to fail with this error
-- (void)failNextFetchWith: (NSError*) fetchChangesError;
+- (void)failNextFetchWith:(NSError*)fetchChangesError;
 
 // Get the next fetchChanges error. Returns NULL if the fetchChanges should succeed.
-- (NSError * _Nullable)popFetchChangesError;
+- (NSError* _Nullable)popFetchChangesError;
 
 // Checks if this record add/modification should fail
-- (NSError * _Nullable)errorFromSavingRecord:(CKRecord*) record;
+- (NSError* _Nullable)errorFromSavingRecord:(CKRecord*)record;
 @end
 
 @interface FakeCKKSNotifier : NSObject <CKKSNotifier>
index 8a06f416b0949ac76777e5fc0dfa337df9c758e8..abaada9305794fe1521d21d82c6179a463a1b1ff 100644 (file)
 - (void)addToZone: (CKKSCKRecordHolder*) item zoneID: (CKRecordZoneID*) zoneID {
     CKRecord* record = [item CKRecordWithZoneID: zoneID];
     [self addToZone: record];
+
+    // Save off the etag
+    item.storedCKRecord = record;
 }
 
 - (void)addToZone: (CKRecord*) record {
     [self addToZone: record];
 }
 
+- (void)deleteFromHistory:(CKRecordID*)recordID {
+    for(NSMutableDictionary* pastDatabase in self.pastDatabases.objectEnumerator) {
+        [pastDatabase removeObjectForKey:recordID];
+    }
+    [self.currentDatabase removeObjectForKey:recordID];
+}
+
+
 - (NSError*)deleteCKRecordIDFromZone:(CKRecordID*) recordID {
     // todo: fail somehow