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