]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSKeychainBackedKey.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ckks / CKKSKeychainBackedKey.m
1
2 #if OCTAGON
3
4 #import "keychain/ckks/CKKSKeychainBackedKey.h"
5
6 #include <CloudKit/CloudKit.h>
7 #include <CloudKit/CloudKit_Private.h>
8 #include <Security/SecItem.h>
9 #include <Security/SecItemPriv.h>
10
11 #import "keychain/categories/NSError+UsefulConstructors.h"
12 #import "keychain/ckks/CKKS.h"
13 #import "keychain/ckks/CKKSItem.h"
14
15 @implementation CKKSKeychainBackedKey
16
17 - (instancetype)initSelfWrappedWithAESKey:(CKKSAESSIVKey*)aeskey
18 uuid:(NSString*)uuid
19 keyclass:(CKKSKeyClass*)keyclass
20 zoneID:(CKRecordZoneID*)zoneID
21 {
22 if((self = [super init])) {
23 _uuid = uuid;
24 _parentKeyUUID = uuid;
25 _zoneID = zoneID;
26
27 _keyclass = keyclass;
28 _aessivkey = aeskey;
29
30 // Wrap the key with the key. Not particularly useful, but there you go.
31 NSError* error = nil;
32 [self wrapUnder:self error:&error];
33 if(error != nil) {
34 ckkserror_global("ckkskey", "Couldn't self-wrap key: %@", error);
35 return nil;
36 }
37 }
38 return self;
39 }
40
41 - (instancetype _Nullable)initWrappedBy:(CKKSKeychainBackedKey*)wrappingKey
42 AESKey:(CKKSAESSIVKey*)aessivkey
43 uuid:(NSString*)uuid
44 keyclass:(CKKSKeyClass*)keyclass
45 zoneID:(CKRecordZoneID*)zoneID
46 {
47 if((self = [super init])) {
48 _uuid = uuid;
49 _parentKeyUUID = uuid;
50 _zoneID = zoneID;
51
52 _keyclass = keyclass;
53 _aessivkey = aessivkey;
54
55 NSError* error = nil;
56 [self wrapUnder:wrappingKey error:&error];
57 if(error != nil) {
58 ckkserror_global("ckkskey", "Couldn't wrap key with key: %@", error);
59 return nil;
60 }
61 }
62 return self;
63 }
64
65 - (instancetype)initWithWrappedAESKey:(CKKSWrappedAESSIVKey*)wrappedaeskey
66 uuid:(NSString*)uuid
67 parentKeyUUID:(NSString*)parentKeyUUID
68 keyclass:(CKKSKeyClass*)keyclass
69 zoneID:(CKRecordZoneID*)zoneID
70 {
71 if((self = [super init])) {
72 _uuid = uuid;
73 _parentKeyUUID = parentKeyUUID;
74 _zoneID = zoneID;
75
76 _wrappedkey = wrappedaeskey;
77
78 _keyclass = keyclass;
79 _aessivkey = nil;
80 }
81 return self;
82 }
83
84 - (instancetype)copyWithZone:(NSZone*)zone
85 {
86 CKKSKeychainBackedKey* c =
87 [[CKKSKeychainBackedKey allocWithZone:zone] initWithWrappedAESKey:self.wrappedkey
88 uuid:self.uuid
89 parentKeyUUID:self.parentKeyUUID
90 keyclass:self.keyclass
91 zoneID:self.zoneID];
92 c.aessivkey = [self.aessivkey copy];
93 return c;
94 }
95
96
97 - (BOOL)isEqual:(id)object
98 {
99 if(![object isKindOfClass:[CKKSKeychainBackedKey class]]) {
100 return NO;
101 }
102
103 CKKSKeychainBackedKey* obj = (CKKSKeychainBackedKey*)object;
104
105 return ([self.uuid isEqual:obj.uuid] && [self.parentKeyUUID isEqual:obj.parentKeyUUID] &&
106 [self.zoneID isEqual:obj.zoneID] && [self.wrappedkey isEqual:obj.wrappedkey] &&
107 [self.keyclass isEqual:obj.keyclass] && true)
108 ? YES
109 : NO;
110 }
111
112 - (bool)wrapsSelf
113 {
114 return [self.uuid isEqual:self.parentKeyUUID];
115 }
116
117 - (bool)wrapUnder:(CKKSKeychainBackedKey*)wrappingKey
118 error:(NSError* __autoreleasing*)error
119 {
120 NSError* localError = nil;
121 CKKSWrappedAESSIVKey* wrappedKey = [wrappingKey wrapAESKey:self.aessivkey error:&localError];
122 if (wrappedKey == nil) {
123 ckkserror_global("ckkskey", "couldn't wrap key: %@", localError);
124 if(error) {
125 *error = localError;
126 }
127 return false;
128 } else {
129 self.wrappedkey = wrappedKey;
130 self.parentKeyUUID = wrappingKey.uuid;
131 }
132 return true;
133 }
134
135 - (bool)unwrapSelfWithAESKey:(CKKSAESSIVKey*)unwrappingKey
136 error:(NSError* __autoreleasing*)error
137 {
138 _aessivkey = [unwrappingKey unwrapAESKey:self.wrappedkey error:error];
139 return (_aessivkey != nil);
140 }
141
142 + (CKKSKeychainBackedKey* _Nullable)randomKeyWrappedByParent:(CKKSKeychainBackedKey*)parentKey
143 error:(NSError* __autoreleasing*)error
144 {
145 return
146 [self randomKeyWrappedByParent:parentKey keyclass:parentKey.keyclass error:error];
147 }
148
149 + (CKKSKeychainBackedKey* _Nullable)randomKeyWrappedByParent:(CKKSKeychainBackedKey*)parentKey
150 keyclass:(CKKSKeyClass*)keyclass
151 error:(NSError* __autoreleasing*)error
152 {
153 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
154 if(aessivkey == nil) {
155 return nil;
156 }
157
158 CKKSKeychainBackedKey* key =
159 [[CKKSKeychainBackedKey alloc] initWrappedBy:parentKey
160 AESKey:aessivkey
161 uuid:[[NSUUID UUID] UUIDString]
162 keyclass:keyclass
163 zoneID:parentKey.zoneID];
164 return key;
165 }
166
167 + (instancetype _Nullable)randomKeyWrappedBySelf:(CKRecordZoneID*)zoneID
168 error:(NSError* __autoreleasing*)error
169 {
170 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
171 if(aessivkey == nil) {
172 return nil;
173 }
174
175 NSString* uuid = [[NSUUID UUID] UUIDString];
176
177 CKKSKeychainBackedKey* key =
178 [[CKKSKeychainBackedKey alloc] initSelfWrappedWithAESKey:aessivkey
179 uuid:uuid
180 keyclass:SecCKKSKeyClassTLK
181 zoneID:zoneID];
182 return key;
183 }
184
185 - (CKKSAESSIVKey*)ensureKeyLoaded:(NSError* __autoreleasing*)error
186 {
187 if(self.aessivkey) {
188 return self.aessivkey;
189 }
190
191 // Attempt to load this key from the keychain
192 if([self loadKeyMaterialFromKeychain:error]) {
193 return self.aessivkey;
194 }
195
196 return nil;
197 }
198
199 - (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate
200 error:(NSError* __autoreleasing*)error
201 {
202 if(![self wrapsSelf]) {
203 if(error) {
204 *error = [NSError errorWithDomain:CKKSErrorDomain
205 code:CKKSKeyNotSelfWrapped
206 userInfo:@{
207 NSLocalizedDescriptionKey : [NSString
208 stringWithFormat:@"%@ is not self-wrapped", self]
209 }];
210 }
211 return false;
212 }
213
214 CKKSAESSIVKey* unwrapped = [candidate unwrapAESKey:self.wrappedkey error:error];
215 if(unwrapped && [unwrapped isEqual:candidate]) {
216 _aessivkey = unwrapped;
217 return true;
218 }
219
220 return false;
221 }
222
223 - (CKKSWrappedAESSIVKey*)wrapAESKey:(CKKSAESSIVKey*)keyToWrap
224 error:(NSError* __autoreleasing*)error
225 {
226 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
227 CKKSWrappedAESSIVKey* wrappedkey = [key wrapAESKey:keyToWrap error:error];
228 return wrappedkey;
229 }
230
231 - (CKKSAESSIVKey*)unwrapAESKey:(CKKSWrappedAESSIVKey*)keyToUnwrap
232 error:(NSError* __autoreleasing*)error
233 {
234 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
235 CKKSAESSIVKey* unwrappedkey = [key unwrapAESKey:keyToUnwrap error:error];
236 return unwrappedkey;
237 }
238
239 - (NSData*)encryptData:(NSData*)plaintext
240 authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
241 error:(NSError* __autoreleasing*)error
242 {
243 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
244 NSData* data = [key encryptData:plaintext authenticatedData:ad error:error];
245 return data;
246 }
247
248 - (NSData*)decryptData:(NSData*)ciphertext
249 authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
250 error:(NSError* __autoreleasing*)error
251 {
252 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
253 NSData* data = [key decryptData:ciphertext authenticatedData:ad error:error];
254 return data;
255 }
256
257 /* Functions to load and save keys from the keychain (where we get to store actual key material!) */
258 - (BOOL)saveKeyMaterialToKeychain:(NSError* __autoreleasing*)error
259 {
260 return [self saveKeyMaterialToKeychain:true error:error];
261 }
262
263 - (BOOL)saveKeyMaterialToKeychain:(bool)stashTLK error:(NSError* __autoreleasing*)error
264 {
265 // Note that we only store the key class, view, UUID, parentKeyUUID, and key material in the keychain
266 // Any other metadata must be stored elsewhere and filled in at load time.
267
268 if(![self ensureKeyLoaded:error]) {
269 // No key material, nothing to save to keychain.
270 return NO;
271 }
272
273 // iOS keychains can't store symmetric keys, so we're reduced to storing this key as a password
274 NSData* keydata =
275 [[[NSData alloc] initWithBytes:self.aessivkey->key length:self.aessivkey->size]
276 base64EncodedDataWithOptions:0];
277 NSMutableDictionary* query = [@{
278 (id)kSecClass : (id)kSecClassInternetPassword,
279 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
280 (id)kSecUseDataProtectionKeychain : @YES,
281 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
282 (id)kSecAttrDescription : self.keyclass,
283 (id)kSecAttrServer : self.zoneID.zoneName,
284 (id)kSecAttrAccount : self.uuid,
285 (id)kSecAttrPath : self.parentKeyUUID,
286 (id)kSecAttrIsInvisible : @YES,
287 (id)kSecValueData : keydata,
288 } mutableCopy];
289
290 // Only TLKs are synchronizable. Other keyclasses must synchronize via key hierarchy.
291 if([self.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
292 // Use PCS-MasterKey view so they'll be initial-synced under SOS.
293 query[(id)kSecAttrSyncViewHint] = (id)kSecAttrViewHintPCSMasterKey;
294 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
295 }
296
297 // Class C keys are accessible after first unlock; TLKs and Class A keys are accessible only when unlocked
298 if([self.keyclass isEqualToString:SecCKKSKeyClassC]) {
299 query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleAfterFirstUnlock;
300 } else {
301 query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleWhenUnlocked;
302 }
303
304 NSError* localError = nil;
305 [CKKSKeychainBackedKey setKeyMaterialInKeychain:query error:&localError];
306
307 if(localError && error) {
308 *error = [NSError errorWithDomain:@"securityd"
309 code:localError.code
310 userInfo:@{
311 NSLocalizedDescriptionKey :
312 [NSString stringWithFormat:@"Couldn't save %@ to keychain: %d",
313 self,
314 (int)localError.code],
315 NSUnderlyingErrorKey : localError,
316 }];
317 }
318
319 // TLKs are synchronizable. Stash them nonsyncably nearby.
320 // Don't report errors here.
321 if(stashTLK && [self.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
322 query = [@{
323 (id)kSecClass : (id)kSecClassInternetPassword,
324 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
325 (id)kSecUseDataProtectionKeychain : @YES,
326 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
327 (id)kSecAttrDescription : [self.keyclass stringByAppendingString:@"-nonsync"],
328 (id)kSecAttrServer : self.zoneID.zoneName,
329 (id)kSecAttrAccount : self.uuid,
330 (id)kSecAttrPath : self.parentKeyUUID,
331 (id)kSecAttrIsInvisible : @YES,
332 (id)kSecValueData : keydata,
333 } mutableCopy];
334 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanFalse;
335
336 NSError* stashError = nil;
337 [CKKSKeychainBackedKey setKeyMaterialInKeychain:query error:&localError];
338
339 if(stashError) {
340 ckkserror_global("ckkskey", "Couldn't stash %@ to keychain: %@", self, stashError);
341 }
342 }
343
344 return (localError == nil) ? YES : NO;
345 }
346
347 + (NSDictionary*)setKeyMaterialInKeychain:(NSDictionary*)query
348 error:(NSError* __autoreleasing*)error
349 {
350 CFTypeRef result = NULL;
351 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, &result);
352
353 NSError* localerror = nil;
354
355 // Did SecItemAdd fall over due to an existing item?
356 if(status == errSecDuplicateItem) {
357 // Add every primary key attribute to this find dictionary
358 NSMutableDictionary* findQuery = [[NSMutableDictionary alloc] init];
359 findQuery[(id)kSecClass] = query[(id)kSecClass];
360 findQuery[(id)kSecAttrSynchronizable] = query[(id)kSecAttrSynchronizable];
361 findQuery[(id)kSecAttrSyncViewHint] = query[(id)kSecAttrSyncViewHint];
362 findQuery[(id)kSecAttrAccessGroup] = query[(id)kSecAttrAccessGroup];
363 findQuery[(id)kSecAttrAccount] = query[(id)kSecAttrAccount];
364 findQuery[(id)kSecAttrServer] = query[(id)kSecAttrServer];
365 findQuery[(id)kSecAttrPath] = query[(id)kSecAttrPath];
366 findQuery[(id)kSecUseDataProtectionKeychain] = query[(id)kSecUseDataProtectionKeychain];
367
368 NSMutableDictionary* updateQuery = [query mutableCopy];
369 updateQuery[(id)kSecClass] = nil;
370
371 status = SecItemUpdate((__bridge CFDictionaryRef)findQuery, (__bridge CFDictionaryRef)updateQuery);
372
373 if(status) {
374 localerror = [NSError
375 errorWithDomain:@"securityd"
376 code:status
377 description:[NSString stringWithFormat:@"SecItemUpdate: %d", (int)status]];
378 }
379 } else {
380 localerror = [NSError
381 errorWithDomain:@"securityd"
382 code:status
383 description:[NSString stringWithFormat:@"SecItemAdd: %d", (int)status]];
384 }
385
386 if(status) {
387 CFReleaseNull(result);
388
389 if(error) {
390 *error = localerror;
391 }
392 return nil;
393 }
394
395 NSDictionary* resultDict = CFBridgingRelease(result);
396 return resultDict;
397 }
398
399 + (NSDictionary*)queryKeyMaterialInKeychain:(NSDictionary*)query
400 error:(NSError* __autoreleasing*)error
401 {
402 CFTypeRef result = NULL;
403 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
404
405 if(status) {
406 CFReleaseNull(result);
407
408 if(error) {
409 *error = [NSError
410 errorWithDomain:@"securityd"
411 code:status
412 userInfo:@{
413 NSLocalizedDescriptionKey :
414 [NSString stringWithFormat:@"SecItemCopyMatching: %d", (int)status]
415 }];
416 }
417 return nil;
418 }
419
420 NSDictionary* resultDict = CFBridgingRelease(result);
421 return resultDict;
422 }
423
424 + (NSDictionary*)fetchKeyMaterialItemFromKeychain:(CKKSKeychainBackedKey*)key
425 resave:(bool*)resavePtr
426 error:(NSError* __autoreleasing*)error
427 {
428 NSMutableDictionary* query = [@{
429 (id)kSecClass : (id)kSecClassInternetPassword,
430 (id)kSecUseDataProtectionKeychain : @YES,
431 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
432 (id)kSecAttrDescription : key.keyclass,
433 (id)kSecAttrAccount : key.uuid,
434 (id)kSecAttrServer : key.zoneID.zoneName,
435 (id)kSecAttrPath : key.parentKeyUUID,
436 (id)kSecReturnAttributes : @YES,
437 (id)kSecReturnData : @YES,
438 } mutableCopy];
439
440 // Synchronizable items are only found if you request synchronizable items. Only TLKs are synchronizable.
441 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
442 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
443 }
444
445 NSError* localError = nil;
446 NSDictionary* result = [self queryKeyMaterialInKeychain:query error:&localError];
447 NSError* originalError = localError;
448
449 // If we found the item or errored in some interesting way, return.
450 if(result) {
451 return result;
452 }
453 if(localError && localError.code != errSecItemNotFound) {
454 if(error) {
455 *error = [NSError errorWithDomain:@"securityd"
456 code:localError.code
457 userInfo:@{
458 NSLocalizedDescriptionKey :
459 [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d",
460 key,
461 (int)localError.code],
462 NSUnderlyingErrorKey : localError,
463 }];
464 }
465 return result;
466 }
467 localError = nil;
468
469 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
470 //didn't find a regular tlk? how about a piggy?
471 query = [@{
472 (id)kSecClass : (id)kSecClassInternetPassword,
473 (id)kSecUseDataProtectionKeychain : @YES,
474 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
475 (id)kSecAttrDescription : [key.keyclass stringByAppendingString:@"-piggy"],
476 (id)kSecAttrSynchronizable : (id)kSecAttrSynchronizableAny,
477 (id)kSecAttrAccount : [NSString stringWithFormat:@"%@-piggy", key.uuid],
478 (id)kSecAttrServer : key.zoneID.zoneName,
479 (id)kSecReturnAttributes : @YES,
480 (id)kSecReturnData : @YES,
481 (id)kSecMatchLimit : (id)kSecMatchLimitOne,
482 } mutableCopy];
483
484 result = [self queryKeyMaterialInKeychain:query error:&localError];
485 if(localError == nil) {
486 ckksnotice_global("ckkskey", "loaded a piggy TLK (%@)", key.uuid);
487
488 if(resavePtr) {
489 *resavePtr = true;
490 }
491
492 return result;
493 }
494
495 if(localError && localError.code != errSecItemNotFound) {
496 if(error) {
497 *error = [NSError errorWithDomain:@"securityd"
498 code:localError.code
499 userInfo:@{
500 NSLocalizedDescriptionKey : [NSString
501 stringWithFormat:@"Couldn't load %@ from keychain: %d",
502 key,
503 (int)localError.code],
504 NSUnderlyingErrorKey : localError,
505 }];
506 }
507 return nil;
508 }
509 }
510
511 localError = nil;
512
513 // Try to load a stashed TLK
514 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
515 localError = nil;
516
517 // Try to look for the non-syncable stashed tlk and resurrect it.
518 query = [@{
519 (id)kSecClass : (id)kSecClassInternetPassword,
520 (id)kSecUseDataProtectionKeychain : @YES,
521 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
522 (id)kSecAttrDescription : [key.keyclass stringByAppendingString:@"-nonsync"],
523 (id)kSecAttrServer : key.zoneID.zoneName,
524 (id)kSecAttrAccount : key.uuid,
525 (id)kSecReturnAttributes : @YES,
526 (id)kSecReturnData : @YES,
527 (id)kSecAttrSynchronizable : @NO,
528 } mutableCopy];
529
530 result = [self queryKeyMaterialInKeychain:query error:&localError];
531 if(localError == nil) {
532 ckksnotice_global("ckkskey", "loaded a stashed TLK (%@)", key.uuid);
533
534 if(resavePtr) {
535 *resavePtr = true;
536 }
537
538 return result;
539 }
540
541 if(localError && localError.code != errSecItemNotFound) {
542 if(error) {
543 *error = [NSError errorWithDomain:@"securityd"
544 code:localError.code
545 userInfo:@{
546 NSLocalizedDescriptionKey : [NSString
547 stringWithFormat:@"Couldn't load %@ from keychain: %d",
548 key,
549 (int)localError.code],
550 NSUnderlyingErrorKey : localError,
551 }];
552 }
553 return nil;
554 }
555 }
556
557 // We didn't early-return. Use whatever error the original fetch produced.
558 if(error) {
559 *error = [NSError errorWithDomain:@"securityd"
560 code:originalError ? originalError.code : errSecParam
561 description:[NSString stringWithFormat:@"Couldn't load %@ from keychain: %d",
562 key,
563 (int)originalError.code]
564 underlying:originalError];
565 }
566
567 return result;
568 }
569
570 - (BOOL)loadKeyMaterialFromKeychain:(NSError* __autoreleasing*)error
571 {
572 bool resave = false;
573 NSDictionary* result = [CKKSKeychainBackedKey fetchKeyMaterialItemFromKeychain:self
574 resave:&resave
575 error:error];
576 if(!result) {
577 return NO;
578 }
579
580 NSData* b64keymaterial = result[(id)kSecValueData];
581 NSMutableData* keymaterial =
582 [[NSMutableData alloc] initWithBase64EncodedData:b64keymaterial options:0];
583 if(!keymaterial) {
584 ckkserror_global("ckkskey", "Unable to unbase64 key: %@", self);
585 if(error) {
586 *error = [NSError
587 errorWithDomain:CKKSErrorDomain
588 code:CKKSKeyUnknownFormat
589 description:[NSString stringWithFormat:@"unable to unbase64 key: %@", self]];
590 }
591 return NO;
592 }
593
594 CKKSAESSIVKey* key = [[CKKSAESSIVKey alloc] initWithBytes:(uint8_t*)keymaterial.bytes
595 len:keymaterial.length];
596 memset_s(keymaterial.mutableBytes, keymaterial.length, 0, keymaterial.length);
597 self.aessivkey = key;
598
599 if(resave) {
600 ckksnotice_global("ckkskey", "Resaving %@ as per request", self);
601 NSError* resaveError = nil;
602 [self saveKeyMaterialToKeychain:&resaveError];
603 if(resaveError) {
604 ckksnotice_global("ckkskey", "Resaving %@ failed: %@", self, resaveError);
605 }
606 }
607
608 return !!(self.aessivkey) ? YES : NO;
609 }
610
611 - (BOOL)deleteKeyMaterialFromKeychain:(NSError* __autoreleasing*)error
612 {
613 NSMutableDictionary* query = [@{
614 (id)kSecClass : (id)kSecClassInternetPassword,
615 (id)kSecUseDataProtectionKeychain : @YES,
616 (id)kSecAttrAccessGroup : @"com.apple.security.ckks",
617 (id)kSecAttrDescription : self.keyclass,
618 (id)kSecAttrAccount : self.uuid,
619 (id)kSecAttrServer : self.zoneID.zoneName,
620 (id)kSecReturnData : @YES,
621 } mutableCopy];
622
623 // Synchronizable items are only found if you request synchronizable items. Only TLKs are synchronizable.
624 if([self.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
625 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanTrue;
626 }
627
628 OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
629
630 if(status) {
631 if(error) {
632 *error = [NSError
633 errorWithDomain:@"securityd"
634 code:status
635 userInfo:@{
636 NSLocalizedDescriptionKey : [NSString
637 stringWithFormat:@"Couldn't delete %@ from keychain: %d", self, (int)status]
638 }];
639 }
640 return NO;
641 }
642 return YES;
643 }
644
645 + (instancetype _Nullable)keyFromKeychain:(NSString*)uuid
646 parentKeyUUID:(NSString*)parentKeyUUID
647 keyclass:(CKKSKeyClass*)keyclass
648 zoneID:(CKRecordZoneID*)zoneID
649 error:(NSError* __autoreleasing*)error
650 {
651 CKKSKeychainBackedKey* key = [[CKKSKeychainBackedKey alloc] initWithWrappedAESKey:nil
652 uuid:uuid
653 parentKeyUUID:parentKeyUUID
654 keyclass:keyclass
655 zoneID:zoneID];
656
657 if(![key loadKeyMaterialFromKeychain:error]) {
658 return nil;
659 }
660
661 return key;
662 }
663
664 #pragma mark Utility
665
666 - (NSString*)description
667 {
668 return [NSString stringWithFormat:@"<%@(%@): %@ (%@)>",
669 NSStringFromClass([self class]),
670 self.zoneID.zoneName,
671 self.uuid,
672 self.keyclass];
673 }
674
675 - (NSData*)serializeAsProtobuf:(NSError* __autoreleasing*)error
676 {
677 if(![self ensureKeyLoaded:error]) {
678 return nil;
679 }
680 CKKSSerializedKey* proto = [[CKKSSerializedKey alloc] init];
681
682 proto.uuid = self.uuid;
683 proto.zoneName = self.zoneID.zoneName;
684 proto.keyclass = self.keyclass;
685 proto.key =
686 [[NSData alloc] initWithBytes:self.aessivkey->key length:self.aessivkey->size];
687
688 return proto.data;
689 }
690
691 + (CKKSKeychainBackedKey*)loadFromProtobuf:(NSData*)data
692 error:(NSError* __autoreleasing*)error
693 {
694 CKKSSerializedKey* key = [[CKKSSerializedKey alloc] initWithData:data];
695 if(key && key.uuid && key.zoneName && key.keyclass && key.key) {
696 return [[CKKSKeychainBackedKey alloc]
697 initSelfWrappedWithAESKey:[[CKKSAESSIVKey alloc]
698 initWithBytes:(uint8_t*)key.key.bytes
699 len:key.key.length]
700 uuid:key.uuid
701 keyclass:(CKKSKeyClass*)key.keyclass // TODO sanitize
702 zoneID:[[CKRecordZoneID alloc] initWithZoneName:key.zoneName
703 ownerName:CKCurrentUserDefaultName]];
704 }
705
706 if(error) {
707 *error = [NSError errorWithDomain:CKKSErrorDomain
708 code:CKKSProtobufFailure
709 description:@"Data failed to parse as a CKKSSerializedKey"];
710 }
711 return nil;
712 }
713
714 #pragma mark NSSecureCoding
715
716 + (BOOL)supportsSecureCoding
717 {
718 return YES;
719 }
720
721 - (void)encodeWithCoder:(nonnull NSCoder*)coder
722 {
723 [coder encodeObject:self.uuid forKey:@"uuid"];
724 [coder encodeObject:self.parentKeyUUID forKey:@"parentKeyUUID"];
725 [coder encodeObject:self.keyclass forKey:@"keyclass"];
726 [coder encodeObject:self.zoneID forKey:@"zoneID"];
727 [coder encodeObject:self.wrappedkey forKey:@"wrappedkey"];
728 }
729
730 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
731 {
732 if ((self = [super init])) {
733 _uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"uuid"];
734 _parentKeyUUID =
735 [decoder decodeObjectOfClass:[NSString class] forKey:@"parentKeyUUID"];
736 _keyclass = (CKKSKeyClass*)[decoder decodeObjectOfClass:[NSString class]
737 forKey:@"keyclass"];
738 _zoneID = [decoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"];
739
740 _wrappedkey = [decoder decodeObjectOfClass:[CKKSWrappedAESSIVKey class]
741 forKey:@"wrappedkey"];
742 }
743 return self;
744 }
745
746 @end
747
748 #pragma mark - CKKSKeychainBackedKeySet
749
750 @implementation CKKSKeychainBackedKeySet
751
752 - (instancetype)initWithTLK:(CKKSKeychainBackedKey*)tlk
753 classA:(CKKSKeychainBackedKey*)classA
754 classC:(CKKSKeychainBackedKey*)classC
755 newUpload:(BOOL)newUpload
756 {
757 if((self = [super init])) {
758 _tlk = tlk;
759 _classA = classA;
760 _classC = classC;
761 _newUpload = newUpload;
762 }
763 return self;
764 }
765
766 - (NSString*)description
767 {
768 return [NSString stringWithFormat: @"<CKKSKeychainBackedKeySet: tlk:%@, classA:%@, classC:%@, newUpload:%d>",
769 self.tlk,
770 self.classA,
771 self.classC,
772 self.newUpload];
773 }
774
775 + (BOOL)supportsSecureCoding
776 {
777 return YES;
778 }
779
780 - (void)encodeWithCoder:(nonnull NSCoder*)coder
781 {
782 [coder encodeObject:self.tlk forKey:@"tlk"];
783 [coder encodeObject:self.classA forKey:@"classA"];
784 [coder encodeObject:self.classC forKey:@"classC"];
785 [coder encodeBool:self.newUpload forKey:@"newUpload"];
786 }
787
788 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
789 {
790 if ((self = [super init])) {
791 _tlk = [decoder decodeObjectOfClass:[CKKSKeychainBackedKey class] forKey:@"tlk"];
792 _classA = [decoder decodeObjectOfClass:[CKKSKeychainBackedKey class] forKey:@"classA"];
793 _classC = [decoder decodeObjectOfClass:[CKKSKeychainBackedKey class] forKey:@"classC"];
794 _newUpload = [decoder decodeBoolForKey:@"newUpload"];
795 }
796 return self;
797 }
798
799 @end
800
801 #endif // OCTAGON