]>
Commit | Line | Data |
---|---|---|
866f8763 A |
1 | /* |
2 | * Copyright (c) 2017 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 | ||
8a50f688 A |
24 | #if OCTAGON |
25 | ||
866f8763 A |
26 | #import "CKKSViewManager.h" |
27 | #import "CKKSKeychainView.h" | |
28 | #import "CKKSCurrentKeyPointer.h" | |
29 | #import "CKKSKey.h" | |
d64be36e | 30 | #import "keychain/ckks/CKKSPeerProvider.h" |
79b9da22 | 31 | #import "keychain/categories/NSError+UsefulConstructors.h" |
7fb2cbd2 | 32 | #include "keychain/securityd/SecItemSchema.h" |
866f8763 A |
33 | #include <Security/SecItem.h> |
34 | #include <Security/SecItemPriv.h> | |
b54c578e | 35 | #include "OSX/sec/Security/SecItemShim.h" |
866f8763 | 36 | |
866f8763 A |
37 | #include <CloudKit/CloudKit.h> |
38 | #include <CloudKit/CloudKit_Private.h> | |
39 | ||
b54c578e | 40 | #import <Foundation/NSData_Private.h> |
ecaf5866 | 41 | |
866f8763 A |
42 | @implementation CKKSKey |
43 | ||
44 | - (instancetype)init { | |
d64be36e A |
45 | if ((self = [super init])) { |
46 | } | |
866f8763 A |
47 | return self; |
48 | } | |
49 | ||
50 | - (instancetype) initSelfWrappedWithAESKey: (CKKSAESSIVKey*) aeskey | |
51 | uuid: (NSString*) uuid | |
52 | keyclass: (CKKSKeyClass*)keyclass | |
53 | state: (CKKSProcessedState*) state | |
54 | zoneID: (CKRecordZoneID*) zoneID | |
55 | encodedCKRecord: (NSData*) encodedrecord | |
56 | currentkey: (NSInteger) currentkey | |
57 | { | |
b54c578e A |
58 | if((self = [super initWithCKRecordType:SecCKRecordIntermediateKeyType |
59 | encodedCKRecord:encodedrecord | |
60 | zoneID:zoneID])) { | |
61 | ||
62 | _keycore = [[CKKSKeychainBackedKey alloc] initSelfWrappedWithAESKey:aeskey | |
63 | uuid:uuid | |
64 | keyclass:keyclass | |
65 | zoneID:zoneID]; | |
66 | if(!_keycore) { | |
866f8763 A |
67 | return nil; |
68 | } | |
b54c578e A |
69 | |
70 | _currentkey = !!currentkey; | |
71 | _state = state; | |
866f8763 A |
72 | } |
73 | return self; | |
74 | } | |
75 | ||
76 | - (instancetype) initWrappedBy: (CKKSKey*) wrappingKey | |
77 | AESKey: (CKKSAESSIVKey*) aeskey | |
78 | uuid: (NSString*) uuid | |
79 | keyclass: (CKKSKeyClass*)keyclass | |
80 | state: (CKKSProcessedState*) state | |
81 | zoneID: (CKRecordZoneID*) zoneID | |
82 | encodedCKRecord: (NSData*) encodedrecord | |
83 | currentkey: (NSInteger) currentkey | |
84 | { | |
b54c578e A |
85 | if((self = [super initWithCKRecordType:SecCKRecordIntermediateKeyType |
86 | encodedCKRecord:encodedrecord | |
87 | zoneID:zoneID])) { | |
88 | _keycore = [[CKKSKeychainBackedKey alloc] initWrappedBy:wrappingKey.keycore | |
89 | AESKey:aeskey | |
90 | uuid:uuid | |
91 | keyclass:keyclass | |
92 | zoneID:zoneID]; | |
93 | if(!_keycore) { | |
866f8763 A |
94 | return nil; |
95 | } | |
b54c578e A |
96 | |
97 | _currentkey = !!currentkey; | |
98 | _state = state; | |
866f8763 A |
99 | } |
100 | return self; | |
101 | } | |
102 | ||
103 | - (instancetype) initWithWrappedAESKey: (CKKSWrappedAESSIVKey*) wrappedaeskey | |
104 | uuid: (NSString*) uuid | |
105 | parentKeyUUID: (NSString*) parentKeyUUID | |
106 | keyclass: (CKKSKeyClass*)keyclass | |
107 | state: (CKKSProcessedState*) state | |
108 | zoneID: (CKRecordZoneID*) zoneID | |
109 | encodedCKRecord: (NSData*) encodedrecord | |
110 | currentkey: (NSInteger) currentkey | |
111 | { | |
b54c578e A |
112 | if((self = [super initWithCKRecordType:SecCKRecordIntermediateKeyType |
113 | encodedCKRecord:encodedrecord | |
114 | zoneID:zoneID])) { | |
115 | ||
116 | _keycore = [[CKKSKeychainBackedKey alloc] initWithWrappedAESKey:wrappedaeskey | |
117 | uuid:uuid | |
118 | parentKeyUUID:parentKeyUUID | |
119 | keyclass:keyclass | |
120 | zoneID:zoneID]; | |
121 | ||
866f8763 | 122 | _currentkey = !!currentkey; |
866f8763 | 123 | _state = state; |
b54c578e A |
124 | } |
125 | return self; | |
126 | } | |
866f8763 | 127 | |
b54c578e A |
128 | - (instancetype)initWithKeyCore:(CKKSKeychainBackedKey*)core |
129 | { | |
130 | if((self = [super initWithCKRecordType:SecCKRecordIntermediateKeyType | |
131 | encodedCKRecord:nil | |
132 | zoneID:core.zoneID])) { | |
133 | _keycore = core; | |
134 | _currentkey = false; | |
135 | _state = SecCKKSProcessedStateRemote; | |
866f8763 A |
136 | } |
137 | return self; | |
138 | } | |
139 | ||
140 | - (void)dealloc { | |
866f8763 A |
141 | } |
142 | ||
b54c578e A |
143 | - (BOOL)isEqual:(id)object { |
144 | if(![object isKindOfClass:[CKKSKey class]]) { | |
145 | return NO; | |
146 | } | |
147 | ||
148 | CKKSKey* obj = (CKKSKey*)object; | |
149 | ||
150 | // Equality ignores state, currentkey, and CK record differences. Be careful... | |
151 | return [self.keycore isEqual:obj.keycore] ? YES : NO; | |
152 | } | |
153 | ||
154 | // These used to be properties on CKKSKey, but are now properties on the actual key inside | |
155 | - (NSString*)uuid | |
156 | { | |
157 | return self.keycore.uuid; | |
866f8763 A |
158 | } |
159 | ||
d64be36e A |
160 | - (NSString*)zoneName |
161 | { | |
162 | return self.keycore.zoneID.zoneName; | |
163 | } | |
164 | ||
b54c578e A |
165 | - (void)setUuid:(NSString *)uuid |
166 | { | |
167 | self.keycore.uuid = uuid; | |
866f8763 A |
168 | } |
169 | ||
b54c578e A |
170 | - (NSString*)parentKeyUUID |
171 | { | |
172 | return self.keycore.parentKeyUUID; | |
173 | } | |
174 | ||
175 | - (void)setParentKeyUUID:(NSString *)parentKeyUUID | |
176 | { | |
177 | self.keycore.parentKeyUUID = parentKeyUUID; | |
178 | } | |
179 | ||
180 | - (CKKSKeyClass*)keyclass | |
181 | { | |
182 | return self.keycore.keyclass; | |
183 | } | |
184 | ||
185 | - (void)setKeyclass:(CKKSKeyClass*)keyclass | |
186 | { | |
187 | self.keycore.keyclass = keyclass; | |
188 | } | |
189 | ||
190 | - (CKKSWrappedAESSIVKey*)wrappedkey | |
191 | { | |
192 | return self.keycore.wrappedkey; | |
866f8763 A |
193 | } |
194 | ||
b54c578e A |
195 | - (void)setWrappedkey:(CKKSWrappedAESSIVKey*)wrappedkey |
196 | { | |
197 | self.keycore.wrappedkey = wrappedkey; | |
198 | } | |
199 | ||
200 | - (CKKSAESSIVKey*)aessivkey | |
201 | { | |
202 | return self.keycore.aessivkey; | |
203 | } | |
204 | ||
205 | - (bool)wrapsSelf { | |
206 | return [self.keycore wrapsSelf]; | |
207 | } | |
208 | ||
209 | - (bool)wrapUnder: (CKKSKey*) wrappingKey error: (NSError * __autoreleasing *) error { | |
210 | return [self.keycore wrapUnder:wrappingKey.keycore error:error]; | |
866f8763 A |
211 | } |
212 | ||
213 | + (instancetype) loadKeyWithUUID: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
214 | CKKSKey* key = [CKKSKey fromDatabase: uuid zoneID:zoneID error:error]; | |
215 | ||
216 | // failed unwrapping means we can't return a key. | |
217 | if(![key ensureKeyLoaded:error]) { | |
218 | return nil; | |
219 | } | |
220 | return key; | |
221 | } | |
222 | ||
223 | + (CKKSKey*) randomKeyWrappedByParent: (CKKSKey*) parentKey error: (NSError * __autoreleasing *) error { | |
224 | return [self randomKeyWrappedByParent: parentKey keyclass:parentKey.keyclass error:error]; | |
225 | } | |
226 | ||
227 | + (CKKSKey*) randomKeyWrappedByParent: (CKKSKey*) parentKey keyclass:(CKKSKeyClass*)keyclass error: (NSError * __autoreleasing *) error { | |
b54c578e A |
228 | CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error]; |
229 | if(aessivkey == nil) { | |
230 | return nil; | |
231 | } | |
866f8763 A |
232 | |
233 | CKKSKey* key = [[CKKSKey alloc] initWrappedBy: parentKey | |
234 | AESKey: aessivkey | |
235 | uuid:[[NSUUID UUID] UUIDString] | |
236 | keyclass:keyclass | |
237 | state:SecCKKSProcessedStateLocal | |
238 | zoneID: parentKey.zoneID | |
239 | encodedCKRecord: nil | |
240 | currentkey: false]; | |
241 | return key; | |
242 | } | |
243 | ||
244 | + (instancetype)randomKeyWrappedBySelf: (CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error { | |
b54c578e A |
245 | CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error]; |
246 | if(aessivkey == nil) { | |
247 | return nil; | |
248 | } | |
249 | ||
866f8763 A |
250 | NSString* uuid = [[NSUUID UUID] UUIDString]; |
251 | ||
252 | CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: aessivkey | |
253 | uuid:uuid | |
254 | keyclass:SecCKKSKeyClassTLK | |
255 | state:SecCKKSProcessedStateLocal | |
256 | zoneID: zoneID | |
257 | encodedCKRecord: nil | |
258 | currentkey: false]; | |
259 | return key; | |
260 | ||
261 | } | |
262 | ||
263 | - (CKKSKey*)topKeyInAnyState: (NSError * __autoreleasing *) error { | |
b54c578e A |
264 | NSMutableSet<NSString*>* seenUUID = [[NSMutableSet alloc] init]; |
265 | CKKSKey* key = self; | |
866f8763 | 266 | |
b54c578e A |
267 | // Find the top-level key in the hierarchy. |
268 | while (key) { | |
269 | if([key wrapsSelf]) { | |
270 | return key; | |
271 | } | |
272 | ||
273 | // Check for circular references. | |
274 | if([seenUUID containsObject:key.uuid]) { | |
d64be36e A |
275 | if (error) { |
276 | *error = [NSError errorWithDomain:CKKSErrorDomain | |
277 | code:CKKSCircularKeyReference | |
278 | description:@"Circular reference in key hierarchy"]; | |
279 | } | |
b54c578e A |
280 | return nil; |
281 | } | |
866f8763 | 282 | |
b54c578e A |
283 | [seenUUID addObject:key.uuid]; |
284 | ||
285 | // Prefer 'remote' parents. | |
286 | CKKSKey* parent = [CKKSKey tryFromDatabaseWhere: @{@"UUID": key.parentKeyUUID, @"state": SecCKKSProcessedStateRemote} error: error]; | |
287 | ||
288 | // No remote parent. Fall back to anything. | |
289 | if(parent == nil) { | |
290 | parent = [CKKSKey fromDatabaseWhere: @{@"UUID": key.parentKeyUUID} error: error]; | |
291 | } | |
292 | ||
293 | key = parent; | |
866f8763 A |
294 | } |
295 | ||
b54c578e | 296 | // Couldn't get the parent. Error is already filled. |
866f8763 A |
297 | return nil; |
298 | } | |
299 | ||
300 | - (CKKSAESSIVKey*)ensureKeyLoaded: (NSError * __autoreleasing *) error { | |
ecaf5866 | 301 | NSError* keychainError = nil; |
b54c578e A |
302 | |
303 | CKKSAESSIVKey* sivkey = [self.keycore ensureKeyLoaded:&keychainError]; | |
304 | if(sivkey) { | |
305 | return sivkey; | |
866f8763 A |
306 | } |
307 | ||
ecaf5866 A |
308 | // Uhh, okay, if that didn't work, try to unwrap via the key hierarchy |
309 | NSError* keyHierarchyError = nil; | |
310 | if([self unwrapViaKeyHierarchy:&keyHierarchyError]) { | |
311 | // Attempt to save this new key, but don't error if it fails | |
312 | NSError* resaveError = nil; | |
313 | if(![self saveKeyMaterialToKeychain:&resaveError] || resaveError) { | |
d64be36e | 314 | ckkserror("ckkskey", self.zoneID, "Resaving missing key failed, continuing: %@", resaveError); |
ecaf5866 A |
315 | } |
316 | ||
317 | return self.aessivkey; | |
318 | } | |
319 | ||
320 | // Pick an error to report | |
321 | if(error) { | |
322 | *error = keyHierarchyError ? keyHierarchyError : keychainError; | |
323 | } | |
324 | ||
866f8763 A |
325 | return nil; |
326 | } | |
327 | ||
866f8763 A |
328 | - (CKKSAESSIVKey*)unwrapViaKeyHierarchy: (NSError * __autoreleasing *) error { |
329 | if(self.aessivkey) { | |
330 | return self.aessivkey; | |
331 | } | |
332 | ||
333 | NSError* localerror = nil; | |
334 | ||
335 | // Attempt to load this key from the keychain | |
b54c578e | 336 | if([self.keycore loadKeyMaterialFromKeychain:&localerror]) { |
866f8763 A |
337 | // Rad. Success! |
338 | return self.aessivkey; | |
339 | } | |
340 | ||
341 | // First, check if we're a TLK. | |
342 | if([self.keyclass isEqual: SecCKKSKeyClassTLK]) { | |
343 | // Okay, not loading the key from the keychain above is an issue. If we have a parent key, then fall through to the recursion below. | |
344 | if(!self.parentKeyUUID || [self.parentKeyUUID isEqualToString: self.uuid]) { | |
345 | if(error) { | |
346 | *error = localerror; | |
347 | } | |
348 | return nil; | |
349 | } | |
350 | } | |
351 | ||
352 | // Recursively unwrap our parent. | |
353 | CKKSKey* parent = [CKKSKey fromDatabaseAnyState:self.parentKeyUUID zoneID:self.zoneID error:error]; | |
354 | ||
355 | // TODO: do we need loop detection here? | |
356 | if(![parent unwrapViaKeyHierarchy: error]) { | |
357 | return nil; | |
358 | } | |
359 | ||
b54c578e | 360 | self.keycore.aessivkey = [parent unwrapAESKey:self.wrappedkey error:error]; |
866f8763 A |
361 | return self.aessivkey; |
362 | } | |
363 | ||
d64be36e A |
364 | - (BOOL)unwrapViaTLKSharesTrustedBy:(NSArray<CKKSPeerProviderState*>*)trustStates |
365 | error:(NSError**)error | |
366 | { | |
367 | NSError* localerror = nil; | |
368 | ||
369 | if(trustStates.count == 0u) { | |
370 | if(error) { | |
371 | *error = [NSError errorWithDomain:CKKSErrorDomain | |
372 | code:CKKSLackingTrust | |
373 | description:@"No current trust states; can't unwrap TLK"]; | |
374 | } | |
375 | return NO; | |
376 | } | |
377 | ||
378 | NSArray<CKKSTLKShareRecord*>* possibleShares = [CKKSTLKShareRecord allForUUID:self.uuid | |
379 | zoneID:self.zoneID | |
380 | error:&localerror]; | |
381 | ||
382 | if(!possibleShares || localerror) { | |
383 | ckkserror("ckksshare", self, "Unable to load TLK shares for TLK(%@): %@", self, localerror); | |
384 | if(error) { | |
385 | *error = localerror; | |
386 | } | |
387 | return NO; | |
388 | } | |
389 | ||
390 | NSError* lastTrustStateError = nil; | |
391 | for(CKKSPeerProviderState* trustState in trustStates) { | |
392 | BOOL extracted = [trustState unwrapKey:self | |
393 | fromShares:possibleShares | |
394 | error:&localerror]; | |
395 | ||
396 | if(!extracted || localerror) { | |
397 | ckkserror("ckksshare", self, "Failed to recover tlk (%@) from trust state (%@): %@", self.uuid, trustState, localerror); | |
398 | lastTrustStateError = localerror; | |
399 | localerror = nil; | |
400 | } else { | |
401 | ckkserror("ckksshare", self, "Recovered tlk (%@) from trust state (%@)", self.uuid, trustState); | |
402 | return YES; | |
403 | } | |
404 | } | |
405 | ||
406 | // Because there's at least one trustState, then either we returned the TLK above, or we filled in lastTrustStateError. | |
407 | if(error) { | |
408 | *error = lastTrustStateError; | |
409 | } | |
410 | ||
411 | return NO; | |
412 | } | |
413 | ||
414 | - (BOOL)validTLK:(NSError**)error | |
415 | { | |
416 | if(![self wrapsSelf]) { | |
417 | NSError* localerror = [NSError errorWithDomain:CKKSErrorDomain | |
418 | code:CKKSKeyNotSelfWrapped | |
419 | description:[NSString stringWithFormat:@"Potential TLK %@ doesn't wrap itself: %@", | |
420 | self, | |
421 | self.parentKeyUUID] | |
422 | underlying:NULL]; | |
423 | ckkserror("ckksshare", self, "Error with TLK: %@", localerror); | |
424 | if (error) { | |
425 | *error = localerror; | |
426 | } | |
427 | return NO; | |
428 | } | |
429 | ||
430 | return YES; | |
431 | } | |
432 | ||
433 | - (BOOL)tlkMaterialPresentOrRecoverableViaTLKShare:(NSArray<CKKSPeerProviderState*>*)trustStates | |
434 | error:(NSError**)error | |
435 | { | |
436 | // If we have the key material, then this TLK is considered valid. | |
437 | NSError* loadError = nil; | |
438 | CKKSAESSIVKey* loadedKey = [self ensureKeyLoaded:&loadError]; | |
439 | if(!loadedKey || loadError) { | |
440 | if(loadError.code == errSecInteractionNotAllowed) { | |
441 | ckkserror("ckksshare", self, "Unable to load key due to lock state: %@", loadError); | |
442 | if(error) { | |
443 | *error = loadError; | |
444 | } | |
445 | ||
446 | return NO; | |
447 | } | |
448 | ||
449 | ckkserror("ckksshare", self, "Do not yet have this key in the keychain: %@", loadError); | |
450 | // Fall through to attempt to recover the TLK via shares below | |
451 | } else { | |
452 | bool result = [self trySelfWrappedKeyCandidate:loadedKey error:&loadError]; | |
453 | if(result) { | |
454 | // We have a key, and it can decrypt itself. | |
455 | return YES; | |
456 | } else { | |
457 | ckkserror("ckksshare", self, "Some key is present, but the key is not self-wrapped: %@", loadError); | |
458 | // Key seems broken. Fall through. | |
459 | } | |
460 | } | |
461 | ||
462 | NSError* localerror = nil; | |
463 | BOOL success = [self unwrapViaTLKSharesTrustedBy:trustStates | |
464 | error:&localerror]; | |
465 | ||
466 | if(!success || localerror) { | |
467 | ckkserror("ckksshare", self, "Failed to unwrap tlk(%@) via shares: %@", self.uuid, localerror); | |
468 | if(error) { | |
469 | *error = localerror; | |
470 | } | |
471 | return NO; | |
472 | } | |
473 | ||
474 | success = [self saveKeyMaterialToKeychain:true error:&localerror]; | |
475 | ||
476 | if(!success || localerror) { | |
477 | ckkserror("ckksshare", self, "Errored saving TLK to keychain: %@", localerror); | |
478 | ||
479 | if(error) { | |
480 | *error = localerror; | |
481 | return NO; | |
482 | } | |
483 | } | |
484 | ||
485 | return YES; | |
486 | } | |
487 | ||
8a50f688 | 488 | - (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate error:(NSError * __autoreleasing *) error { |
b54c578e | 489 | return [self.keycore trySelfWrappedKeyCandidate:candidate error:error]; |
8a50f688 A |
490 | } |
491 | ||
866f8763 | 492 | - (CKKSWrappedAESSIVKey*)wrapAESKey: (CKKSAESSIVKey*) keyToWrap error: (NSError * __autoreleasing *) error { |
b54c578e | 493 | return [self.keycore wrapAESKey:keyToWrap error:error]; |
866f8763 A |
494 | } |
495 | ||
496 | - (CKKSAESSIVKey*)unwrapAESKey: (CKKSWrappedAESSIVKey*) keyToUnwrap error: (NSError * __autoreleasing *) error { | |
b54c578e | 497 | return [self.keycore unwrapAESKey:keyToUnwrap error:error]; |
866f8763 A |
498 | } |
499 | ||
500 | - (NSData*)encryptData: (NSData*) plaintext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error { | |
b54c578e | 501 | return [self.keycore encryptData:plaintext authenticatedData:ad error:error]; |
866f8763 A |
502 | } |
503 | ||
504 | - (NSData*)decryptData: (NSData*) ciphertext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error { | |
b54c578e | 505 | return [self.keycore decryptData:ciphertext authenticatedData:ad error:error]; |
866f8763 A |
506 | } |
507 | ||
508 | /* Functions to load and save keys from the keychain (where we get to store actual key material!) */ | |
b54c578e A |
509 | - (BOOL)saveKeyMaterialToKeychain: (NSError * __autoreleasing *) error { |
510 | return [self.keycore saveKeyMaterialToKeychain:true error: error]; | |
866f8763 A |
511 | } |
512 | ||
b54c578e A |
513 | - (BOOL)saveKeyMaterialToKeychain: (bool)stashTLK error:(NSError * __autoreleasing *) error { |
514 | return [self.keycore saveKeyMaterialToKeychain:stashTLK error:error]; | |
866f8763 A |
515 | } |
516 | ||
b54c578e A |
517 | - (BOOL)loadKeyMaterialFromKeychain: (NSError * __autoreleasing *) error { |
518 | return [self.keycore loadKeyMaterialFromKeychain:error]; | |
3f0f0d49 A |
519 | } |
520 | ||
b54c578e A |
521 | - (BOOL)deleteKeyMaterialFromKeychain: (NSError * __autoreleasing *) error { |
522 | return [self.keycore deleteKeyMaterialFromKeychain:error]; | |
866f8763 A |
523 | } |
524 | ||
525 | + (instancetype)keyFromKeychain: (NSString*) uuid | |
526 | parentKeyUUID: (NSString*) parentKeyUUID | |
527 | keyclass: (CKKSKeyClass*)keyclass | |
528 | state: (CKKSProcessedState*) state | |
529 | zoneID: (CKRecordZoneID*) zoneID | |
530 | encodedCKRecord: (NSData*) encodedrecord | |
531 | currentkey: (NSInteger) currentkey | |
532 | error: (NSError * __autoreleasing *) error { | |
533 | CKKSKey* key = [[CKKSKey alloc] initWithWrappedAESKey:nil | |
534 | uuid:uuid | |
535 | parentKeyUUID:parentKeyUUID | |
536 | keyclass:keyclass | |
537 | state:state | |
538 | zoneID:zoneID | |
539 | encodedCKRecord:encodedrecord | |
540 | currentkey:currentkey]; | |
541 | ||
542 | if(![key loadKeyMaterialFromKeychain:error]) { | |
543 | return nil; | |
544 | } | |
545 | ||
546 | return key; | |
547 | } | |
548 | ||
b54c578e | 549 | + (NSString* _Nullable)isItemKeyForKeychainView:(SecDbItemRef)item { |
866f8763 A |
550 | |
551 | NSString* accessgroup = (__bridge NSString*) SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup); | |
552 | NSString* description = (__bridge NSString*) SecDbItemGetCachedValueWithName(item, kSecAttrDescription); | |
553 | NSString* server = (__bridge NSString*) SecDbItemGetCachedValueWithName(item, kSecAttrServer); | |
554 | ||
555 | if(accessgroup && description && server && | |
556 | ![accessgroup isEqual:[NSNull null]] && | |
557 | ![description isEqual:[NSNull null]] && | |
558 | ![server isEqual:[NSNull null]] && | |
559 | ||
560 | [accessgroup isEqualToString:@"com.apple.security.ckks"] && | |
561 | ([description isEqualToString: SecCKKSKeyClassTLK] || | |
562 | [description isEqualToString: [NSString stringWithFormat:@"%@-nonsync", SecCKKSKeyClassTLK]] || | |
563 | [description isEqualToString: [NSString stringWithFormat:@"%@-piggy", SecCKKSKeyClassTLK]] || | |
564 | [description isEqualToString: SecCKKSKeyClassA] || | |
565 | [description isEqualToString: SecCKKSKeyClassC])) { | |
566 | ||
567 | // Certainly looks like us! Return the view name. | |
568 | return server; | |
569 | } | |
570 | ||
571 | // Never heard of this item. | |
572 | return nil; | |
573 | } | |
574 | ||
575 | ||
576 | /* Database functions only return keys marked 'local', unless otherwise specified. */ | |
577 | ||
578 | + (instancetype) fromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
579 | return [self fromDatabaseWhere: @{@"UUID": uuid, @"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error: error]; | |
580 | } | |
581 | ||
582 | + (instancetype) fromDatabaseAnyState: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
583 | return [self fromDatabaseWhere: @{@"UUID": uuid, @"ckzone":zoneID.zoneName} error: error]; | |
584 | } | |
585 | ||
586 | + (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
587 | return [self tryFromDatabaseWhere: @{@"UUID": uuid, @"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error: error]; | |
588 | } | |
589 | ||
590 | + (instancetype) tryFromDatabaseAnyState: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
591 | return [self tryFromDatabaseWhere: @{@"UUID": uuid, @"ckzone":zoneID.zoneName} error: error]; | |
592 | } | |
593 | ||
594 | + (NSArray<CKKSKey*>*)selfWrappedKeys:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
b3971512 A |
595 | return [self allWhere: @{@"UUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals |
596 | column:CKKSSQLWhereColumnNameParentKeyUUID], | |
597 | @"state": SecCKKSProcessedStateLocal, | |
598 | @"ckzone":zoneID.zoneName} | |
599 | error:error]; | |
866f8763 A |
600 | } |
601 | ||
b54c578e A |
602 | + (instancetype _Nullable)currentKeyForClass:(CKKSKeyClass*)keyclass |
603 | zoneID:(CKRecordZoneID*)zoneID | |
604 | error:(NSError *__autoreleasing*)error | |
605 | { | |
866f8763 A |
606 | // Load the CurrentKey record, and find the key for it |
607 | CKKSCurrentKeyPointer* ckp = [CKKSCurrentKeyPointer fromDatabase:keyclass zoneID:zoneID error:error]; | |
608 | if(!ckp) { | |
609 | return nil; | |
610 | } | |
611 | return [self fromDatabase:ckp.currentKeyUUID zoneID:zoneID error:error]; | |
612 | } | |
613 | ||
614 | + (NSArray<CKKSKey*>*) currentKeysForClass: (CKKSKeyClass*) keyclass state:(NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
615 | return [self allWhere: @{@"keyclass": keyclass, @"currentkey": @"1", @"state": state ? state : SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error:error]; | |
616 | } | |
617 | ||
618 | /* Returns all keys for a zone */ | |
619 | + (NSArray<CKKSKey*>*)allKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
620 | return [self allWhere: @{@"ckzone":zoneID.zoneName} error:error]; | |
621 | } | |
622 | ||
623 | /* Returns all keys marked 'remote', i.e., downloaded from CloudKit */ | |
624 | + (NSArray<CKKSKey*>*)remoteKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
625 | return [self allWhere: @{@"state": SecCKKSProcessedStateRemote, @"ckzone":zoneID.zoneName} error:error]; | |
626 | } | |
627 | ||
628 | /* Returns all keys marked 'local', i.e., processed in the past */ | |
629 | + (NSArray<CKKSKey*>*)localKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
630 | return [self allWhere: @{@"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error:error]; | |
631 | } | |
632 | ||
633 | - (bool)saveToDatabaseAsOnlyCurrentKeyForClassAndState: (NSError * __autoreleasing *) error { | |
634 | self.currentkey = true; | |
635 | ||
636 | // Find other keys for our key class | |
637 | NSArray<CKKSKey*>* keys = [CKKSKey currentKeysForClass: self.keyclass state: self.state zoneID:self.zoneID error:error]; | |
638 | if(!keys) { | |
639 | return false; | |
640 | } | |
641 | ||
642 | for(CKKSKey* key in keys) { | |
643 | key.currentkey = false; | |
644 | if(![key saveToDatabase: error]) { | |
645 | return false; | |
646 | } | |
647 | } | |
648 | if(![self saveToDatabase: error]) { | |
649 | return false; | |
650 | } | |
651 | ||
652 | return true; | |
653 | } | |
654 | ||
655 | #pragma mark - CKRecord handling | |
656 | ||
b54c578e A |
657 | - (NSString*)CKRecordName |
658 | { | |
659 | return self.keycore.uuid; | |
660 | } | |
661 | ||
866f8763 A |
662 | - (void) setFromCKRecord: (CKRecord*) record { |
663 | if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) { | |
664 | @throw [NSException | |
665 | exceptionWithName:@"WrongCKRecordTypeException" | |
666 | reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordIntermediateKeyType] | |
667 | userInfo:nil]; | |
668 | } | |
669 | ||
670 | [self setStoredCKRecord: record]; | |
671 | ||
b54c578e A |
672 | NSString* uuid = record.recordID.recordName; |
673 | NSString* parentKeyUUID = nil; | |
674 | ||
866f8763 | 675 | if(record[SecCKRecordParentKeyRefKey] != nil) { |
b54c578e | 676 | parentKeyUUID = [record[SecCKRecordParentKeyRefKey] recordID].recordName; |
866f8763 A |
677 | } else { |
678 | // We wrap ourself. | |
b54c578e | 679 | parentKeyUUID = uuid; |
866f8763 A |
680 | } |
681 | ||
b54c578e A |
682 | NSString* keyclass = record[SecCKRecordKeyClassKey]; |
683 | CKKSWrappedAESSIVKey* wrappedkey = | |
684 | [[CKKSWrappedAESSIVKey alloc] initWithBase64:record[SecCKRecordWrappedKeyKey]]; | |
685 | ||
686 | self.keycore = [[CKKSKeychainBackedKey alloc] initWithWrappedAESKey:wrappedkey | |
687 | uuid:uuid | |
688 | parentKeyUUID:parentKeyUUID | |
689 | keyclass:(CKKSKeyClass *)keyclass | |
690 | zoneID:record.recordID.zoneID]; | |
691 | ||
866f8763 A |
692 | self.keyclass = record[SecCKRecordKeyClassKey]; |
693 | self.wrappedkey = [[CKKSWrappedAESSIVKey alloc] initWithBase64: record[SecCKRecordWrappedKeyKey]]; | |
b54c578e A |
694 | |
695 | self.state = SecCKKSProcessedStateRemote; | |
866f8763 A |
696 | } |
697 | ||
698 | - (CKRecord*) updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID { | |
699 | if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) { | |
700 | @throw [NSException | |
701 | exceptionWithName:@"WrongCKRecordTypeException" | |
702 | reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordIntermediateKeyType] | |
703 | userInfo:nil]; | |
704 | } | |
705 | ||
706 | // The parent key must exist in CloudKit, or this record save will fail. | |
707 | if([self.parentKeyUUID isEqual: self.uuid]) { | |
708 | // We wrap ourself. No parent. | |
709 | record[SecCKRecordParentKeyRefKey] = nil; | |
710 | } else { | |
711 | record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.parentKeyUUID zoneID: zoneID] action: CKReferenceActionValidate]; | |
712 | } | |
713 | ||
714 | [CKKSItem setOSVersionInRecord: record]; | |
715 | ||
716 | record[SecCKRecordKeyClassKey] = self.keyclass; | |
717 | record[SecCKRecordWrappedKeyKey] = [self.wrappedkey base64WrappedKey]; | |
718 | ||
719 | return record; | |
720 | } | |
721 | ||
3f0f0d49 A |
722 | - (bool)matchesCKRecord:(CKRecord*)record { |
723 | if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) { | |
724 | return false; | |
725 | } | |
726 | ||
727 | if(![record.recordID.recordName isEqualToString: self.uuid]) { | |
d64be36e | 728 | ckksinfo_global("ckkskey", "UUID does not match"); |
3f0f0d49 A |
729 | return false; |
730 | } | |
731 | ||
732 | // For the parent key ref, ensure that if it's nil, we wrap ourself | |
733 | if(record[SecCKRecordParentKeyRefKey] == nil) { | |
734 | if(![self wrapsSelf]) { | |
d64be36e | 735 | ckksinfo_global("ckkskey", "wrapping key reference (self-wrapped) does not match"); |
3f0f0d49 A |
736 | return false; |
737 | } | |
738 | ||
739 | } else { | |
740 | if(![[[record[SecCKRecordParentKeyRefKey] recordID] recordName] isEqualToString: self.parentKeyUUID]) { | |
d64be36e | 741 | ckksinfo_global("ckkskey", "wrapping key reference (non-self-wrapped) does not match"); |
3f0f0d49 A |
742 | return false; |
743 | } | |
744 | } | |
745 | ||
746 | if(![record[SecCKRecordKeyClassKey] isEqual: self.keyclass]) { | |
d64be36e | 747 | ckksinfo_global("ckkskey", "key class does not match"); |
3f0f0d49 A |
748 | return false; |
749 | } | |
750 | ||
751 | if(![record[SecCKRecordWrappedKeyKey] isEqual: [self.wrappedkey base64WrappedKey]]) { | |
d64be36e | 752 | ckksinfo_global("ckkskey", "wrapped key does not match"); |
3f0f0d49 A |
753 | return false; |
754 | } | |
755 | ||
756 | return true; | |
757 | } | |
758 | ||
759 | ||
866f8763 A |
760 | #pragma mark - Utility |
761 | ||
762 | - (NSString*)description { | |
8a50f688 | 763 | return [NSString stringWithFormat: @"<%@(%@): %@ (%@,%@:%d)>", |
866f8763 A |
764 | NSStringFromClass([self class]), |
765 | self.zoneID.zoneName, | |
766 | self.uuid, | |
767 | self.keyclass, | |
768 | self.state, | |
8a50f688 | 769 | self.currentkey]; |
866f8763 A |
770 | } |
771 | ||
772 | #pragma mark - CKKSSQLDatabaseObject methods | |
773 | ||
774 | + (NSString*) sqlTable { | |
775 | return @"synckeys"; | |
776 | } | |
777 | ||
778 | + (NSArray<NSString*>*) sqlColumns { | |
779 | return @[@"UUID", @"parentKeyUUID", @"ckzone", @"ckrecord", @"keyclass", @"state", @"currentkey", @"wrappedkey"]; | |
780 | } | |
781 | ||
782 | - (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf { | |
783 | return @{@"UUID": self.uuid, @"state": self.state, @"ckzone":self.zoneID.zoneName}; | |
784 | } | |
785 | ||
786 | - (NSDictionary<NSString*,NSString*>*) sqlValues { | |
787 | return @{@"UUID": self.uuid, | |
788 | @"parentKeyUUID": self.parentKeyUUID ? self.parentKeyUUID : self.uuid, // if we don't have a parent, we wrap ourself. | |
789 | @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName), | |
790 | @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]), | |
791 | @"keyclass": CKKSNilToNSNull(self.keyclass), | |
792 | @"state": CKKSNilToNSNull(self.state), | |
793 | @"wrappedkey": CKKSNilToNSNull([self.wrappedkey base64WrappedKey]), | |
794 | @"currentkey": self.currentkey ? @"1" : @"0"}; | |
795 | } | |
796 | ||
b54c578e A |
797 | + (instancetype)fromDatabaseRow:(NSDictionary<NSString*, CKKSSQLResult*>*)row { |
798 | return [[CKKSKey alloc] initWithWrappedAESKey:row[@"wrappedkey"].asString ? [[CKKSWrappedAESSIVKey alloc] initWithBase64: row[@"wrappedkey"].asString] : nil | |
799 | uuid:row[@"UUID"].asString | |
800 | parentKeyUUID:row[@"parentKeyUUID"].asString | |
801 | keyclass:(CKKSKeyClass*)row[@"keyclass"].asString | |
802 | state:(CKKSProcessedState*)row[@"state"].asString | |
803 | zoneID:[[CKRecordZoneID alloc] initWithZoneName:row[@"ckzone"].asString ownerName:CKCurrentUserDefaultName] | |
804 | encodedCKRecord:row[@"ckrecord"].asBase64DecodedData | |
805 | currentkey:row[@"currentkey"].asNSInteger]; | |
866f8763 A |
806 | |
807 | } | |
808 | ||
809 | + (NSDictionary<NSString*,NSNumber*>*)countsByClass:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error { | |
810 | NSMutableDictionary* results = [[NSMutableDictionary alloc] init]; | |
811 | ||
812 | [CKKSSQLDatabaseObject queryDatabaseTable: [[self class] sqlTable] | |
813 | where: @{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)} | |
814 | columns: @[@"keyclass", @"state", @"count(rowid)"] | |
815 | groupBy: @[@"keyclass", @"state"] | |
816 | orderBy:nil | |
817 | limit: -1 | |
b54c578e A |
818 | processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) { |
819 | results[[NSString stringWithFormat: @"%@-%@", row[@"state"].asString, row[@"keyclass"].asString]] = | |
820 | row[@"count(rowid)"].asNSNumberInteger; | |
866f8763 A |
821 | } |
822 | error: error]; | |
823 | return results; | |
824 | } | |
825 | ||
826 | - (instancetype)copyWithZone:(NSZone *)zone { | |
827 | CKKSKey *keyCopy = [super copyWithZone:zone]; | |
b54c578e A |
828 | keyCopy->_keycore = [_keycore copyWithZone:zone]; |
829 | ||
866f8763 | 830 | keyCopy->_state = _state; |
866f8763 A |
831 | keyCopy->_currentkey = _currentkey; |
832 | return keyCopy; | |
833 | } | |
834 | ||
8a50f688 A |
835 | - (NSData*)serializeAsProtobuf: (NSError * __autoreleasing *) error { |
836 | if(![self ensureKeyLoaded:error]) { | |
837 | return nil; | |
838 | } | |
839 | CKKSSerializedKey* proto = [[CKKSSerializedKey alloc] init]; | |
840 | ||
841 | proto.uuid = self.uuid; | |
842 | proto.zoneName = self.zoneID.zoneName; | |
843 | proto.keyclass = self.keyclass; | |
b54c578e | 844 | proto.key = [NSData _newZeroingDataWithBytes:self.aessivkey->key length:self.aessivkey->size]; |
8a50f688 A |
845 | |
846 | return proto.data; | |
847 | } | |
848 | ||
849 | + (CKKSKey*)loadFromProtobuf:(NSData*)data error:(NSError* __autoreleasing *)error { | |
850 | CKKSSerializedKey* key = [[CKKSSerializedKey alloc] initWithData: data]; | |
851 | if(key && key.uuid && key.zoneName && key.keyclass && key.key) { | |
852 | return [[CKKSKey alloc] initSelfWrappedWithAESKey:[[CKKSAESSIVKey alloc] initWithBytes:(uint8_t*)key.key.bytes len:key.key.length] | |
853 | uuid:key.uuid | |
854 | keyclass:(CKKSKeyClass*)key.keyclass // TODO sanitize | |
855 | state:SecCKKSProcessedStateRemote | |
856 | zoneID:[[CKRecordZoneID alloc] initWithZoneName:key.zoneName | |
857 | ownerName:CKCurrentUserDefaultName] | |
858 | encodedCKRecord:nil | |
859 | currentkey:false]; | |
860 | } | |
861 | ||
862 | if(error) { | |
863 | *error = [NSError errorWithDomain:CKKSErrorDomain code:CKKSProtobufFailure description:@"Data failed to parse as a CKKSSerializedKey"]; | |
864 | } | |
865 | return nil; | |
866 | } | |
867 | ||
866f8763 A |
868 | @end |
869 | ||
870 | #endif // OCTAGON |