2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "CKKSKeychainView.h"
25 #import "CKKSCurrentKeyPointer.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"
36 #import "keychain/ckks/CKKSTLKShareRecord.h"
38 @interface CKKSNewTLKOperation ()
39 @property (nullable) CKKSCurrentKeySet* keyset;
42 @implementation CKKSNewTLKOperation
43 @synthesize intendedState = _intendedState;
44 @synthesize nextState = _nextState;
47 - (instancetype)init {
50 - (instancetype)initWithDependencies:(CKKSOperationDependencies*)dependencies
51 ckks:(CKKSKeychainView*)ckks
53 if(self = [super init]) {
57 _nextState = SecCKKSZoneKeyStateError;
58 _intendedState = SecCKKSZoneKeyStateWaitForTLKUpload;
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.
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.
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).
78 CKKSKeychainView* ckks = self.ckks;
81 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
86 ckkserror("ckkstlk", ckks, "no CKKS object");
90 NSArray<CKKSPeerProviderState*>* currentTrustStates = self.deps.currentTrustStates;
92 // Synchronous, on some thread. Get back on the CKKS queue for SQL thread-safety.
93 [self.deps.databaseProvider dispatchSyncWithSQLTransaction:^CKKSDatabaseTransactionResult{
95 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
96 return CKKSDatabaseTransactionRollback;
99 ckks.lastNewTLKOperation = self;
101 NSError* error = nil;
103 ckksinfo("ckkstlk", ckks, "Generating new TLK");
105 // Promote to strong reference
106 CKKSKeychainView* ckks = self.ckks;
108 CKKSKey* newTLK = nil;
109 CKKSKey* newClassAKey = nil;
110 CKKSKey* newClassCKey = nil;
111 CKKSKey* wrappedOldTLK = nil;
113 // Now, prepare data for the operation:
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];
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
123 [oldTLK ensureKeyLoaded: &error];
125 ckksnotice("ckkstlk", ckks, "Old TLK is: %@ %@", oldTLK, error);
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;
132 ckkserror("ckkstlk", self.deps.zoneID, "Couldn't fetch and unwrap old TLK: %@", error);
133 self.nextState = SecCKKSZoneKeyStateError;
136 return CKKSDatabaseTransactionRollback;
139 // Generate new hierarchy:
144 // oldTLK classA classC
146 CKKSAESSIVKey* newAESKey = [CKKSAESSIVKey randomKey:&error];
148 ckkserror("ckkstlk", ckks, "Couldn't create new TLK: %@", error);
149 self.nextState = SecCKKSZoneKeyStateError;
151 return CKKSDatabaseTransactionRollback;
153 newTLK = [[CKKSKey alloc] initSelfWrappedWithAESKey:newAESKey
154 uuid:[[NSUUID UUID] UUIDString]
155 keyclass:SecCKKSKeyClassTLK
156 state:SecCKKSProcessedStateLocal
161 newClassAKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassA error: &error];
162 newClassCKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassC error: &error];
165 ckkserror("ckkstlk", ckks, "couldn't make new key hierarchy: %@", error);
166 // TODO: this really isn't the error state, but a 'retry'.
168 self.nextState = SecCKKSZoneKeyStateError;
169 return CKKSDatabaseTransactionRollback;
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];
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;
181 return CKKSDatabaseTransactionRollback;
184 // Wrap old TLK under the new TLK
185 wrappedOldTLK = [oldTLK copy];
187 [wrappedOldTLK ensureKeyLoaded: &error];
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;
194 ckkserror("ckkstlk", self.deps.zoneID, "couldn't unwrap TLK, aborting new TLK operation: %@", error);
195 self.nextState = SecCKKSZoneKeyStateError;
198 return CKKSDatabaseTransactionRollback;
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
204 ckkserror("ckkstlk", ckks, "couldn't wrap oldTLK, aborting new TLK operation: %@", error);
205 self.nextState = SecCKKSZoneKeyStateError;
207 return CKKSDatabaseTransactionRollback;
210 wrappedOldTLK.currentkey = false;
213 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] init];
214 keyset.viewName = newTLK.zoneID.zoneName;
217 keyset.classA = newClassAKey;
218 keyset.classC = newClassCKey;
220 keyset.currentTLKPointer = currentTLKPointer;
221 keyset.currentClassAPointer = currentClassAPointer;
222 keyset.currentClassCPointer = currentClassCPointer;
224 keyset.proposed = YES;
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);
233 [newTLK saveKeyMaterialToKeychain: &error];
234 [newClassAKey saveKeyMaterialToKeychain: &error];
235 [newClassCKey saveKeyMaterialToKeychain: &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;
242 ckkserror("ckkstlk", self.deps.zoneID, "couldn't save new key material to keychain; aborting new TLK operation: %@", error);
243 self.nextState = SecCKKSZoneKeyStateError;
246 return CKKSDatabaseTransactionRollback;
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;
259 ckksnotice("ckkstlk", ckks, "Unable to generate TLK shares for (%@): %@", newTLK, trustState);
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);
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];
272 [tlkShares addObject:share];
276 keyset.pendingTLKShares = [tlkShares allObjects];
278 self.keyset = keyset;
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;
288 [self.deps provideKeySet:self.keyset];