]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSNewTLKOperation.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSNewTLKOperation.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 "CKKSKeychainView.h"
25 #import "CKKSCurrentKeyPointer.h"
26 #import "CKKSKey.h"
27 #import "CKKSNewTLKOperation.h"
28 #import "CKKSGroupOperation.h"
29 #import "CKKSNearFutureScheduler.h"
30 #import "keychain/ckks/CKKSStates.h"
31 #import "keychain/ckks/CloudKitCategories.h"
32 #import "keychain/categories/NSError+UsefulConstructors.h"
33
34 #if OCTAGON
35
36 #import "keychain/ckks/CKKSTLKShareRecord.h"
37
38 @interface CKKSNewTLKOperation ()
39 @property (nullable) CKKSCurrentKeySet* keyset;
40 @end
41
42 @implementation CKKSNewTLKOperation
43 @synthesize intendedState = _intendedState;
44 @synthesize nextState = _nextState;
45 @synthesize keyset;
46
47 - (instancetype)init {
48 return nil;
49 }
50 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies
51 ckks:(CKKSKeychainView*)ckks
52 {
53 if(self = [super init]) {
54 _deps = dependencies;
55 _ckks = ckks;
56
57 _nextState = SecCKKSZoneKeyStateError;
58 _intendedState = SecCKKSZoneKeyStateWaitForTLKUpload;
59 }
60 return self;
61 }
62
63 - (void)groupStart {
64 /*
65 * Rolling keys is an essential operation, and must be transactional: either completing successfully or
66 * failing entirely. Also, in the case of failure, some other peer has beaten us to CloudKit and changed
67 * the keys stored there (which we must now fetch and handle): the keys we attempted to upload are useless.
68
69 * Therefore, we'll skip the normal OutgoingQueue behavior, and persist keys in-memory until such time as
70 * CloudKit tells us the operation succeeds or fails, at which point we'll commit them or throw them away.
71 *
72 * Note that this means edge cases in the case of secd dying in the middle of this operation; our normal
73 * retry mechanisms won't work. We'll have to make the policy decision to re-roll the keys if needed upon
74 * the next launch of secd (or, the write will succeed after we die, and we'll handle receiving the CK
75 * items as if a different peer uploaded them).
76 */
77
78 CKKSKeychainView* ckks = self.ckks;
79
80 if(self.cancelled) {
81 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
82 return;
83 }
84
85 if(!ckks) {
86 ckkserror("ckkstlk", ckks, "no CKKS object");
87 return;
88 }
89
90 NSArray<CKKSPeerProviderState*>* currentTrustStates = self.deps.currentTrustStates;
91
92 // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety.
93 [self.deps.databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
94 if(self.cancelled) {
95 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
96 return CKKSDatabaseTransactionRollback;
97 }
98
99 ckks.lastNewTLKOperation = self;
100
101 NSError* error = nil;
102
103 ckksinfo("ckkstlk", ckks, "Generating new TLK");
104
105 // Promote to strong reference
106 CKKSKeychainView* ckks = self.ckks;
107
108 CKKSKey* newTLK = nil;
109 CKKSKey* newClassAKey = nil;
110 CKKSKey* newClassCKey = nil;
111 CKKSKey* wrappedOldTLK = nil;
112
113 // Now, prepare data for the operation:
114
115 // We must find the current TLK (to wrap it to the new TLK).
116 NSError* localerror = nil;
117 CKKSKey* oldTLK = [CKKSKey currentKeyForClass: SecCKKSKeyClassTLK zoneID:ckks.zoneID error: &localerror];
118 if(localerror) {
119 ckkserror("ckkstlk", ckks, "couldn't load the current TLK: %@", localerror);
120 // TODO: not loading the old TLK is fine, but only if there aren't any TLKs
121 }
122
123 [oldTLK ensureKeyLoaded: &error];
124
125 ckksnotice("ckkstlk", ckks, "Old TLK is: %@ %@", oldTLK, error);
126 if(error != nil) {
127 if([self.deps.lockStateTracker isLockedError:error]) {
128 ckkserror("ckkstlk", self.deps.zoneID, "Couldn't fetch and unwrap old TLK due to lock state. Entering a waiting state; %@", error);
129 [self.deps.flagHandler _onqueueHandleFlag:CKKSFlagTLKCreationRequested];
130 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
131 } else {
132 ckkserror("ckkstlk", self.deps.zoneID, "Couldn't fetch and unwrap old TLK: %@", error);
133 self.nextState = SecCKKSZoneKeyStateError;
134 }
135 self.error = error;
136 return CKKSDatabaseTransactionRollback;
137 }
138
139 // Generate new hierarchy:
140 // newTLK
141 // / | \
142 // / | \
143 // / | \
144 // oldTLK classA classC
145
146 CKKSAESSIVKey* newAESKey = [CKKSAESSIVKey randomKey:&error];
147 if(error) {
148 ckkserror("ckkstlk", ckks, "Couldn't create new TLK: %@", error);
149 self.nextState = SecCKKSZoneKeyStateError;
150 self.error = error;
151 return CKKSDatabaseTransactionRollback;
152 }
153 newTLK = [[CKKSKey alloc] initSelfWrappedWithAESKey:newAESKey
154 uuid:[[NSUUID UUID] UUIDString]
155 keyclass:SecCKKSKeyClassTLK
156 state:SecCKKSProcessedStateLocal
157 zoneID:ckks.zoneID
158 encodedCKRecord:nil
159 currentkey:true];
160
161 newClassAKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassA error: &error];
162 newClassCKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassC error: &error];
163
164 if(error != nil) {
165 ckkserror("ckkstlk", ckks, "couldn't make new key hierarchy: %@", error);
166 // TODO: this really isn't the error state, but a 'retry'.
167 self.error = error;
168 self.nextState = SecCKKSZoneKeyStateError;
169 return CKKSDatabaseTransactionRollback;
170 }
171
172 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassTLK withKeyUUID:newTLK.uuid zoneID:ckks.zoneID error: &error];
173 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassA withKeyUUID:newClassAKey.uuid zoneID:ckks.zoneID error: &error];
174 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassC withKeyUUID:newClassCKey.uuid zoneID:ckks.zoneID error: &error];
175
176 if(error != nil) {
177 ckkserror("ckkstlk", ckks, "couldn't make current key records: %@", error);
178 // TODO: this really isn't the error state, but a 'retry'.
179 self.nextState = SecCKKSZoneKeyStateError;
180 self.error = error;
181 return CKKSDatabaseTransactionRollback;
182 }
183
184 // Wrap old TLK under the new TLK
185 wrappedOldTLK = [oldTLK copy];
186 if(wrappedOldTLK) {
187 [wrappedOldTLK ensureKeyLoaded: &error];
188 if(error != nil) {
189 if([self.deps.lockStateTracker isLockedError:error]) {
190 ckkserror("ckkstlk", self.deps.zoneID, "Couldn't unwrap TLK due to lock state. Entering a waiting state; %@", error);
191 [self.deps.flagHandler _onqueueHandleFlag:CKKSFlagTLKCreationRequested];
192 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
193 } else {
194 ckkserror("ckkstlk", self.deps.zoneID, "couldn't unwrap TLK, aborting new TLK operation: %@", error);
195 self.nextState = SecCKKSZoneKeyStateError;
196 }
197 self.error = error;
198 return CKKSDatabaseTransactionRollback;
199 }
200
201 [wrappedOldTLK wrapUnder: newTLK error:&error];
202 // TODO: should we continue in this error state? Might be required to fix broken TLKs/argue over which TLK should be used
203 if(error != nil) {
204 ckkserror("ckkstlk", ckks, "couldn't wrap oldTLK, aborting new TLK operation: %@", error);
205 self.nextState = SecCKKSZoneKeyStateError;
206 self.error = error;
207 return CKKSDatabaseTransactionRollback;
208 }
209
210 wrappedOldTLK.currentkey = false;
211 }
212
213 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] init];
214 keyset.viewName = newTLK.zoneID.zoneName;
215
216 keyset.tlk = newTLK;
217 keyset.classA = newClassAKey;
218 keyset.classC = newClassCKey;
219
220 keyset.currentTLKPointer = currentTLKPointer;
221 keyset.currentClassAPointer = currentClassAPointer;
222 keyset.currentClassCPointer = currentClassCPointer;
223
224 keyset.proposed = YES;
225
226 if(wrappedOldTLK) {
227 // TODO o no
228 }
229
230 // Save the proposed keys to the keychain. Note that we might reject this TLK later, but in that case, this TLK is just orphaned. No worries!
231 ckksnotice("ckkstlk", ckks, "Saving new keys %@ to keychain", keyset);
232
233 [newTLK saveKeyMaterialToKeychain: &error];
234 [newClassAKey saveKeyMaterialToKeychain: &error];
235 [newClassCKey saveKeyMaterialToKeychain: &error];
236 if(error) {
237 if([self.deps.lockStateTracker isLockedError:error]) {
238 ckkserror("ckkstlk", self.deps.zoneID, "Couldn't save new key material to keychain due to lock state. Entering a waiting state; %@", error);
239 [self.deps.flagHandler _onqueueHandleFlag:CKKSFlagTLKCreationRequested];
240 self.nextState = SecCKKSZoneKeyStateWaitForUnlock;
241 } else {
242 ckkserror("ckkstlk", self.deps.zoneID, "couldn't save new key material to keychain; aborting new TLK operation: %@", error);
243 self.nextState = SecCKKSZoneKeyStateError;
244 }
245 self.error = error;
246 return CKKSDatabaseTransactionRollback;
247 }
248
249 // Generate the TLK sharing records for all trusted peers
250 NSMutableSet<CKKSTLKShareRecord*>* tlkShares = [NSMutableSet set];
251 for(CKKSPeerProviderState* trustState in currentTrustStates) {
252 if(trustState.currentSelfPeers.currentSelf == nil || trustState.currentSelfPeersError) {
253 if(trustState.essential) {
254 ckksnotice("ckkstlk", ckks, "Fatal error: unable to generate TLK shares for (%@): %@", newTLK, trustState.currentSelfPeersError);
255 self.error = trustState.currentSelfPeersError;
256 self.nextState = SecCKKSZoneKeyStateError;
257 return CKKSDatabaseTransactionRollback;
258 }
259 ckksnotice("ckkstlk", ckks, "Unable to generate TLK shares for (%@): %@", newTLK, trustState);
260 continue;
261 }
262
263 for(id<CKKSPeer> trustedPeer in trustState.currentTrustedPeers) {
264 if(!trustedPeer.publicEncryptionKey) {
265 ckksnotice("ckkstlk", ckks, "No need to make TLK for %@; they don't have any encryption keys", trustedPeer);
266 continue;
267 }
268
269 ckksnotice("ckkstlk", ckks, "Generating TLK(%@) share for %@", newTLK, trustedPeer);
270 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:newTLK as:trustState.currentSelfPeers.currentSelf to:trustedPeer epoch:-1 poisoned:0 error:&error];
271
272 [tlkShares addObject:share];
273 }
274 }
275
276 keyset.pendingTLKShares = [tlkShares allObjects];
277
278 self.keyset = keyset;
279
280 // Finish this transaction to cause a keychiain db commit
281 // This means that if we provide the new keys to another thread, they'll be able to immediately load them from the keychain
282 // We'll provide the keyset after the commit occurs.
283 self.nextState = SecCKKSZoneKeyStateWaitForTLKUpload;
284 return CKKSDatabaseTransactionCommit;
285 }];
286
287 if(self.keyset) {
288 [self.deps provideKeySet:self.keyset];
289 }
290 }
291
292 - (void)cancel {
293 [super cancel];
294 }
295
296 @end;
297
298 #endif