]> git.saurik.com Git - apple/security.git/blobdiff - keychain/ckks/CKKSHealTLKSharesOperation.m
Security-59306.41.2.tar.gz
[apple/security.git] / keychain / ckks / CKKSHealTLKSharesOperation.m
index 97d0cb2d2bc1ac68b81352c1fccde5d5711229a5..a407f46619a970713591d27fd5a5125f30f49e8b 100644 (file)
 
 #if OCTAGON
 
+#import <CloudKit/CloudKit.h>
+#import <CloudKit/CloudKit_Private.h>
+
 #import "keychain/ckks/CKKSKeychainView.h"
 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
 #import "keychain/ckks/CKKSKey.h"
 #import "keychain/ckks/CKKSHealTLKSharesOperation.h"
 #import "keychain/ckks/CKKSGroupOperation.h"
-#import "keychain/ckks/CKKSTLKShare.h"
+#import "keychain/ckks/CKKSTLKShareRecord.h"
+#import "keychain/ot/ObjCImprovements.h"
+
+#import "CKKSPowerCollection.h"
 
 @interface CKKSHealTLKSharesOperation ()
 @property NSBlockOperation* cloudkitModifyOperationFinished;
@@ -55,7 +61,7 @@
      * Attempt to figure out what it is, and what we can do about it.
      */
 
-    __weak __typeof(self) weakSelf = self;
+    WEAKIFY(self);
 
     CKKSKeychainView* ckks = self.ckks;
     if(!ckks) {
@@ -76,7 +82,7 @@
 
         NSError* error = nil;
 
-        CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] initForZone:ckks.zoneID];
+        CKKSCurrentKeySet* keyset = [CKKSCurrentKeySet loadForZone:ckks.zoneID];
 
         if(keyset.error) {
             self.error = keyset.error;
             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);
             return true;
         }
 
-        NSSet<CKKSTLKShare*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
+        NSSet<CKKSTLKShareRecord*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
                                                                          error:&error];
         if(error) {
             ckkserror("ckksshare", ckks, "Unable to create shares: %@", error);
+            [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
             return false;
         }
 
             return true;
         }
 
+        // Let's double-check: if we upload these TLKShares, will the world be right?
+        BOOL newSharesSufficient = [ckks _onqueueAreNewSharesSufficient:newShares
+                                                             currentTLK:keyset.tlk
+                                                                  error:&error];
+        if(!newSharesSufficient || error) {
+            ckksnotice("ckksshare", ckks, "New shares won't resolve the share issue; erroring to avoid infinite loops");
+            [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:error];
+            return true;
+        }
+
         // Fire up our CloudKit operation!
 
         NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
         NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
         NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
 
-        for(CKKSTLKShare* share in newShares) {
+        for(CKKSTLKShareRecord* share in newShares) {
             CKRecord* record = [share CKRecordWithZoneID:ckks.zoneID];
             [recordsToSave addObject: record];
             attemptedRecords[record.recordID] = record;
                                                                                           recordIDsToDelete:recordIDsToDelete];
         modifyRecordsOp.atomic = YES;
         modifyRecordsOp.longLived = NO;
-        modifyRecordsOp.timeoutIntervalForRequest = 10;
-        modifyRecordsOp.qualityOfService = NSQualityOfServiceUtility;  // relatively important. Use Utility.
+
+        // very important: get the TLKShares off-device ASAP
+        modifyRecordsOp.configuration.automaticallyRetryNetworkFailures = NO;
+        modifyRecordsOp.configuration.discretionaryNetworkBehavior = CKOperationDiscretionaryNetworkBehaviorNonDiscretionary;
+        modifyRecordsOp.configuration.isCloudKitSupportOperation = YES;
+
         modifyRecordsOp.group = self.ckoperationGroup;
         ckksnotice("ckksshare", ckks, "Operation group is %@", self.ckoperationGroup);
 
         modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
-            __strong __typeof(weakSelf) strongSelf = weakSelf;
-            __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
+            STRONGIFY(self);
+            CKKSKeychainView* blockCKKS = self.ckks;
 
             // These should all fail or succeed as one. Do the hard work in the records completion block.
             if(!error) {
-                ckksinfo("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
+                ckksnotice("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
             } else {
                 ckkserror("ckksshare", blockCKKS, "error on row: %@ %@", record.recordID, error);
             }
         };
 
         modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
-            __strong __typeof(weakSelf) strongSelf = weakSelf;
-            __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
-            if(!strongSelf) {
+            STRONGIFY(self);
+            CKKSKeychainView* strongCKKS = self.ckks;
+            if(!self) {
                 secerror("ckks: received callback for released object");
                 return;
             }
 
                     // Save the new CKRecords to the database
                     for(CKRecord* record in savedRecords) {
-                        CKKSTLKShare* savedShare = [[CKKSTLKShare alloc] initWithCKRecord:record];
-                        [savedShare saveToDatabase:&localerror];
+                        CKKSTLKShareRecord* savedShare = [[CKKSTLKShareRecord alloc] initWithCKRecord:record];
+                        bool saved = [savedShare saveToDatabase:&localerror];
 
-                        if(localerror) {
+                        if(!saved || localerror != nil) {
+                            // erroring means we were unable to save the new TLKShare records to the database. This will cause us to try to reupload them. Fail.
                             // No recovery from this, really...
                             ckkserror("ckksshare", strongCKKS, "Couldn't save new TLKShare record to database: %@", localerror);
-                            localerror = nil;
+                            [strongCKKS _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError: localerror];
+                            return true;
+
                         } else {
                             ckksnotice("ckksshare", strongCKKS, "Successfully completed upload for %@", savedShare);
                         }
             }];
 
             // Notify that we're done
-            [strongSelf.operationQueue addOperation: strongSelf.cloudkitModifyOperationFinished];
+            [self.operationQueue addOperation: self.cloudkitModifyOperationFinished];
         };
 
         [ckks.database addOperation: modifyRecordsOp];