]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSProcessReceivedKeysOperation.m
Security-58286.240.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 #import <AssertMacros.h>
25
26 #import "CKKSKeychainView.h"
27 #import "CKKSCurrentKeyPointer.h"
28 #import "CKKSKey.h"
29 #import "CKKSProcessReceivedKeysOperation.h"
30 #import "keychain/ckks/CloudKitCategories.h"
31 #import "keychain/categories/NSError+UsefulConstructors.h"
32
33 #if OCTAGON
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 }
44 return self;
45 }
46
47 - (void) main {
48 // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety.
49
50 // Take a strong reference.
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 if(self.cancelled) {
64 ckksinfo("ckkskey", ckks, "CKKSProcessReceivedKeysOperation cancelled, quitting");
65 return false;
66 }
67
68 ckks.lastProcessReceivedKeysOperation = self;
69
70 NSError* error = nil;
71 CKKSKey* tlk = nil;
72 CKKSKey* topKey = nil;
73
74 // The synckeys table contains everything that's in CloudKit, if looked at correctly.
75 // Updates from CloudKit are marked 'remote'; everything else is 'local'.
76
77 // Step 1. Find all remote keys.
78 NSArray<CKKSKey*>* remoteKeys = [CKKSKey remoteKeys:ckks.zoneID error:&error];
79 if(!remoteKeys) {
80 ckkserror("ckkskey", ckks, "couldn't fetch list of remote keys: %@", error);
81 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:error];
82 return false;
83 }
84
85 if([remoteKeys count] == 0u) {
86 ckksnotice("ckkskey", ckks, "No remote keys? Quitting.");
87 // Not a ready state, more of a quizzical one? The key state machine will know what to do.
88 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateReady withError:error];
89 return false;
90 }
91
92 ckksinfo("ckkskey", ckks, "remote keys: %@", remoteKeys);
93
94 // current TLK record:
95 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassTLK zoneID:ckks.zoneID error:&error];
96 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassA zoneID:ckks.zoneID error:&error];
97 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassC zoneID:ckks.zoneID error:&error];
98
99 // Do these pointers point at anything?
100 NSError* localerror = nil;
101 CKKSKey* suggestedTLK = currentTLKPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentTLKPointer.currentKeyUUID zoneID:ckks.zoneID error:&localerror] : nil;
102 CKKSKey* suggestedClassA = currentClassAPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassAPointer.currentKeyUUID zoneID:ckks.zoneID error:&localerror] : nil;
103 CKKSKey* suggestedClassC = currentClassCPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassCPointer.currentKeyUUID zoneID:ckks.zoneID error:&localerror] : nil;
104
105 if(!currentTLKPointer || !currentClassAPointer || !currentClassCPointer ||
106 !currentTLKPointer.currentKeyUUID || !currentClassAPointer.currentKeyUUID || !currentClassCPointer.currentKeyUUID ||
107 !suggestedTLK || !suggestedClassA || !suggestedClassC) {
108 ckkserror("ckkskey", ckks, "no current pointer for some keyclass: tlk:%@ a:%@ c:%@ %@ %@",
109 currentTLKPointer, currentClassAPointer, currentClassCPointer, error, localerror);
110 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateBadCurrentPointers withError:error];
111 return true;
112 }
113
114 for(CKKSKey* key in remoteKeys) {
115 // Find the active TLK.
116 if([key.uuid isEqualToString: currentTLKPointer.currentKeyUUID]) {
117 if([key wrapsSelf]) {
118 tlk = key;
119 } else {
120 ckkserror("ckkskey", ckks, "current TLK doesn't wrap itself: %@ %@", key, key.parentKeyUUID);
121 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:error];
122 return true;
123 }
124 }
125 }
126
127 if(!tlk) {
128 ckkserror("ckkskey", ckks, "couldn't find active TLK: %@", currentTLKPointer);
129 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateUnhealthy withError:error];
130 return true;
131 }
132
133 // This key is our proposed TLK. Check with the CKKS object.
134 if(![ckks _onqueueWithAccountKeysCheckTLK: tlk error: &error]) {
135 // Was this error "I've never seen that TLK before in my life"? If so, enter the "wait for TLK sync" state.
136 if(error && [error.domain isEqualToString: @"securityd"] && error.code == errSecItemNotFound) {
137 ckksnotice("ckkskey", ckks, "Received a TLK which we don't have in the local keychain(%@). Entering waitfortlk.", tlk);
138 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForTLK withError:nil];
139 return true;
140 } else if(error && [ckks.lockStateTracker isLockedError:error]) {
141 // TODO: _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError should handle this. But, we don't have tests, so, leave this in until 33204154
142 ckksnotice("ckkskey", ckks, "Received a TLK(%@), but keybag appears to be locked. Entering a waiting state.", tlk);
143 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:nil];
144 return true;
145 } else {
146 // Otherwise, something has gone horribly wrong. enter error state.
147 ckkserror("ckkskey", ckks, "CKKS claims %@ is not a valid TLK: %@", tlk, error);
148 NSError* newError = [NSError errorWithDomain:CKKSErrorDomain code:CKKSInvalidTLK description:@"invalid TLK from CloudKit" underlying:error];
149 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:newError];
150 return true;
151 }
152 }
153
154 // Ensure that new keys wrap to the TLK.
155 for(CKKSKey* key in remoteKeys) {
156 if(key == tlk) {
157 continue;
158 }
159
160 topKey = [key topKeyInAnyState:&error];
161
162 if(error != nil || ![topKey.uuid isEqual: tlk.uuid]) {
163 ckkserror("ckkskey", ckks, "new key %@ is orphaned (%@)", key, error);
164 // TODO: possibly re-fetch. Maybe not an actual error state.
165 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError
166 withError:[NSError errorWithDomain:CKKSErrorDomain
167 code:CKKSOrphanedKey
168 description:[NSString stringWithFormat:@"orphaned key(%@) in hierarchy", topKey]
169 underlying:error]];
170 return true;
171
172 }
173
174 // Okay, it wraps to the TLK. Can we unwrap it?
175 if(![key unwrapViaKeyHierarchy:&error] || error != nil) {
176 if(error && [ckks.lockStateTracker isLockedError:error]) {
177 ckksnotice("ckkskey", ckks, "Couldn't unwrap new key (%@), but keybag appears to be locked. Entering waitforunlock.", key);
178 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForUnlock withError:error];
179 return true;
180 } else {
181 ckkserror("ckkskey", ckks, "new key %@ claims to wrap to TLK, but we can't unwrap it: %@", topKey, error);
182 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError
183 withError:[NSError errorWithDomain:CKKSErrorDomain
184 code:CKKSOrphanedKey
185 description:[NSString stringWithFormat:@"unwrappable key(%@) in hierarchy: %@", topKey, error]
186 underlying:error]];
187 return true;
188 }
189 }
190
191 ckksnotice("ckkskey", ckks, "New key %@ wraps to tlk %@", key, tlk);
192 }
193
194
195 // We're happy with this key hierarchy. Save it.
196 for(CKKSKey* key in remoteKeys) {
197 key.state = SecCKKSProcessedStateLocal;
198
199 if([key.uuid isEqualToString: currentClassAPointer.currentKeyUUID] ||
200 [key.uuid isEqualToString: currentClassCPointer.currentKeyUUID]) {
201 [key saveToDatabaseAsOnlyCurrentKeyForClassAndState: &error];
202 } else {
203 [key saveToDatabase: &error];
204 }
205
206 [key saveKeyMaterialToKeychain: &error];
207
208 if(error) {
209 ckkserror("ckkskey", ckks, "couldn't save newly local key %@ to database: %@", key, error);
210 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError: error];
211 return false;
212 }
213 }
214
215 if(!error) {
216 ckksnotice("ckkskey", ckks, "Accepted new key hierarchy");
217 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateReady withError: nil];
218 } else {
219 ckkserror("ckkskey", ckks, "error accepting new key hierarchy: %@", error);
220 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
221 }
222 return true;
223 }];
224 }
225
226 @end;
227
228 #endif