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