]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSHealTLKSharesOperation.m
Security-58286.70.7.tar.gz
[apple/security.git] / keychain / ckks / CKKSHealTLKSharesOperation.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import "keychain/ckks/CKKSKeychainView.h"
27 #import "keychain/ckks/CKKSCurrentKeyPointer.h"
28 #import "keychain/ckks/CKKSKey.h"
29 #import "keychain/ckks/CKKSHealTLKSharesOperation.h"
30 #import "keychain/ckks/CKKSGroupOperation.h"
31 #import "keychain/ckks/CKKSTLKShare.h"
32
33 #import "CKKSPowerCollection.h"
34
35 @interface CKKSHealTLKSharesOperation ()
36 @property NSBlockOperation* cloudkitModifyOperationFinished;
37 @property CKOperationGroup* ckoperationGroup;
38 @end
39
40 @implementation CKKSHealTLKSharesOperation
41
42 - (instancetype)init {
43 return nil;
44 }
45 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks ckoperationGroup:(CKOperationGroup*)ckoperationGroup {
46 if(self = [super init]) {
47 _ckks = ckks;
48 _ckoperationGroup = ckoperationGroup;
49 }
50 return self;
51 }
52
53 - (void)groupStart {
54 /*
55 * We've been invoked because something is wonky with the tlk shares.
56 *
57 * Attempt to figure out what it is, and what we can do about it.
58 */
59
60 __weak __typeof(self) weakSelf = self;
61
62 CKKSKeychainView* ckks = self.ckks;
63 if(!ckks) {
64 ckkserror("ckksshare", ckks, "no CKKS object");
65 return;
66 }
67
68 if(self.cancelled) {
69 ckksnotice("ckksshare", ckks, "CKKSHealTLKSharesOperation cancelled, quitting");
70 return;
71 }
72
73 [ckks dispatchSyncWithAccountKeys: ^bool{
74 if(self.cancelled) {
75 ckksnotice("ckksshare", ckks, "CKKSHealTLKSharesOperation cancelled, quitting");
76 return false;
77 }
78
79 NSError* error = nil;
80
81 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] initForZone:ckks.zoneID];
82
83 if(keyset.error) {
84 self.error = keyset.error;
85 ckkserror("ckksshare", ckks, "couldn't load current keys: can't fix TLK shares");
86 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
87 return true;
88 } else {
89 ckksnotice("ckksshare", ckks, "Key set is %@", keyset);
90 }
91
92 [CKKSPowerCollection CKKSPowerEvent:kCKKSPowerEventTLKShareProcessing zone:ckks.zoneName];
93
94 // Okay! Perform the checks.
95 if(![keyset.tlk loadKeyMaterialFromKeychain:&error] || error) {
96 // Well, that's no good. We can't share a TLK we don't have.
97 if([ckks.lockStateTracker isLockedError: error]) {
98 ckkserror("ckksshare", ckks, "Keychain is locked: can't fix shares yet: %@", error);
99 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReadyPendingUnlock withError:nil];
100 } else {
101 // TODO go to waitfortlk
102 ckkserror("ckksshare", ckks, "couldn't load current tlk from keychain: %@", error);
103 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:nil];
104 }
105 return true;
106 }
107
108 NSSet<CKKSTLKShare*>* newShares = [ckks _onqueueCreateMissingKeyShares:keyset.tlk
109 error:&error];
110 if(error) {
111 ckkserror("ckksshare", ckks, "Unable to create shares: %@", error);
112 return false;
113 }
114
115 if(newShares.count == 0u) {
116 ckksnotice("ckksshare", ckks, "Don't believe we need to change any TLKShares, stopping");
117 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReady withError:nil];
118 return true;
119 }
120
121 // Fire up our CloudKit operation!
122
123 NSMutableArray<CKRecord *>* recordsToSave = [[NSMutableArray alloc] init];
124 NSMutableArray<CKRecordID *>* recordIDsToDelete = [[NSMutableArray alloc] init];
125 NSMutableDictionary<CKRecordID*, CKRecord*>* attemptedRecords = [[NSMutableDictionary alloc] init];
126
127 for(CKKSTLKShare* share in newShares) {
128 CKRecord* record = [share CKRecordWithZoneID:ckks.zoneID];
129 [recordsToSave addObject: record];
130 attemptedRecords[record.recordID] = record;
131 }
132
133 // Use the spare operation trick to wait for the CKModifyRecordsOperation to complete
134 self.cloudkitModifyOperationFinished = [NSBlockOperation named:@"heal-tlkshares-modify-operation-finished" withBlock:^{}];
135 [self dependOnBeforeGroupFinished: self.cloudkitModifyOperationFinished];
136
137
138 // Get the CloudKit operation ready...
139 CKModifyRecordsOperation* modifyRecordsOp = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:recordsToSave
140 recordIDsToDelete:recordIDsToDelete];
141 modifyRecordsOp.atomic = YES;
142 modifyRecordsOp.longLived = NO;
143 modifyRecordsOp.qualityOfService = NSQualityOfServiceUserInitiated; // very important: get the TLKShares off-device ASAP
144 modifyRecordsOp.group = self.ckoperationGroup;
145 ckksnotice("ckksshare", ckks, "Operation group is %@", self.ckoperationGroup);
146
147 modifyRecordsOp.perRecordCompletionBlock = ^(CKRecord *record, NSError * _Nullable error) {
148 __strong __typeof(weakSelf) strongSelf = weakSelf;
149 __strong __typeof(strongSelf.ckks) blockCKKS = strongSelf.ckks;
150
151 // These should all fail or succeed as one. Do the hard work in the records completion block.
152 if(!error) {
153 ckksinfo("ckksshare", blockCKKS, "Successfully completed upload for record %@", record.recordID.recordName);
154 } else {
155 ckkserror("ckksshare", blockCKKS, "error on row: %@ %@", record.recordID, error);
156 }
157 };
158
159 modifyRecordsOp.modifyRecordsCompletionBlock = ^(NSArray<CKRecord *> *savedRecords, NSArray<CKRecordID *> *deletedRecordIDs, NSError *error) {
160 __strong __typeof(weakSelf) strongSelf = weakSelf;
161 __strong __typeof(strongSelf.ckks) strongCKKS = strongSelf.ckks;
162 if(!strongSelf) {
163 secerror("ckks: received callback for released object");
164 return;
165 }
166
167 [strongCKKS dispatchSyncWithAccountKeys: ^bool {
168 if(error == nil) {
169 // Success. Persist the records to the CKKS database
170 ckksnotice("ckksshare", strongCKKS, "Completed TLK Share heal operation with success");
171 NSError* localerror = nil;
172
173 // Save the new CKRecords to the database
174 for(CKRecord* record in savedRecords) {
175 CKKSTLKShare* savedShare = [[CKKSTLKShare alloc] initWithCKRecord:record];
176 [savedShare saveToDatabase:&localerror];
177
178 if(localerror) {
179 // No recovery from this, really...
180 ckkserror("ckksshare", strongCKKS, "Couldn't save new TLKShare record to database: %@", localerror);
181 localerror = nil;
182 } else {
183 ckksnotice("ckksshare", strongCKKS, "Successfully completed upload for %@", savedShare);
184 }
185 }
186
187 // Successfully sharing TLKs means we're now in ready!
188 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateReady withError: nil];
189 } else {
190 ckkserror("ckksshare", strongCKKS, "Completed TLK Share heal operation with error: %@", error);
191 [strongCKKS _onqueueCKWriteFailed:error attemptedRecordsChanged:attemptedRecords];
192 // Send the key state machine into tlksharesfailed
193 [strongCKKS _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateHealTLKSharesFailed withError: nil];
194 }
195 return true;
196 }];
197
198 // Notify that we're done
199 [strongSelf.operationQueue addOperation: strongSelf.cloudkitModifyOperationFinished];
200 };
201
202 [ckks.database addOperation: modifyRecordsOp];
203 return true;
204 }];
205 }
206
207 - (void)cancel {
208 [self.cloudkitModifyOperationFinished cancel];
209 [super cancel];
210 }
211
212 @end;
213
214 #endif