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