]>
Commit | Line | Data |
---|---|---|
866f8763 A |
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" | |
79b9da22 | 31 | #import "keychain/categories/NSError+UsefulConstructors.h" |
866f8763 A |
32 | |
33 | #if OCTAGON | |
34 | ||
b54c578e | 35 | #import "keychain/ckks/CKKSTLKShareRecord.h" |
8a50f688 | 36 | |
866f8763 A |
37 | @interface CKKSNewTLKOperation () |
38 | @property NSBlockOperation* cloudkitModifyOperationFinished; | |
39 | @property CKOperationGroup* ckoperationGroup; | |
b54c578e A |
40 | |
41 | @property (nullable) CKKSCurrentKeySet* keyset; | |
866f8763 A |
42 | @end |
43 | ||
44 | @implementation CKKSNewTLKOperation | |
b54c578e | 45 | @synthesize keyset; |
866f8763 A |
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 | ||
866f8763 A |
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. | |
8a50f688 | 86 | [ckks dispatchSyncWithAccountKeys: ^bool{ |
866f8763 A |
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 | ||
866f8763 A |
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 | ||
b54c578e A |
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 | |
866f8763 A |
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 | ||
b54c578e A |
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; | |
866f8763 | 198 | |
b54c578e | 199 | keyset.proposed = YES; |
866f8763 A |
200 | |
201 | if(wrappedOldTLK) { | |
b54c578e | 202 | // TODO o no |
866f8763 A |
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! | |
b54c578e | 206 | ckksnotice("ckkstlk", ckks, "Saving new keys %@ to keychain", keyset); |
866f8763 A |
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 | ||
8a50f688 | 218 | // Generate the TLK sharing records for all trusted peers |
b54c578e A |
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); | |
ecaf5866 A |
229 | continue; |
230 | } | |
231 | ||
b54c578e A |
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 | } | |
866f8763 | 237 | |
b54c578e A |
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]; | |
866f8763 | 240 | |
b54c578e | 241 | [tlkShares addObject:share]; |
866f8763 | 242 | } |
b54c578e | 243 | } |
866f8763 | 244 | |
b54c578e | 245 | keyset.pendingTLKShares = [tlkShares allObjects]; |
866f8763 | 246 | |
b54c578e | 247 | self.keyset = keyset; |
866f8763 | 248 | |
b54c578e | 249 | [ckks _onqueueAdvanceKeyStateMachineToState:SecCKKSZoneKeyStateWaitForTLKUpload withError:nil]; |
866f8763 | 250 | |
866f8763 A |
251 | return true; |
252 | }]; | |
253 | } | |
254 | ||
255 | - (void)cancel { | |
866f8763 A |
256 | [super cancel]; |
257 | } | |
258 | ||
259 | @end; | |
260 | ||
261 | #endif |