]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSProcessReceivedKeysOperation.m
Security-59306.80.4.tar.gz
[apple/security.git] / keychain / ckks / CKKSProcessReceivedKeysOperation.m
1 /*
2 * Copyright (c) 2016 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 <AssertMacros.h>
27
28 #import "CKKSKeychainView.h"
29 #import "CKKSCurrentKeyPointer.h"
30 #import "CKKSKey.h"
31 #import "CKKSProcessReceivedKeysOperation.h"
32 #import "keychain/ckks/CloudKitCategories.h"
33 #import "keychain/categories/NSError+UsefulConstructors.h"
34
35 @implementation CKKSProcessReceivedKeysOperation
36
37 - (instancetype)init {
38 return nil;
39 }
40 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks {
41 if(self = [super init]) {
42 _ckks = ckks;
43 _nextState = SecCKKSZoneKeyStateError;
44 }
45 return self;
46 }
47
48 - (void)main {
49 // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety.
50
51 CKKSKeychainView* ckks = self.ckks;
52 if(!ckks) {
53 secerror("ckkskeys: No CKKS object");
54 return;
55 }
56
57 if(self.cancelled) {
58 ckksinfo("ckkskey", ckks, "CKKSProcessReceivedKeysOperation cancelled, quitting");
59 return;
60 }
61
62 [ckks dispatchSyncWithAccountKeys: ^bool{
63 return [self _onqueueMain:ckks];
64 }];
65 }
66
67 - (bool)_onqueueMain:(CKKSKeychainView*)ckks
68 {
69 if(self.cancelled) {
70 ckksinfo("ckkskey", ckks, "CKKSProcessReceivedKeysOperation cancelled, quitting");
71 return false;
72 }
73
74 ckks.lastProcessReceivedKeysOperation = self;
75
76 NSError* error = nil;
77 CKKSKey* tlk = nil;
78 CKKSKey* topKey = nil;
79
80 // The synckeys table contains everything that's in CloudKit, if looked at correctly.
81 // Updates from CloudKit are marked 'remote'; everything else is 'local'.
82
83 // Step 1. Find all remote keys.
84 NSArray<CKKSKey*>* remoteKeys = [CKKSKey remoteKeys:ckks.zoneID error:&error];
85 if(!remoteKeys) {
86 ckkserror("ckkskey", ckks, "couldn't fetch list of remote keys: %@", error);
87 self.error = error;
88 self.nextState = SecCKKSZoneKeyStateError;
89 return false;
90 }
91
92 if([remoteKeys count] == 0u) {
93 ckksnotice("ckkskey", ckks, "No remote keys? Quitting.");
94 // Not a ready state, more of a quizzical one? The key state machine will know what to do.
95 self.error = error;
96 self.nextState = SecCKKSZoneKeyStateReady;
97 return false;
98 }
99
100 ckksinfo("ckkskey", ckks, "remote keys: %@", remoteKeys);
101
102 // current TLK record:
103 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassTLK zoneID:ckks.zoneID error:&error];
104 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassA zoneID:ckks.zoneID error:&error];
105 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassC zoneID:ckks.zoneID error:&error];
106
107 // Do these pointers point at anything?
108 NSError* localerror = nil;
109 CKKSKey* suggestedTLK = currentTLKPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentTLKPointer.currentKeyUUID zoneID:ckks.zoneID error:&localerror] : nil;
110 CKKSKey* suggestedClassA = currentClassAPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassAPointer.currentKeyUUID zoneID:ckks.zoneID error:&localerror] : nil;
111 CKKSKey* suggestedClassC = currentClassCPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassCPointer.currentKeyUUID zoneID:ckks.zoneID error:&localerror] : nil;
112
113 if(!currentTLKPointer || !currentClassAPointer || !currentClassCPointer ||
114 !currentTLKPointer.currentKeyUUID || !currentClassAPointer.currentKeyUUID || !currentClassCPointer.currentKeyUUID ||
115 !suggestedTLK || !suggestedClassA || !suggestedClassC) {
116 ckkserror("ckkskey", ckks, "no current pointer for some keyclass: tlk:%@ a:%@ c:%@ %@ %@",
117 currentTLKPointer, currentClassAPointer, currentClassCPointer, error, localerror);
118 self.error = error;
119 self.nextState = SecCKKSZoneKeyStateBadCurrentPointers;
120 return true;
121 }
122
123 for(CKKSKey* key in remoteKeys) {
124 // Find the active TLK.
125 if([key.uuid isEqualToString: currentTLKPointer.currentKeyUUID]) {
126 if([key wrapsSelf]) {
127 tlk = key;
128 } else {
129 NSError *newError = [NSError errorWithDomain:CKKSErrorDomain code:CKKSKeyNotSelfWrapped description:[NSString stringWithFormat: @"current TLK doesn't wrap itself: %@ %@", key, key.parentKeyUUID] underlying:error];
130 ckkserror("ckkskey", ckks, "%@", error);
131 self.error = newError;
132 self.nextState = SecCKKSZoneKeyStateUnhealthy;
133 return true;
134 }
135 }
136 }
137
138 if(!tlk) {
139 ckkserror("ckkskey", ckks, "couldn't find active TLK: %@", currentTLKPointer);
140 self.error = error;
141 self.nextState = SecCKKSZoneKeyStateUnhealthy;
142 return true;
143 }
144
145 // This key is our proposed TLK. Check with the CKKS object.
146 if(![ckks _onqueueWithAccountKeysCheckTLK: tlk error: &error]) {
147 // Was this error "I've never seen that TLK before in my life"? If so, enter the "wait for TLK sync" state.
148 if(error && [error.domain isEqualToString: @"securityd"] && error.code == errSecItemNotFound) {
149 ckksnotice("ckkskey", ckks, "Received a TLK which we don't have in the local keychain(%@). Entering waitfortlk.", tlk);
150 self.nextState = SecCKKSZoneKeyStateWaitForTLK;
151 return true;
152 } else if(error && [ckks.lockStateTracker isLockedError:error]) {
153 // TODO: _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError should handle this. But, we don't have tests, so, leave this in until 33204154
154 ckksnotice("ckkskey", ckks, "Received a TLK(%@), but keybag appears to be locked. Entering a waiting state.", tlk);
155 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
156 return true;
157 } else {
158 // Otherwise, something has gone horribly wrong. enter error state.
159 ckkserror("ckkskey", ckks, "CKKS claims %@ is not a valid TLK: %@", tlk, error);
160 self.error = [NSError errorWithDomain:CKKSErrorDomain code:CKKSInvalidTLK description:@"invalid TLK from CloudKit" underlying:error];
161 self.nextState = SecCKKSZoneKeyStateError;
162 return true;
163 }
164 }
165
166 // Ensure that new keys wrap to the TLK.
167 for(CKKSKey* key in remoteKeys) {
168 if(key == tlk) {
169 continue;
170 }
171
172 topKey = [key topKeyInAnyState:&error];
173
174 if(error != nil || ![topKey.uuid isEqual: tlk.uuid]) {
175 ckkserror("ckkskey", ckks, "new key %@ is orphaned (%@)", key, error);
176 // TODO: possibly re-fetch. Maybe not an actual error state.
177 self.error = [NSError errorWithDomain:CKKSErrorDomain
178 code:CKKSOrphanedKey
179 description:[NSString stringWithFormat:@"orphaned key(%@) in hierarchy", topKey]
180 underlying:error];
181 self.nextState = SecCKKSZoneKeyStateError;
182 return true;
183
184 }
185
186 // Okay, it wraps to the TLK. Can we unwrap it?
187 if(![key unwrapViaKeyHierarchy:&error] || error != nil) {
188 if(error && [ckks.lockStateTracker isLockedError:error]) {
189 ckksnotice("ckkskey", ckks, "Couldn't unwrap new key (%@), but keybag appears to be locked. Entering waitforunlock.", key);
190 self.error = error;
191 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
192 return true;
193 } else {
194 ckkserror("ckkskey", ckks, "new key %@ claims to wrap to TLK, but we can't unwrap it: %@", topKey, error);
195 self.error = [NSError errorWithDomain:CKKSErrorDomain
196 code:CKKSOrphanedKey
197 description:[NSString stringWithFormat:@"unwrappable key(%@) in hierarchy: %@", topKey, error]
198 underlying:error];
199 self.nextState = SecCKKSZoneKeyStateError;
200 return true;
201 }
202 }
203
204 ckksnotice("ckkskey", ckks, "New key %@ wraps to tlk %@", key, tlk);
205 }
206
207
208 // We're happy with this key hierarchy. Save it.
209 for(CKKSKey* key in remoteKeys) {
210 key.state = SecCKKSProcessedStateLocal;
211
212 if([key.uuid isEqualToString: currentClassAPointer.currentKeyUUID] ||
213 [key.uuid isEqualToString: currentClassCPointer.currentKeyUUID]) {
214 [key saveToDatabaseAsOnlyCurrentKeyForClassAndState: &error];
215 } else {
216 [key saveToDatabase: &error];
217 }
218
219 [key saveKeyMaterialToKeychain: &error];
220
221 if(error) {
222 ckkserror("ckkskey", ckks, "couldn't save newly local key %@ to database: %@", key, error);
223 self.error = error;
224 self.nextState = SecCKKSZoneKeyStateError;
225 return false;
226 }
227 }
228
229 // New key hierarchy? Get it backed up!
230 // TLKs are now saved in the local keychain; fire off a backup
231 CKKSNearFutureScheduler* tlkNotifier = ckks.savedTLKNotifier;
232 ckksnotice("ckkstlk", ckks, "triggering new TLK notification: %@", tlkNotifier);
233 [tlkNotifier trigger];
234
235 if(!error) {
236 ckksnotice("ckkskey", ckks, "Accepted new key hierarchy");
237 self.nextState = SecCKKSZoneKeyStateReady;
238 } else {
239 ckkserror("ckkskey", ckks, "error accepting new key hierarchy: %@", error);
240 self.error = error;
241 self.nextState = SecCKKSZoneKeyStateError;
242 }
243 return true;
244 }
245
246 @end;
247
248 @implementation CKKSProcessReceivedKeysStateMachineOperation
249 - (instancetype)initWithCKKSKeychainView:(CKKSKeychainView*)ckks {
250 if(self = [super init]) {
251 _ckks = ckks;
252 }
253 return self;
254 }
255
256 - (void)main
257 {
258 CKKSKeychainView* ckks = self.ckks;
259
260 // The following is an abuse of operations, but we need the state machine advancement to be in the same txion as the decision
261 CKKSProcessReceivedKeysOperation* op = [[CKKSProcessReceivedKeysOperation alloc] initWithCKKSKeychainView:ckks];
262
263 [ckks dispatchSyncWithAccountKeys:^bool {
264 bool ret = [op _onqueueMain:ckks];
265 ckksnotice("ckkskey", ckks, "Finished processing received keys, moving to state %@", op.nextState);
266 [ckks _onqueueAdvanceKeyStateMachineToState:op.nextState withError:op.error];
267 return ret;
268 }];
269 }
270
271 @end
272
273 #endif