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