]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSProcessReceivedKeysOperation.m
Security-59754.41.1.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/CKKSOperationDependencies.h"
33 #import "keychain/ckks/CloudKitCategories.h"
34 #import "keychain/categories/NSError+UsefulConstructors.h"
35
36 @implementation CKKSProcessReceivedKeysOperation
37 @synthesize intendedState = _intendedState;
38
39 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies
40 intendedState:(OctagonState*)intendedState
41 errorState:(OctagonState*)errorState
42 {
43 if(self = [super init]) {
44 _deps = dependencies;
45 _intendedState = intendedState;
46 _nextState = errorState;
47 }
48 return self;
49 }
50
51 - (void)main {
52 NSArray<CKKSPeerProviderState*>* currentTrustStates = self.deps.currentTrustStates;
53
54 [self.deps.databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult {
55 bool ok = [self _onqueueMain:currentTrustStates];
56
57 return ok ? CKKSDatabaseTransactionCommit : CKKSDatabaseTransactionRollback;
58 }];
59 }
60
61 - (bool)_onqueueMain:(NSArray<CKKSPeerProviderState*>*)currentTrustStates
62 {
63 NSError* error = nil;
64 CKKSKey* tlk = nil;
65 CKKSKey* topKey = nil;
66
67 // The synckeys table contains everything that's in CloudKit, if looked at correctly.
68 // Updates from CloudKit are marked 'remote'; everything else is 'local'.
69
70 // Step 1. Find all remote keys.
71 NSArray<CKKSKey*>* remoteKeys = [CKKSKey remoteKeys:self.deps.zoneID error:&error];
72 if(!remoteKeys) {
73 ckkserror("ckkskey", self.deps.zoneID, "couldn't fetch list of remote keys: %@", error);
74 self.error = error;
75 self.nextState = SecCKKSZoneKeyStateError;
76 return false;
77 }
78
79 if([remoteKeys count] == 0u) {
80 ckksnotice("ckkskey", self.deps.zoneID, "No remote keys? Quitting.");
81 // Not a ready state, more of a quizzical one? The key state machine will know what to do.
82 self.error = error;
83 self.nextState = SecCKKSZoneKeyStateBecomeReady;
84 return false;
85 }
86
87 ckksinfo("ckkskey", self.deps.zoneID, "remote keys: %@", remoteKeys);
88
89 // current TLK record:
90 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassTLK zoneID:self.deps.zoneID error:&error];
91 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassA zoneID:self.deps.zoneID error:&error];
92 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassC zoneID:self.deps.zoneID error:&error];
93
94 // Do these pointers point at anything?
95 NSError* localerror = nil;
96 CKKSKey* suggestedTLK = currentTLKPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentTLKPointer.currentKeyUUID zoneID:self.deps.zoneID error:&localerror] : nil;
97 CKKSKey* suggestedClassA = currentClassAPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassAPointer.currentKeyUUID zoneID:self.deps.zoneID error:&localerror] : nil;
98 CKKSKey* suggestedClassC = currentClassCPointer.currentKeyUUID ? [CKKSKey tryFromDatabaseAnyState:currentClassCPointer.currentKeyUUID zoneID:self.deps.zoneID error:&localerror] : nil;
99
100 if(!currentTLKPointer || !currentClassAPointer || !currentClassCPointer ||
101 !currentTLKPointer.currentKeyUUID || !currentClassAPointer.currentKeyUUID || !currentClassCPointer.currentKeyUUID ||
102 !suggestedTLK || !suggestedClassA || !suggestedClassC) {
103 ckkserror("ckkskey", self.deps.zoneID, "no current pointer for some keyclass: tlk:%@ a:%@ c:%@ %@ %@",
104 currentTLKPointer, currentClassAPointer, currentClassCPointer, error, localerror);
105 self.error = error;
106 self.nextState = SecCKKSZoneKeyStateBadCurrentPointers;
107 return true;
108 }
109
110 for(CKKSKey* key in remoteKeys) {
111 // Find the active TLK.
112 if([key.uuid isEqualToString: currentTLKPointer.currentKeyUUID]) {
113 if([key wrapsSelf]) {
114 tlk = key;
115 } else {
116 NSError *newError = [NSError errorWithDomain:CKKSErrorDomain code:CKKSKeyNotSelfWrapped description:[NSString stringWithFormat: @"current TLK doesn't wrap itself: %@ %@", key, key.parentKeyUUID] underlying:error];
117 ckkserror("ckkskey", self.deps.zoneID, "%@", error);
118 self.error = newError;
119 self.nextState = SecCKKSZoneKeyStateUnhealthy;
120 return true;
121 }
122 }
123 }
124
125 if(!tlk) {
126 ckkserror("ckkskey", self.deps.zoneID, "couldn't find active TLK: %@", currentTLKPointer);
127 self.error = error;
128 self.nextState = SecCKKSZoneKeyStateUnhealthy;
129 return true;
130 }
131
132 if(![tlk validTLK:&error]) {
133 // Something has gone horribly wrong. Enter error state.
134 ckkserror("ckkskey", self.deps.zoneID, "CKKS claims %@ is not a valid TLK: %@", tlk, error);
135 self.error = [NSError errorWithDomain:CKKSErrorDomain code:CKKSInvalidTLK description:@"invalid TLK from CloudKit" underlying:error];
136 self.nextState = SecCKKSZoneKeyStateError;
137 return true;
138 }
139
140 // This key is our proposed TLK.
141 if(![tlk tlkMaterialPresentOrRecoverableViaTLKShare:currentTrustStates
142 error:&error]) {
143 // TLK is valid, but not present locally
144 if(error && [self.deps.lockStateTracker isLockedError:error]) {
145 ckksnotice("ckkskey", self.deps.zoneID, "Received a TLK(%@), but keybag appears to be locked. Entering a waiting state.", tlk);
146 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
147 } else {
148 ckksnotice("ckkskey", self.deps.zoneID, "Received a TLK(%@) which we don't have in the local keychain: %@", tlk, error);
149 self.error = error;
150 self.nextState = SecCKKSZoneKeyStateTLKMissing;
151 }
152 return true;
153 }
154
155 // Ensure that new keys wrap to the TLK.
156 for(CKKSKey* key in remoteKeys) {
157 if(key == tlk) {
158 continue;
159 }
160
161 topKey = [key topKeyInAnyState:&error];
162
163 if(error != nil || ![topKey.uuid isEqual: tlk.uuid]) {
164 ckkserror("ckkskey", self.deps.zoneID, "new key %@ is orphaned (%@)", key, error);
165 // TODO: possibly re-fetch. Maybe not an actual error state.
166 self.error = [NSError errorWithDomain:CKKSErrorDomain
167 code:CKKSOrphanedKey
168 description:[NSString stringWithFormat:@"orphaned key(%@) in hierarchy", topKey]
169 underlying:error];
170 self.nextState = SecCKKSZoneKeyStateError;
171 return true;
172
173 }
174
175 // Okay, it wraps to the TLK. Can we unwrap it?
176 if(![key unwrapViaKeyHierarchy:&error] || error != nil) {
177 if(error && [self.deps.lockStateTracker isLockedError:error]) {
178 ckksnotice("ckkskey", self.deps.zoneID, "Couldn't unwrap new key (%@), but keybag appears to be locked. Entering waitforunlock.", key);
179 self.error = error;
180 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
181 return true;
182 } else {
183 ckkserror("ckkskey", self.deps.zoneID, "new key %@ claims to wrap to TLK, but we can't unwrap it: %@", topKey, error);
184 self.error = [NSError errorWithDomain:CKKSErrorDomain
185 code:CKKSOrphanedKey
186 description:[NSString stringWithFormat:@"unwrappable key(%@) in hierarchy: %@", topKey, error]
187 underlying:error];
188 self.nextState = SecCKKSZoneKeyStateError;
189 return true;
190 }
191 }
192
193 ckksnotice("ckkskey", self.deps.zoneID, "New key %@ wraps to tlk %@", key, tlk);
194 }
195
196
197 // We're happy with this key hierarchy. Save it.
198 for(CKKSKey* key in remoteKeys) {
199 key.state = SecCKKSProcessedStateLocal;
200
201 if([key.uuid isEqualToString: currentClassAPointer.currentKeyUUID] ||
202 [key.uuid isEqualToString: currentClassCPointer.currentKeyUUID]) {
203 [key saveToDatabaseAsOnlyCurrentKeyForClassAndState: &error];
204 } else {
205 [key saveToDatabase: &error];
206 }
207
208 [key saveKeyMaterialToKeychain: &error];
209
210
211 if(error) {
212 if([self.deps.lockStateTracker isLockedError:error]) {
213 ckksnotice("ckkskey", self.deps.zoneID, "Couldn't save newly local key %@ keychain, due to lock state. Entering a waiting state; %@", key, error);
214 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
215 } else {
216 ckkserror("ckkskey", self.deps.zoneID, "couldn't save newly local key %@ to database: %@", key, error);
217 self.nextState = SecCKKSZoneKeyStateError;
218 }
219 self.error = error;
220 return false;
221 }
222 }
223
224 // New key hierarchy? Get it backed up!
225 // TLKs are now saved in the local keychain; fire off a backup
226 CKKSNearFutureScheduler* tlkNotifier = self.deps.savedTLKNotifier;
227 ckksnotice("ckkstlk", self.deps.zoneID, "triggering new TLK notification: %@", tlkNotifier);
228 [tlkNotifier trigger];
229
230 if(!error) {
231 ckksnotice("ckkskey", self.deps.zoneID, "Accepted new key hierarchy");
232 self.nextState = self.intendedState;
233 } else {
234 ckkserror("ckkskey", self.deps.zoneID, "error accepting new key hierarchy: %@", error);
235 self.error = error;
236 self.nextState = SecCKKSZoneKeyStateError;
237 }
238 return true;
239 }
240
241 @end;
242
243 #endif