]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSNewTLKOperation.m
Security-59306.11.20.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 [ckks dispatchSyncWithAccountKeys: ^bool{
87 if(self.cancelled) {
88 ckksnotice("ckkstlk", ckks, "CKKSNewTLKOperation cancelled, quitting");
89 return false;
90 }
91
92 ckks.lastNewTLKOperation = self;
93
94 NSError* error = nil;
95
96 ckksinfo("ckkstlk", ckks, "Generating new TLK");
97
98 // Promote to strong reference
99 CKKSKeychainView* ckks = self.ckks;
100
101 CKKSKey* newTLK = nil;
102 CKKSKey* newClassAKey = nil;
103 CKKSKey* newClassCKey = nil;
104 CKKSKey* wrappedOldTLK = nil;
105
106 // Now, prepare data for the operation:
107
108 // We must find the current TLK (to wrap it to the new TLK).
109 NSError* localerror = nil;
110 CKKSKey* oldTLK = [CKKSKey currentKeyForClass: SecCKKSKeyClassTLK zoneID:ckks.zoneID error: &localerror];
111 if(localerror) {
112 ckkserror("ckkstlk", ckks, "couldn't load the current TLK: %@", localerror);
113 // TODO: not loading the old TLK is fine, but only if there aren't any TLKs
114 }
115
116 [oldTLK ensureKeyLoaded: &error];
117
118 ckksnotice("ckkstlk", ckks, "Old TLK is: %@ %@", oldTLK, error);
119 if(error != nil) {
120 ckkserror("ckkstlk", ckks, "Couldn't fetch and unwrap old TLK: %@", error);
121 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
122 return false;
123 }
124
125 // Generate new hierarchy:
126 // newTLK
127 // / | \
128 // / | \
129 // / | \
130 // oldTLK classA classC
131
132 CKKSAESSIVKey* newAESKey = [CKKSAESSIVKey randomKey:&error];
133 if(error) {
134 ckkserror("ckkstlk", ckks, "Couldn't create new TLK: %@", error);
135 self.error = error;
136 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:error];
137 return false;
138 }
139 newTLK = [[CKKSKey alloc] initSelfWrappedWithAESKey:newAESKey
140 uuid:[[NSUUID UUID] UUIDString]
141 keyclass:SecCKKSKeyClassTLK
142 state:SecCKKSProcessedStateLocal
143 zoneID:ckks.zoneID
144 encodedCKRecord:nil
145 currentkey:true];
146
147 newClassAKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassA error: &error];
148 newClassCKey = [CKKSKey randomKeyWrappedByParent: newTLK keyclass: SecCKKSKeyClassC error: &error];
149
150 if(error != nil) {
151 ckkserror("ckkstlk", ckks, "couldn't make new key hierarchy: %@", error);
152 // TODO: this really isn't the error state, but a 'retry'.
153 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
154 return false;
155 }
156
157 CKKSCurrentKeyPointer* currentTLKPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassTLK withKeyUUID:newTLK.uuid zoneID:ckks.zoneID error: &error];
158 CKKSCurrentKeyPointer* currentClassAPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassA withKeyUUID:newClassAKey.uuid zoneID:ckks.zoneID error: &error];
159 CKKSCurrentKeyPointer* currentClassCPointer = [CKKSCurrentKeyPointer forKeyClass: SecCKKSKeyClassC withKeyUUID:newClassCKey.uuid zoneID:ckks.zoneID error: &error];
160
161 if(error != nil) {
162 ckkserror("ckkstlk", ckks, "couldn't make current key records: %@", error);
163 // TODO: this really isn't the error state, but a 'retry'.
164 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
165 return false;
166 }
167
168 // Wrap old TLK under the new TLK
169 wrappedOldTLK = [oldTLK copy];
170 if(wrappedOldTLK) {
171 [wrappedOldTLK ensureKeyLoaded: &error];
172 if(error != nil) {
173 ckkserror("ckkstlk", ckks, "couldn't unwrap TLK, aborting new TLK operation: %@", error);
174 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
175 return false;
176 }
177
178 [wrappedOldTLK wrapUnder: newTLK error:&error];
179 // TODO: should we continue in this error state? Might be required to fix broken TLKs/argue over which TLK should be used
180 if(error != nil) {
181 ckkserror("ckkstlk", ckks, "couldn't wrap oldTLK, aborting new TLK operation: %@", error);
182 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
183 return false;
184 }
185
186 wrappedOldTLK.currentkey = false;
187 }
188
189 CKKSCurrentKeySet* keyset = [[CKKSCurrentKeySet alloc] init];
190
191 keyset.tlk = newTLK;
192 keyset.classA = newClassAKey;
193 keyset.classC = newClassCKey;
194
195 keyset.currentTLKPointer = currentTLKPointer;
196 keyset.currentClassAPointer = currentClassAPointer;
197 keyset.currentClassCPointer = currentClassCPointer;
198
199 keyset.proposed = YES;
200
201 if(wrappedOldTLK) {
202 // TODO o no
203 }
204
205 // 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!
206 ckksnotice("ckkstlk", ckks, "Saving new keys %@ to keychain", keyset);
207
208 [newTLK saveKeyMaterialToKeychain: &error];
209 [newClassAKey saveKeyMaterialToKeychain: &error];
210 [newClassCKey saveKeyMaterialToKeychain: &error];
211 if(error) {
212 self.error = error;
213 ckkserror("ckkstlk", ckks, "couldn't save new key material to keychain, aborting new TLK operation: %@", error);
214 [ckks _onqueueAdvanceKeyStateMachineToState: SecCKKSZoneKeyStateError withError: error];
215 return false;
216 }
217
218 // Generate the TLK sharing records for all trusted peers
219 NSMutableSet<CKKSTLKShareRecord*>* tlkShares = [NSMutableSet set];
220 for(CKKSPeerProviderState* trustState in ckks.currentTrustStates) {
221 if(trustState.currentSelfPeers.currentSelf == nil || trustState.currentSelfPeersError) {
222 if(trustState.essential) {
223 ckksnotice("ckkstlk", ckks, "Fatal error: unable to generate TLK shares for (%@): %@", newTLK, trustState.currentSelfPeersError);
224 self.error = trustState.currentSelfPeersError;
225 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateError withError:trustState.currentSelfPeersError];
226 return false;
227 }
228 ckksnotice("ckkstlk", ckks, "Unable to generate TLK shares for (%@): %@", newTLK, trustState);
229 continue;
230 }
231
232 for(id<CKKSPeer> trustedPeer in trustState.currentTrustedPeers) {
233 if(!trustedPeer.publicEncryptionKey) {
234 ckksnotice("ckkstlk", ckks, "No need to make TLK for %@; they don't have any encryption keys", trustedPeer);
235 continue;
236 }
237
238 ckksnotice("ckkstlk", ckks, "Generating TLK(%@) share for %@", newTLK, trustedPeer);
239 CKKSTLKShareRecord* share = [CKKSTLKShareRecord share:newTLK as:trustState.currentSelfPeers.currentSelf to:trustedPeer epoch:-1 poisoned:0 error:&error];
240
241 [tlkShares addObject:share];
242 }
243 }
244
245 keyset.pendingTLKShares = [tlkShares allObjects];
246
247 self.keyset = keyset;
248
249 [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForTLKUpload withError:nil];
250
251 return true;
252 }];
253 }
254
255 - (void)cancel {
256 [super cancel];
257 }
258
259 @end;
260
261 #endif