]> git.saurik.com Git - apple/security.git/blame - keychain/ckks/CKKSKey.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / ckks / CKKSKey.m
CommitLineData
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
24#import "CKKSViewManager.h"
25#import "CKKSKeychainView.h"
26#import "CKKSCurrentKeyPointer.h"
27#import "CKKSKey.h"
28#include <securityd/SecItemSchema.h>
29#include <Security/SecItem.h>
30#include <Security/SecItemPriv.h>
31
32#if OCTAGON
33
34#include <CloudKit/CloudKit.h>
35#include <CloudKit/CloudKit_Private.h>
36
37@implementation CKKSKey
38
39- (instancetype)init {
40 self = [super init];
41 return self;
42}
43
44- (instancetype) initSelfWrappedWithAESKey: (CKKSAESSIVKey*) aeskey
45 uuid: (NSString*) uuid
46 keyclass: (CKKSKeyClass*)keyclass
47 state: (CKKSProcessedState*) state
48 zoneID: (CKRecordZoneID*) zoneID
49 encodedCKRecord: (NSData*) encodedrecord
50 currentkey: (NSInteger) currentkey
51{
52 if(self = [super initWithUUID: uuid
53 parentKeyUUID: uuid
54 zoneID: zoneID
55 encodedCKRecord: encodedrecord
56 encItem: nil
57 wrappedkey: nil
58 generationCount: 0
59 encver: currentCKKSItemEncryptionVersion]) {
60 _keyclass = keyclass;
61 _currentkey = !!currentkey;
62 _aessivkey = aeskey;
63 _state = state;
64
65 self.ckRecordType = SecCKRecordIntermediateKeyType;
66
67 // Wrap the key with the key. Not particularly useful, but there you go.
68 NSError* error = nil;
69 [self wrapUnder: self error:&error];
70 if(error != nil) {
71 secerror("CKKSKey: Couldn't self-wrap key: %@", error);
72 return nil;
73 }
74 }
75 return self;
76}
77
78- (instancetype) initWrappedBy: (CKKSKey*) wrappingKey
79 AESKey: (CKKSAESSIVKey*) aeskey
80 uuid: (NSString*) uuid
81 keyclass: (CKKSKeyClass*)keyclass
82 state: (CKKSProcessedState*) state
83 zoneID: (CKRecordZoneID*) zoneID
84 encodedCKRecord: (NSData*) encodedrecord
85 currentkey: (NSInteger) currentkey
86{
87 if(self = [super initWithUUID: uuid
88 parentKeyUUID: wrappingKey.uuid
89 zoneID: zoneID
90 encodedCKRecord: encodedrecord
91 encItem:nil
92 wrappedkey:nil
93 generationCount:0
94 encver:
95 currentCKKSItemEncryptionVersion]) {
96 _keyclass = keyclass;
97 _currentkey = !!currentkey;
98 _aessivkey = aeskey;
99 _state = state;
100
101 self.ckRecordType = SecCKRecordIntermediateKeyType;
102
103 NSError* error = nil;
104 [self wrapUnder: wrappingKey error:&error];
105 if(error != nil) {
106 secerror("CKKSKey: Couldn't wrap key with key: %@", error);
107 return nil;
108 }
109 }
110 return self;
111}
112
113- (instancetype) initWithWrappedAESKey: (CKKSWrappedAESSIVKey*) wrappedaeskey
114 uuid: (NSString*) uuid
115 parentKeyUUID: (NSString*) parentKeyUUID
116 keyclass: (CKKSKeyClass*)keyclass
117 state: (CKKSProcessedState*) state
118 zoneID: (CKRecordZoneID*) zoneID
119 encodedCKRecord: (NSData*) encodedrecord
120 currentkey: (NSInteger) currentkey
121{
122 if(self = [super initWithUUID:uuid
123 parentKeyUUID:parentKeyUUID
124 zoneID:zoneID
125 encodedCKRecord:encodedrecord
126 encItem:nil
127 wrappedkey:wrappedaeskey
128 generationCount:0
129 encver:currentCKKSItemEncryptionVersion]) {
130 _keyclass = keyclass;
131 _currentkey = !!currentkey;
132 _aessivkey = nil;
133 _state = state;
134
135 self.ckRecordType = SecCKRecordIntermediateKeyType;
136 }
137 return self;
138}
139
140- (void)dealloc {
141 [self zeroKeys];
142}
143
144- (void)zeroKeys {
145 [self.aessivkey zeroKey];
146}
147
148- (bool)wrapsSelf {
149 return [self.uuid isEqual: self.parentKeyUUID];
150}
151
152- (bool)wrapUnder: (CKKSKey*) wrappingKey error: (NSError * __autoreleasing *) error {
153 self.wrappedkey = [wrappingKey wrapAESKey: self.aessivkey error:error];
154 if(self.wrappedkey == nil) {
155 secerror("CKKSKey: couldn't wrap key: %@", error ? *error : @"unknown error");
156 } else {
157 self.parentKeyUUID = wrappingKey.uuid;
158 }
159 return (self.wrappedkey != nil);
160}
161
162- (bool)unwrapSelfWithAESKey: (CKKSAESSIVKey*) unwrappingKey error: (NSError * __autoreleasing *) error {
163 _aessivkey = [unwrappingKey unwrapAESKey:self.wrappedkey error:error];
164 return (_aessivkey != nil);
165}
166
167+ (instancetype) loadKeyWithUUID: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
168 CKKSKey* key = [CKKSKey fromDatabase: uuid zoneID:zoneID error:error];
169
170 // failed unwrapping means we can't return a key.
171 if(![key ensureKeyLoaded:error]) {
172 return nil;
173 }
174 return key;
175}
176
177+ (CKKSKey*) randomKeyWrappedByParent: (CKKSKey*) parentKey error: (NSError * __autoreleasing *) error {
178 return [self randomKeyWrappedByParent: parentKey keyclass:parentKey.keyclass error:error];
179}
180
181+ (CKKSKey*) randomKeyWrappedByParent: (CKKSKey*) parentKey keyclass:(CKKSKeyClass*)keyclass error: (NSError * __autoreleasing *) error {
182 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey];
183
184 CKKSKey* key = [[CKKSKey alloc] initWrappedBy: parentKey
185 AESKey: aessivkey
186 uuid:[[NSUUID UUID] UUIDString]
187 keyclass:keyclass
188 state:SecCKKSProcessedStateLocal
189 zoneID: parentKey.zoneID
190 encodedCKRecord: nil
191 currentkey: false];
192 return key;
193}
194
195+ (instancetype)randomKeyWrappedBySelf: (CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error {
196 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey];
197 NSString* uuid = [[NSUUID UUID] UUIDString];
198
199 CKKSKey* key = [[CKKSKey alloc] initSelfWrappedWithAESKey: aessivkey
200 uuid:uuid
201 keyclass:SecCKKSKeyClassTLK
202 state:SecCKKSProcessedStateLocal
203 zoneID: zoneID
204 encodedCKRecord: nil
205 currentkey: false];
206 return key;
207
208}
209
210- (CKKSKey*)topKeyInAnyState: (NSError * __autoreleasing *) error {
211 // Recursively find the top-level key in the hierarchy, preferring 'remote' keys.
212 if([self wrapsSelf]) {
213 return self;
214 }
215
216 CKKSKey* remoteParent = [CKKSKey tryFromDatabaseWhere: @{@"UUID": self.parentKeyUUID, @"state": SecCKKSProcessedStateRemote} error: error];
217 if(remoteParent) {
218 return [remoteParent topKeyInAnyState: error];
219 }
220
221 // No remote parent. Fall back to anything.
222 CKKSKey* parent = [CKKSKey fromDatabaseWhere: @{@"UUID": self.parentKeyUUID} error: error];
223 if(parent) {
224 return [parent topKeyInAnyState: error];
225 }
226
227 // No good. Error is already filled.
228 return nil;
229}
230
231- (CKKSAESSIVKey*)ensureKeyLoaded: (NSError * __autoreleasing *) error {
232 if(self.aessivkey) {
233 return self.aessivkey;
234 }
235
236 // Attempt to load this key from the keychain
237 if([self loadKeyMaterialFromKeychain:error]) {
238 return self.aessivkey;
239 }
240
241 return nil;
242}
243
244
245- (CKKSAESSIVKey*)unwrapViaKeyHierarchy: (NSError * __autoreleasing *) error {
246 if(self.aessivkey) {
247 return self.aessivkey;
248 }
249
250 NSError* localerror = nil;
251
252 // Attempt to load this key from the keychain
253 if([self loadKeyMaterialFromKeychain:&localerror]) {
254 // Rad. Success!
255 return self.aessivkey;
256 }
257
258 // First, check if we're a TLK.
259 if([self.keyclass isEqual: SecCKKSKeyClassTLK]) {
260 // 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.
261 if(!self.parentKeyUUID || [self.parentKeyUUID isEqualToString: self.uuid]) {
262 if(error) {
263 *error = localerror;
264 }
265 return nil;
266 }
267 }
268
269 // Recursively unwrap our parent.
270 CKKSKey* parent = [CKKSKey fromDatabaseAnyState:self.parentKeyUUID zoneID:self.zoneID error:error];
271
272 // TODO: do we need loop detection here?
273 if(![parent unwrapViaKeyHierarchy: error]) {
274 return nil;
275 }
276
277 _aessivkey = [parent unwrapAESKey:self.wrappedkey error:error];
278 return self.aessivkey;
279}
280
281- (CKKSWrappedAESSIVKey*)wrapAESKey: (CKKSAESSIVKey*) keyToWrap error: (NSError * __autoreleasing *) error {
282 CKKSAESSIVKey* key = [self ensureKeyLoaded: error];
283 CKKSWrappedAESSIVKey* wrappedkey = [key wrapAESKey: keyToWrap error:error];
284 return wrappedkey;
285}
286
287- (CKKSAESSIVKey*)unwrapAESKey: (CKKSWrappedAESSIVKey*) keyToUnwrap error: (NSError * __autoreleasing *) error {
288 CKKSAESSIVKey* key = [self ensureKeyLoaded: error];
289 CKKSAESSIVKey* unwrappedkey = [key unwrapAESKey: keyToUnwrap error:error];
290 return unwrappedkey;
291}
292
293- (NSData*)encryptData: (NSData*) plaintext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
294 CKKSAESSIVKey* key = [self ensureKeyLoaded: error];
295 NSData* data = [key encryptData: plaintext authenticatedData:ad error:error];
296 return data;
297}
298
299- (NSData*)decryptData: (NSData*) ciphertext authenticatedData: (NSDictionary<NSString*, NSData*>*) ad error: (NSError * __autoreleasing *) error {
300 CKKSAESSIVKey* key = [self ensureKeyLoaded: error];
301 NSData* data = [key decryptData: ciphertext authenticatedData:ad error:error];
302 return data;
303}
304
305/* Functions to load and save keys from the keychain (where we get to store actual key material!) */
306- (bool)saveKeyMaterialToKeychain: (NSError * __autoreleasing *) error {
307 return [self saveKeyMaterialToKeychain: true error: error];
308}
309
310- (bool)saveKeyMaterialToKeychain: (bool)stashTLK error:(NSError * __autoreleasing *) error {
311 return [CKKSKey saveKeyMaterialToKeychain:self stashTLK:stashTLK error:error];
312}
313
314+(bool)saveKeyMaterialToKeychain:(CKKSKey*)key stashTLK:(bool)stashTLK error:(NSError * __autoreleasing *) error {
315
316 // Note that we only store the key class, view, UUID, parentKeyUUID, and key material in the keychain
317 // Any other metadata must be stored elsewhere and filled in at load time.
318
319 if(![key ensureKeyLoaded:error]) {
320 // No key material, nothing to save to keychain.
321 return false;
322 }
323
324 // iOS keychains can't store symmetric keys, so we're reduced to storing this key as a password
325 NSData* keydata = [[[NSData alloc] initWithBytes:key.aessivkey->key length:key.aessivkey->size] base64EncodedDataWithOptions:0];
326 NSMutableDictionary* query = [@{
327 (id)kSecClass : (id)kSecClassInternetPassword,
328 (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
329 (id)kSecAttrNoLegacy : @YES,
330 (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
331 (id)kSecAttrDescription: key.keyclass,
332 (id)kSecAttrServer: key.zoneID.zoneName,
333 (id)kSecAttrAccount: key.uuid,
334 (id)kSecAttrPath: key.parentKeyUUID,
335 (id)kSecAttrIsInvisible: @YES,
336 (id)kSecValueData : keydata,
337 } mutableCopy];
338
339 // Only TLKs are synchronizable. Other keyclasses must synchronize via key hierarchy.
340 if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
341 // Use PCS-MasterKey view so they'll be initial-synced under SOS.
342 query[(id)kSecAttrSyncViewHint] = (id)kSecAttrViewHintPCSMasterKey;
343 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
344 }
345
346 // Class C keys are accessible after first unlock; TLKs and Class A keys are accessible only when unlocked
347 if([key.keyclass isEqualToString: SecCKKSKeyClassC]) {
348 query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleAfterFirstUnlock;
349 } else {
350 query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleWhenUnlocked;
351 }
352
353 OSStatus status = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
354
355 if(status == errSecDuplicateItem) {
356 // Sure, okay, fine, we'll update.
357 error = nil;
358 NSMutableDictionary* update = [@{
359 (id)kSecValueData: keydata,
360 (id)kSecAttrPath: key.parentKeyUUID,
361 } mutableCopy];
362 query[(id)kSecValueData] = nil;
363 query[(id)kSecAttrPath] = nil;
364
365 // Udpate the view-hint, too
366 if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
367 update[(id)kSecAttrSyncViewHint] = (id)kSecAttrViewHintPCSMasterKey;
368 query[(id)kSecAttrSyncViewHint] = nil;
369 }
370
371 status = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef)update);
372 }
373
374 if(status && error) {
375 *error = [NSError errorWithDomain:@"securityd"
376 code:status
377 userInfo:@{NSLocalizedDescriptionKey:
378 [NSString stringWithFormat:@"Couldn't save %@ to keychain: %d", self, (int)status]}];
379 }
380
381 // TLKs are synchronizable. Stash them nonsyncably nearby.
382 // Don't report errors here.
383 if(stashTLK && [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
384 query = [@{
385 (id)kSecClass : (id)kSecClassInternetPassword,
386 (id)kSecAttrAccessible: (id)kSecAttrAccessibleWhenUnlocked,
387 (id)kSecAttrNoLegacy : @YES,
388 (id)kSecAttrAccessGroup: @"com.apple.security.ckks",
389 (id)kSecAttrDescription: [key.keyclass stringByAppendingString: @"-nonsync"],
390 (id)kSecAttrServer: key.zoneID.zoneName,
391 (id)kSecAttrAccount: key.uuid,
392 (id)kSecAttrPath: key.parentKeyUUID,
393 (id)kSecAttrIsInvisible: @YES,
394 (id)kSecValueData : keydata,
395 } mutableCopy];
396 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanFalse;
397
398 OSStatus stashstatus = SecItemAdd((__bridge CFDictionaryRef) query, NULL);
399 if(stashstatus != errSecSuccess) {
400 if(stashstatus == errSecDuplicateItem) {
401 // Sure, okay, fine, we'll update.
402 error = nil;
403 NSDictionary* update = @{
404 (id)kSecValueData: keydata,
405 (id)kSecAttrPath: key.parentKeyUUID,
406 };
407 query[(id)kSecValueData] = nil;
408 query[(id)kSecAttrPath] = nil;
409
410 stashstatus = SecItemUpdate((__bridge CFDictionaryRef) query, (__bridge CFDictionaryRef)update);
411 }
412
413 if(stashstatus != errSecSuccess) {
414 secerror("ckkskey: Couldn't stash %@ to keychain: %d", self, (int)stashstatus);
415 }
416 }
417 }
418
419 return status == 0;
420}
421
422+ (NSData*)loadKeyMaterialFromKeychain:(CKKSKey*)key resave:(bool*)resavePtr error:(NSError* __autoreleasing *)error {
423 NSMutableDictionary* query = [@{
424 (id)kSecClass : (id)kSecClassInternetPassword,
425 (id)kSecAttrNoLegacy : @YES,
426 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
427 (id)kSecAttrDescription: key.keyclass,
428 (id)kSecAttrAccount: key.uuid,
429 (id)kSecAttrServer: key.zoneID.zoneName,
430 (id)kSecReturnAttributes: @YES,
431 (id)kSecReturnData: @YES,
432 } mutableCopy];
433
434 // Synchronizable items are only found if you request synchronizable items. Only TLKs are synchronizable.
435 if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
436 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
437 }
438
439 CFTypeRef result = NULL;
440 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &result);
441
442 if(status == errSecItemNotFound) {
443 CFReleaseNull(result);
444 //didn't find a regular tlk? how about a piggy?
445 if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
446 query = [@{
447 (id)kSecClass : (id)kSecClassInternetPassword,
448 (id)kSecAttrNoLegacy : @YES,
449 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
450 (id)kSecAttrDescription: @"tlk-piggy",
451 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
452 (id)kSecAttrAccount: [NSString stringWithFormat: @"%@-piggy", key.uuid],
453 (id)kSecAttrServer: key.zoneID.zoneName,
454 (id)kSecReturnAttributes: @YES,
455 (id)kSecReturnData: @YES,
456 (id)kSecMatchLimit: (id)kSecMatchLimitOne,
457 } mutableCopy];
458 status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &result);
459 if(status == errSecSuccess){
460 secnotice("ckkskey", "loaded a piggy TLK (%@)", key.uuid);
461
462 if(resavePtr) {
463 *resavePtr = true;
464 }
465 }
466 }
467 }
468 if(status == errSecItemNotFound && [key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
469 CFReleaseNull(result);
470
471 // Try to look for the non-syncable stashed tlk and resurrect it.
472 query = [@{
473 (id)kSecClass : (id)kSecClassInternetPassword,
474 (id)kSecAttrNoLegacy : @YES,
475 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
476 (id)kSecAttrDescription: [key.keyclass stringByAppendingString: @"-nonsync"],
477 (id)kSecAttrServer: key.zoneID.zoneName,
478 (id)kSecAttrAccount: key.uuid,
479 (id)kSecReturnAttributes: @YES,
480 (id)kSecReturnData: @YES,
481 (id)kSecAttrSynchronizable: @NO,
482 } mutableCopy];
483
484 status = SecItemCopyMatching((__bridge CFDictionaryRef) query, &result);
485 if(status == errSecSuccess) {
486 secnotice("ckkskey", "loaded a stashed TLK (%@)", key.uuid);
487
488 if(resavePtr) {
489 *resavePtr = true;
490 }
491 }
492 }
493
494 if(status){ //still can't find it!
495 if(error) {
496 *error = [NSError errorWithDomain:@"securityd"
497 code:status
498 userInfo:@{NSLocalizedDescriptionKey:
499 [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d", self, (int)status]}];
500 }
501 return false;
502 }
503
504 // Determine if we should fix up any attributes on this item...
505 NSDictionary* resultDict = CFBridgingRelease(result);
506
507 // We created some TLKs with no ViewHint. Fix it.
508 if([key.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
509 NSString* viewHint = resultDict[(id)kSecAttrSyncViewHint];
510 if(!viewHint) {
511 ckksnotice("ckkskey", key.zoneID, "Fixing up non-viewhinted TLK %@", self);
512 query[(id)kSecReturnAttributes] = nil;
513 query[(id)kSecReturnData] = nil;
514
515 NSDictionary* update = @{(id)kSecAttrSyncViewHint: (id)kSecAttrViewHintPCSMasterKey};
516
517 status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);
518 if(status) {
519 // Don't report error upwards; this is an optimization fixup.
520 secerror("ckkskey: Couldn't update viewhint on existing TLK %@", self);
521 }
522 }
523 }
524
525 // Okay, back to the real purpose of this function: extract the CFData currently in the results dictionary
526 NSData* b64keymaterial = resultDict[(id)kSecValueData];
527 NSData* keymaterial = [[NSData alloc] initWithBase64EncodedData:b64keymaterial options:0];
528 return keymaterial;
529}
530
531- (bool)loadKeyMaterialFromKeychain: (NSError * __autoreleasing *) error {
532 bool resave = false;
533 NSData* keymaterial = [CKKSKey loadKeyMaterialFromKeychain:self resave:&resave error:error];
534 if(!keymaterial) {
535 return false;
536 }
537
538 CKKSAESSIVKey* key = [[CKKSAESSIVKey alloc] initWithBytes: (uint8_t*) keymaterial.bytes len:keymaterial.length];
539 _aessivkey = key;
540
541 if(resave) {
542 secnotice("ckkskey", "Resaving %@ as per request", self);
543 NSError* resaveError = nil;
544 [self saveKeyMaterialToKeychain:&resaveError];
545 if(resaveError) {
546 secnotice("ckkskey", "Resaving %@ failed: %@", self, resaveError);
547 }
548 }
549
550 return !!(self.aessivkey);
551}
552
553- (bool)deleteKeyMaterialFromKeychain: (NSError * __autoreleasing *) error {
554
555 NSMutableDictionary* query = [@{
556 (id)kSecClass : (id)kSecClassInternetPassword,
557 (id)kSecAttrNoLegacy : @YES,
558 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
559 (id)kSecAttrDescription: self.keyclass,
560 (id)kSecAttrAccount: self.uuid,
561 (id)kSecAttrServer: self.zoneID.zoneName,
562 (id)kSecReturnData: @YES,
563 } mutableCopy];
564
565 // Synchronizable items are only found if you request synchronizable items. Only TLKs are synchronizable.
566 if([self.keyclass isEqualToString: SecCKKSKeyClassTLK]) {
567 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
568 }
569
570 OSStatus status = SecItemDelete((__bridge CFDictionaryRef) query);
571
572 if(status) {
573 if(error) {
574 *error = [NSError errorWithDomain:@"securityd"
575 code:status
576 userInfo:@{NSLocalizedDescriptionKey:
577 [NSString stringWithFormat:@"Couldn't delete %@ from keychain: %d", self, (int)status]}];
578 }
579 return false;
580 }
581 return true;
582}
583
584+ (instancetype)keyFromKeychain: (NSString*) uuid
585 parentKeyUUID: (NSString*) parentKeyUUID
586 keyclass: (CKKSKeyClass*)keyclass
587 state: (CKKSProcessedState*) state
588 zoneID: (CKRecordZoneID*) zoneID
589 encodedCKRecord: (NSData*) encodedrecord
590 currentkey: (NSInteger) currentkey
591 error: (NSError * __autoreleasing *) error {
592 CKKSKey* key = [[CKKSKey alloc] initWithWrappedAESKey:nil
593 uuid:uuid
594 parentKeyUUID:parentKeyUUID
595 keyclass:keyclass
596 state:state
597 zoneID:zoneID
598 encodedCKRecord:encodedrecord
599 currentkey:currentkey];
600
601 if(![key loadKeyMaterialFromKeychain:error]) {
602 return nil;
603 }
604
605 return key;
606}
607
608+ (NSString*)isItemKeyForKeychainView: (SecDbItemRef) item {
609
610 NSString* accessgroup = (__bridge NSString*) SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup);
611 NSString* description = (__bridge NSString*) SecDbItemGetCachedValueWithName(item, kSecAttrDescription);
612 NSString* server = (__bridge NSString*) SecDbItemGetCachedValueWithName(item, kSecAttrServer);
613
614 if(accessgroup && description && server &&
615 ![accessgroup isEqual:[NSNull null]] &&
616 ![description isEqual:[NSNull null]] &&
617 ![server isEqual:[NSNull null]] &&
618
619 [accessgroup isEqualToString:@"com.apple.security.ckks"] &&
620 ([description isEqualToString: SecCKKSKeyClassTLK] ||
621 [description isEqualToString: [NSString stringWithFormat:@"%@-nonsync", SecCKKSKeyClassTLK]] ||
622 [description isEqualToString: [NSString stringWithFormat:@"%@-piggy", SecCKKSKeyClassTLK]] ||
623 [description isEqualToString: SecCKKSKeyClassA] ||
624 [description isEqualToString: SecCKKSKeyClassC])) {
625
626 // Certainly looks like us! Return the view name.
627 return server;
628 }
629
630 // Never heard of this item.
631 return nil;
632}
633
634
635/* Database functions only return keys marked 'local', unless otherwise specified. */
636
637+ (instancetype) fromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
638 return [self fromDatabaseWhere: @{@"UUID": uuid, @"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error: error];
639}
640
641+ (instancetype) fromDatabaseAnyState: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
642 return [self fromDatabaseWhere: @{@"UUID": uuid, @"ckzone":zoneID.zoneName} error: error];
643}
644
645+ (instancetype) tryFromDatabase: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
646 return [self tryFromDatabaseWhere: @{@"UUID": uuid, @"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error: error];
647}
648
649+ (instancetype) tryFromDatabaseAnyState: (NSString*) uuid zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
650 return [self tryFromDatabaseWhere: @{@"UUID": uuid, @"ckzone":zoneID.zoneName} error: error];
651}
652
653+ (NSArray<CKKSKey*>*)selfWrappedKeys:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
654 return [self allWhere: @{@"UUID": [CKKSSQLWhereObject op:@"=" string:@"parentKeyUUID"], @"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error:error];
655}
656
657+ (instancetype) currentKeyForClass: (CKKSKeyClass*) keyclass zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
658 // Load the CurrentKey record, and find the key for it
659 CKKSCurrentKeyPointer* ckp = [CKKSCurrentKeyPointer fromDatabase:keyclass zoneID:zoneID error:error];
660 if(!ckp) {
661 return nil;
662 }
663 return [self fromDatabase:ckp.currentKeyUUID zoneID:zoneID error:error];
664}
665
666+ (NSArray<CKKSKey*>*) currentKeysForClass: (CKKSKeyClass*) keyclass state:(NSString*) state zoneID:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
667 return [self allWhere: @{@"keyclass": keyclass, @"currentkey": @"1", @"state": state ? state : SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error:error];
668}
669
670/* Returns all keys for a zone */
671+ (NSArray<CKKSKey*>*)allKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
672 return [self allWhere: @{@"ckzone":zoneID.zoneName} error:error];
673}
674
675/* Returns all keys marked 'remote', i.e., downloaded from CloudKit */
676+ (NSArray<CKKSKey*>*)remoteKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
677 return [self allWhere: @{@"state": SecCKKSProcessedStateRemote, @"ckzone":zoneID.zoneName} error:error];
678}
679
680/* Returns all keys marked 'local', i.e., processed in the past */
681+ (NSArray<CKKSKey*>*)localKeys: (CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
682 return [self allWhere: @{@"state": SecCKKSProcessedStateLocal, @"ckzone":zoneID.zoneName} error:error];
683}
684
685- (bool)saveToDatabaseAsOnlyCurrentKeyForClassAndState: (NSError * __autoreleasing *) error {
686 self.currentkey = true;
687
688 // Find other keys for our key class
689 NSArray<CKKSKey*>* keys = [CKKSKey currentKeysForClass: self.keyclass state: self.state zoneID:self.zoneID error:error];
690 if(!keys) {
691 return false;
692 }
693
694 for(CKKSKey* key in keys) {
695 key.currentkey = false;
696 if(![key saveToDatabase: error]) {
697 return false;
698 }
699 }
700 if(![self saveToDatabase: error]) {
701 return false;
702 }
703
704 return true;
705}
706
707#pragma mark - CKRecord handling
708
709- (void) setFromCKRecord: (CKRecord*) record {
710 if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) {
711 @throw [NSException
712 exceptionWithName:@"WrongCKRecordTypeException"
713 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordIntermediateKeyType]
714 userInfo:nil];
715 }
716
717 [self setStoredCKRecord: record];
718
719 self.uuid = [[record recordID] recordName];
720 if(record[SecCKRecordParentKeyRefKey] != nil) {
721 self.parentKeyUUID = [record[SecCKRecordParentKeyRefKey] recordID].recordName;
722 } else {
723 // We wrap ourself.
724 self.parentKeyUUID = self.uuid;
725 }
726
727 self.keyclass = record[SecCKRecordKeyClassKey];
728 self.wrappedkey = [[CKKSWrappedAESSIVKey alloc] initWithBase64: record[SecCKRecordWrappedKeyKey]];
729}
730
731- (CKRecord*) updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID {
732 if(![record.recordType isEqual: SecCKRecordIntermediateKeyType]) {
733 @throw [NSException
734 exceptionWithName:@"WrongCKRecordTypeException"
735 reason:[NSString stringWithFormat: @"CKRecordType (%@) was not %@", record.recordType, SecCKRecordIntermediateKeyType]
736 userInfo:nil];
737 }
738
739 // The parent key must exist in CloudKit, or this record save will fail.
740 if([self.parentKeyUUID isEqual: self.uuid]) {
741 // We wrap ourself. No parent.
742 record[SecCKRecordParentKeyRefKey] = nil;
743 } else {
744 record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.parentKeyUUID zoneID: zoneID] action: CKReferenceActionValidate];
745 }
746
747 [CKKSItem setOSVersionInRecord: record];
748
749 record[SecCKRecordKeyClassKey] = self.keyclass;
750 record[SecCKRecordWrappedKeyKey] = [self.wrappedkey base64WrappedKey];
751
752 return record;
753}
754
755#pragma mark - Utility
756
757- (NSString*)description {
758 return [NSString stringWithFormat: @"<%@(%@): %@ (%@,%@:%d) %p>",
759 NSStringFromClass([self class]),
760 self.zoneID.zoneName,
761 self.uuid,
762 self.keyclass,
763 self.state,
764 self.currentkey,
765 &self];
766}
767
768#pragma mark - CKKSSQLDatabaseObject methods
769
770+ (NSString*) sqlTable {
771 return @"synckeys";
772}
773
774+ (NSArray<NSString*>*) sqlColumns {
775 return @[@"UUID", @"parentKeyUUID", @"ckzone", @"ckrecord", @"keyclass", @"state", @"currentkey", @"wrappedkey"];
776}
777
778- (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf {
779 return @{@"UUID": self.uuid, @"state": self.state, @"ckzone":self.zoneID.zoneName};
780}
781
782- (NSDictionary<NSString*,NSString*>*) sqlValues {
783 return @{@"UUID": self.uuid,
784 @"parentKeyUUID": self.parentKeyUUID ? self.parentKeyUUID : self.uuid, // if we don't have a parent, we wrap ourself.
785 @"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
786 @"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
787 @"keyclass": CKKSNilToNSNull(self.keyclass),
788 @"state": CKKSNilToNSNull(self.state),
789 @"wrappedkey": CKKSNilToNSNull([self.wrappedkey base64WrappedKey]),
790 @"currentkey": self.currentkey ? @"1" : @"0"};
791}
792
793+ (instancetype) fromDatabaseRow: (NSDictionary*) row {
794 return [[CKKSKey alloc] initWithWrappedAESKey: row[@"wrappedkey"] ? [[CKKSWrappedAESSIVKey alloc] initWithBase64: row[@"wrappedkey"]] : nil
795 uuid: row[@"UUID"]
796 parentKeyUUID: row[@"parentKeyUUID"]
797 keyclass: row[@"keyclass"]
798 state: row[@"state"]
799 zoneID: [[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName]
800 encodedCKRecord: [[NSData alloc] initWithBase64EncodedString: row[@"ckrecord"] options:0]
801 currentkey: [row[@"currentkey"] integerValue]];
802
803}
804
805+ (NSDictionary<NSString*,NSNumber*>*)countsByClass:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
806 NSMutableDictionary* results = [[NSMutableDictionary alloc] init];
807
808 [CKKSSQLDatabaseObject queryDatabaseTable: [[self class] sqlTable]
809 where: @{@"ckzone": CKKSNilToNSNull(zoneID.zoneName)}
810 columns: @[@"keyclass", @"state", @"count(rowid)"]
811 groupBy: @[@"keyclass", @"state"]
812 orderBy:nil
813 limit: -1
814 processRow: ^(NSDictionary* row) {
815 results[[NSString stringWithFormat: @"%@-%@", row[@"state"], row[@"keyclass"]]] =
816 [NSNumber numberWithInteger: [row[@"count(rowid)"] integerValue]];
817 }
818 error: error];
819 return results;
820}
821
822- (instancetype)copyWithZone:(NSZone *)zone {
823 CKKSKey *keyCopy = [super copyWithZone:zone];
824 keyCopy->_aessivkey = _aessivkey;
825 keyCopy->_state = _state;
826 keyCopy->_keyclass = _keyclass;
827 keyCopy->_currentkey = _currentkey;
828 return keyCopy;
829}
830
831@end
832
833#endif // OCTAGON