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