]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSKey.m
Security-59754.41.1.tar.gz
[apple/security.git] / keychain / ckks / CKKSKey.m
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
24 #if OCTAGON
25
26 #import "CKKSViewManager.h"
27 #import "CKKSKeychainView.h"
28 #import "CKKSCurrentKeyPointer.h"
29 #import "CKKSKey.h"
30 #import "keychain/ckks/CKKSPeerProvider.h"
31 #import "keychain/categories/NSError+UsefulConstructors.h"
32 #include "keychain/securityd/SecItemSchema.h"
33 #include <Security/SecItem.h>
34 #include <Security/SecItemPriv.h>
35 #include "OSX/sec/Security/SecItemShim.h"
36
37 #include <CloudKit/CloudKit.h>
38 #include <CloudKit/CloudKit_Private.h>
39
40 #import <Foundation/NSData_Private.h>
41
42 @implementation CKKSKey
43
44 - (instancetype)init {
45 if ((self = [super init])) {
46 }
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 {
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) {
67 return nil;
68 }
69
70 _currentkey = !!currentkey;
71 _state = state;
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 {
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) {
94 return nil;
95 }
96
97 _currentkey = !!currentkey;
98 _state = state;
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 {
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
122 _currentkey = !!currentkey;
123 _state = state;
124 }
125 return self;
126 }
127
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;
136 }
137 return self;
138 }
139
140 - (void)dealloc {
141 }
142
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;
158 }
159
160 - (NSString*)zoneName
161 {
162 return self.keycore.zoneID.zoneName;
163 }
164
165 - (void)setUuid:(NSString *)uuid
166 {
167 self.keycore.uuid = uuid;
168 }
169
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;
193 }
194
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];
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 {
228 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
229 if(aessivkey == nil) {
230 return nil;
231 }
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 {
245 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
246 if(aessivkey == nil) {
247 return nil;
248 }
249
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 {
264 NSMutableSet<NSString*>* seenUUID = [[NSMutableSet alloc] init];
265 CKKSKey* key = self;
266
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]) {
275 if (error) {
276 *error = [NSError errorWithDomain:CKKSErrorDomain
277 code:CKKSCircularKeyReference
278 description:@"Circular reference in key hierarchy"];
279 }
280 return nil;
281 }
282
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;
294 }
295
296 // Couldn't get the parent. Error is already filled.
297 return nil;
298 }
299
300 - (CKKSAESSIVKey*)ensureKeyLoaded: (NSError * __autoreleasing *) error {
301 NSError* keychainError = nil;
302
303 CKKSAESSIVKey* sivkey = [self.keycore ensureKeyLoaded:&keychainError];
304 if(sivkey) {
305 return sivkey;
306 }
307
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) {
314 ckkserror("ckkskey", self.zoneID, "Resaving missing key failed, continuing: %@", resaveError);
315 }
316
317 return self.aessivkey;
318 }
319
320 // Pick an error to report
321 if(error) {
322 *error = keyHierarchyError ? keyHierarchyError : keychainError;
323 }
324
325 return nil;
326 }
327
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
336 if([self.keycore loadKeyMaterialFromKeychain:&localerror]) {
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
360 self.keycore.aessivkey = [parent unwrapAESKey:self.wrappedkey error:error];
361 return self.aessivkey;
362 }
363
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
488 - (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate error:(NSError * __autoreleasing *) error {
489 return [self.keycore trySelfWrappedKeyCandidate:candidate error:error];
490 }
491
492 - (CKKSWrappedAESSIVKey*)wrapAESKey: (CKKSAESSIVKey*) keyToWrap error: (NSError * __autoreleasing *) error {
493 return [self.keycore wrapAESKey:keyToWrap error:error];
494 }
495
496 - (CKKSAESSIVKey*)unwrapAESKey: (CKKSWrappedAESSIVKey*) keyToUnwrap error: (NSError * __autoreleasing *) error {
497 return [self.keycore unwrapAESKey:keyToUnwrap error:error];
498 }
499
500 - (NSData*)encryptData: (NSData*) plaintext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
501 return [self.keycore encryptData:plaintext authenticatedData:ad error:error];
502 }
503
504 - (NSData*)decryptData: (NSData*) ciphertext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
505 return [self.keycore decryptData:ciphertext authenticatedData:ad error:error];
506 }
507
508 /* Functions to load and save keys from the keychain (where we get to store actual key material!) */
509 - (BOOL)saveKeyMaterialToKeychain: (NSError * __autoreleasing *) error {
510 return [self.keycore saveKeyMaterialToKeychain:true error: error];
511 }
512
513 - (BOOL)saveKeyMaterialToKeychain: (bool)stashTLK error:(NSError * __autoreleasing *) error {
514 return [self.keycore saveKeyMaterialToKeychain:stashTLK error:error];
515 }
516
517 - (BOOL)loadKeyMaterialFromKeychain: (NSError * __autoreleasing *) error {
518 return [self.keycore loadKeyMaterialFromKeychain:error];
519 }
520
521 - (BOOL)deleteKeyMaterialFromKeychain: (NSError * __autoreleasing *) error {
522 return [self.keycore deleteKeyMaterialFromKeychain:error];
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
549 + (NSString* _Nullable)isItemKeyForKeychainView:(SecDbItemRef)item {
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 {
595 return [self allWhere: @{@"UUID": [CKKSSQLWhereColumn op:CKKSSQLWhereComparatorEquals
596 column:CKKSSQLWhereColumnNameParentKeyUUID],
597 @"state": SecCKKSProcessedStateLocal,
598 @"ckzone":zoneID.zoneName}
599 error:error];
600 }
601
602 + (instancetype _Nullable)currentKeyForClass:(CKKSKeyClass*)keyclass
603 zoneID:(CKRecordZoneID*)zoneID
604 error:(NSError *__autoreleasing*)error
605 {
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
657 - (NSString*)CKRecordName
658 {
659 return self.keycore.uuid;
660 }
661
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
672 NSString* uuid = record.recordID.recordName;
673 NSString* parentKeyUUID = nil;
674
675 if(record[SecCKRecordParentKeyRefKey] != nil) {
676 parentKeyUUID = [record[SecCKRecordParentKeyRefKey] recordID].recordName;
677 } else {
678 // We wrap ourself.
679 parentKeyUUID = uuid;
680 }
681
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
692 self.keyclass = record[SecCKRecordKeyClassKey];
693 self.wrappedkey = [[CKKSWrappedAESSIVKey alloc] initWithBase64: record[SecCKRecordWrappedKeyKey]];
694
695 self.state = SecCKKSProcessedStateRemote;
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
722 - (bool)matchesCKRecord:(CKRecord*)record {
723 if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) {
724 return false;
725 }
726
727 if(![record.recordID.recordName isEqualToString: self.uuid]) {
728 ckksinfo_global("ckkskey", "UUID does not match");
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]) {
735 ckksinfo_global("ckkskey", "wrapping key reference (self-wrapped) does not match");
736 return false;
737 }
738
739 } else {
740 if(![[[record[SecCKRecordParentKeyRefKey] recordID] recordName] isEqualToString: self.parentKeyUUID]) {
741 ckksinfo_global("ckkskey", "wrapping key reference (non-self-wrapped) does not match");
742 return false;
743 }
744 }
745
746 if(![record[SecCKRecordKeyClassKey] isEqual: self.keyclass]) {
747 ckksinfo_global("ckkskey", "key class does not match");
748 return false;
749 }
750
751 if(![record[SecCKRecordWrappedKeyKey] isEqual: [self.wrappedkey base64WrappedKey]]) {
752 ckksinfo_global("ckkskey", "wrapped key does not match");
753 return false;
754 }
755
756 return true;
757 }
758
759
760 #pragma mark - Utility
761
762 - (NSString*)description {
763 return [NSString stringWithFormat: @"<%@(%@): %@ (%@,%@:%d)>",
764 NSStringFromClass([self class]),
765 self.zoneID.zoneName,
766 self.uuid,
767 self.keyclass,
768 self.state,
769 self.currentkey];
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
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];
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
818 processRow: ^(NSDictionary<NSString*, CKKSSQLResult*>* row) {
819 results[[NSString stringWithFormat: @"%@-%@", row[@"state"].asString, row[@"keyclass"].asString]] =
820 row[@"count(rowid)"].asNSNumberInteger;
821 }
822 error: error];
823 return results;
824 }
825
826 - (instancetype)copyWithZone:(NSZone *)zone {
827 CKKSKey *keyCopy = [super copyWithZone:zone];
828 keyCopy->_keycore = [_keycore copyWithZone:zone];
829
830 keyCopy->_state = _state;
831 keyCopy->_currentkey = _currentkey;
832 return keyCopy;
833 }
834
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;
844 proto.key = [NSData _newZeroingDataWithBytes:self.aessivkey->key length:self.aessivkey->size];
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
868 @end
869
870 #endif // OCTAGON