4 #import "keychain/ckks/CKKSKeychainBackedKey.h"
6 #include <CloudKit/CloudKit.h>
7 #include <CloudKit/CloudKit_Private.h>
8 #include <Security/SecItem.h>
9 #include <Security/SecItemPriv.h>
11 #import "keychain/categories/NSError+UsefulConstructors.h"
12 #import "keychain/ckks/CKKS.h"
13 #import "keychain/ckks/CKKSItem.h"
15 @implementation CKKSKeychainBackedKey
17 - (instancetype)initSelfWrappedWithAESKey:(CKKSAESSIVKey*)aeskey
19 keyclass:(CKKSKeyClass*)keyclass
20 zoneID:(CKRecordZoneID*)zoneID
22 if((self = [super init])) {
24 _parentKeyUUID = uuid;
30 // Wrap the key with the key. Not particularly useful, but there you go.
32 [self wrapUnder:self error:&error];
34 ckkserror_global("ckkskey", "Couldn't self-wrap key: %@", error);
41 - (instancetype _Nullable)initWrappedBy:(CKKSKeychainBackedKey*)wrappingKey
42 AESKey:(CKKSAESSIVKey*)aessivkey
44 keyclass:(CKKSKeyClass*)keyclass
45 zoneID:(CKRecordZoneID*)zoneID
47 if((self = [super init])) {
49 _parentKeyUUID = uuid;
53 _aessivkey = aessivkey;
56 [self wrapUnder:wrappingKey error:&error];
58 ckkserror_global("ckkskey", "Couldn't wrap key with key: %@", error);
65 - (instancetype)initWithWrappedAESKey:(CKKSWrappedAESSIVKey*)wrappedaeskey
67 parentKeyUUID:(NSString*)parentKeyUUID
68 keyclass:(CKKSKeyClass*)keyclass
69 zoneID:(CKRecordZoneID*)zoneID
71 if((self = [super init])) {
73 _parentKeyUUID = parentKeyUUID;
76 _wrappedkey = wrappedaeskey;
84 - (instancetype)copyWithZone:(NSZone*)zone
86 CKKSKeychainBackedKey* c =
87 [[CKKSKeychainBackedKey allocWithZone:zone] initWithWrappedAESKey:self.wrappedkey
89 parentKeyUUID:self.parentKeyUUID
90 keyclass:self.keyclass
92 c.aessivkey = [self.aessivkey copy];
97 - (BOOL)isEqual:(id)object
99 if(![object isKindOfClass:[CKKSKeychainBackedKey class]]) {
103 CKKSKeychainBackedKey* obj = (CKKSKeychainBackedKey*)object;
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)
114 return [self.uuid isEqual:self.parentKeyUUID];
117 - (bool)wrapUnder:(CKKSKeychainBackedKey*)wrappingKey
118 error:(NSError* __autoreleasing*)error
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);
129 self.wrappedkey = wrappedKey;
130 self.parentKeyUUID = wrappingKey.uuid;
135 - (bool)unwrapSelfWithAESKey:(CKKSAESSIVKey*)unwrappingKey
136 error:(NSError* __autoreleasing*)error
138 _aessivkey = [unwrappingKey unwrapAESKey:self.wrappedkey error:error];
139 return (_aessivkey != nil);
142 + (CKKSKeychainBackedKey* _Nullable)randomKeyWrappedByParent:(CKKSKeychainBackedKey*)parentKey
143 error:(NSError* __autoreleasing*)error
146 [self randomKeyWrappedByParent:parentKey keyclass:parentKey.keyclass error:error];
149 + (CKKSKeychainBackedKey* _Nullable)randomKeyWrappedByParent:(CKKSKeychainBackedKey*)parentKey
150 keyclass:(CKKSKeyClass*)keyclass
151 error:(NSError* __autoreleasing*)error
153 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
154 if(aessivkey == nil) {
158 CKKSKeychainBackedKey* key =
159 [[CKKSKeychainBackedKey alloc] initWrappedBy:parentKey
161 uuid:[[NSUUID UUID] UUIDString]
163 zoneID:parentKey.zoneID];
167 + (instancetype _Nullable)randomKeyWrappedBySelf:(CKRecordZoneID*)zoneID
168 error:(NSError* __autoreleasing*)error
170 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
171 if(aessivkey == nil) {
175 NSString* uuid = [[NSUUID UUID] UUIDString];
177 CKKSKeychainBackedKey* key =
178 [[CKKSKeychainBackedKey alloc] initSelfWrappedWithAESKey:aessivkey
180 keyclass:SecCKKSKeyClassTLK
185 - (CKKSAESSIVKey*)ensureKeyLoaded:(NSError* __autoreleasing*)error
188 return self.aessivkey;
191 // Attempt to load this key from the keychain
192 if([self loadKeyMaterialFromKeychain:error]) {
193 return self.aessivkey;
199 - (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate
200 error:(NSError* __autoreleasing*)error
202 if(![self wrapsSelf]) {
204 *error = [NSError errorWithDomain:CKKSErrorDomain
205 code:CKKSKeyNotSelfWrapped
207 NSLocalizedDescriptionKey : [NSString
208 stringWithFormat:@"%@ is not self-wrapped", self]
214 CKKSAESSIVKey* unwrapped = [candidate unwrapAESKey:self.wrappedkey error:error];
215 if(unwrapped && [unwrapped isEqual:candidate]) {
216 _aessivkey = unwrapped;
223 - (CKKSWrappedAESSIVKey*)wrapAESKey:(CKKSAESSIVKey*)keyToWrap
224 error:(NSError* __autoreleasing*)error
226 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
227 CKKSWrappedAESSIVKey* wrappedkey = [key wrapAESKey:keyToWrap error:error];
231 - (CKKSAESSIVKey*)unwrapAESKey:(CKKSWrappedAESSIVKey*)keyToUnwrap
232 error:(NSError* __autoreleasing*)error
234 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
235 CKKSAESSIVKey* unwrappedkey = [key unwrapAESKey:keyToUnwrap error:error];
239 - (NSData*)encryptData:(NSData*)plaintext
240 authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
241 error:(NSError* __autoreleasing*)error
243 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
244 NSData* data = [key encryptData:plaintext authenticatedData:ad error:error];
248 - (NSData*)decryptData:(NSData*)ciphertext
249 authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
250 error:(NSError* __autoreleasing*)error
252 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
253 NSData* data = [key decryptData:ciphertext authenticatedData:ad error:error];
257 /* Functions to load and save keys from the keychain (where we get to store actual key material!) */
258 - (BOOL)saveKeyMaterialToKeychain:(NSError* __autoreleasing*)error
260 return [self saveKeyMaterialToKeychain:true error:error];
263 - (BOOL)saveKeyMaterialToKeychain:(bool)stashTLK error:(NSError* __autoreleasing*)error
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.
268 if(![self ensureKeyLoaded:error]) {
269 // No key material, nothing to save to keychain.
273 // iOS keychains can't store symmetric keys, so we're reduced to storing this key as a password
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,
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;
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;
301 query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleWhenUnlocked;
304 NSError* localError = nil;
305 [CKKSKeychainBackedKey setKeyMaterialInKeychain:query error:&localError];
307 if(localError && error) {
308 *error = [NSError errorWithDomain:@"securityd"
311 NSLocalizedDescriptionKey :
312 [NSString stringWithFormat:@"Couldn't save %@ to keychain: %d",
314 (int)localError.code],
315 NSUnderlyingErrorKey : localError,
319 // TLKs are synchronizable. Stash them nonsyncably nearby.
320 // Don't report errors here.
321 if(stashTLK && [self.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
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,
334 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanFalse;
336 NSError* stashError = nil;
337 [CKKSKeychainBackedKey setKeyMaterialInKeychain:query error:&localError];
340 ckkserror_global("ckkskey", "Couldn't stash %@ to keychain: %@", self, stashError);
344 return (localError == nil) ? YES : NO;
347 + (NSDictionary*)setKeyMaterialInKeychain:(NSDictionary*)query
348 error:(NSError* __autoreleasing*)error
350 CFTypeRef result = NULL;
351 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, &result);
353 NSError* localerror = nil;
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];
368 NSMutableDictionary* updateQuery = [query mutableCopy];
369 updateQuery[(id)kSecClass] = nil;
371 status = SecItemUpdate((__bridge CFDictionaryRef)findQuery, (__bridge CFDictionaryRef)updateQuery);
374 localerror = [NSError
375 errorWithDomain:@"securityd"
377 description:[NSString stringWithFormat:@"SecItemUpdate: %d", (int)status]];
380 localerror = [NSError
381 errorWithDomain:@"securityd"
383 description:[NSString stringWithFormat:@"SecItemAdd: %d", (int)status]];
387 CFReleaseNull(result);
395 NSDictionary* resultDict = CFBridgingRelease(result);
399 + (NSDictionary*)queryKeyMaterialInKeychain:(NSDictionary*)query
400 error:(NSError* __autoreleasing*)error
402 CFTypeRef result = NULL;
403 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
406 CFReleaseNull(result);
410 errorWithDomain:@"securityd"
413 NSLocalizedDescriptionKey :
414 [NSString stringWithFormat:@"SecItemCopyMatching: %d", (int)status]
420 NSDictionary* resultDict = CFBridgingRelease(result);
424 + (NSDictionary*)fetchKeyMaterialItemFromKeychain:(CKKSKeychainBackedKey*)key
425 resave:(bool*)resavePtr
426 error:(NSError* __autoreleasing*)error
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,
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;
445 NSError* localError = nil;
446 NSDictionary* result = [self queryKeyMaterialInKeychain:query error:&localError];
447 NSError* originalError = localError;
449 // If we found the item or errored in some interesting way, return.
453 if(localError && localError.code != errSecItemNotFound) {
455 *error = [NSError errorWithDomain:@"securityd"
458 NSLocalizedDescriptionKey :
459 [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d",
461 (int)localError.code],
462 NSUnderlyingErrorKey : localError,
469 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
470 //didn't find a regular tlk? how about a piggy?
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,
484 result = [self queryKeyMaterialInKeychain:query error:&localError];
485 if(localError == nil) {
486 ckksnotice_global("ckkskey", "loaded a piggy TLK (%@)", key.uuid);
495 if(localError && localError.code != errSecItemNotFound) {
497 *error = [NSError errorWithDomain:@"securityd"
500 NSLocalizedDescriptionKey : [NSString
501 stringWithFormat:@"Couldn't load %@ from keychain: %d",
503 (int)localError.code],
504 NSUnderlyingErrorKey : localError,
513 // Try to load a stashed TLK
514 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
517 // Try to look for the non-syncable stashed tlk and resurrect it.
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,
530 result = [self queryKeyMaterialInKeychain:query error:&localError];
531 if(localError == nil) {
532 ckksnotice_global("ckkskey", "loaded a stashed TLK (%@)", key.uuid);
541 if(localError && localError.code != errSecItemNotFound) {
543 *error = [NSError errorWithDomain:@"securityd"
546 NSLocalizedDescriptionKey : [NSString
547 stringWithFormat:@"Couldn't load %@ from keychain: %d",
549 (int)localError.code],
550 NSUnderlyingErrorKey : localError,
557 // We didn't early-return. Use whatever error the original fetch produced.
559 *error = [NSError errorWithDomain:@"securityd"
560 code:originalError ? originalError.code : errSecParam
561 description:[NSString stringWithFormat:@"Couldn't load %@ from keychain: %d",
563 (int)originalError.code]
564 underlying:originalError];
570 - (BOOL)loadKeyMaterialFromKeychain:(NSError* __autoreleasing*)error
573 NSDictionary* result = [CKKSKeychainBackedKey fetchKeyMaterialItemFromKeychain:self
580 NSData* b64keymaterial = result[(id)kSecValueData];
581 NSMutableData* keymaterial =
582 [[NSMutableData alloc] initWithBase64EncodedData:b64keymaterial options:0];
584 ckkserror_global("ckkskey", "Unable to unbase64 key: %@", self);
587 errorWithDomain:CKKSErrorDomain
588 code:CKKSKeyUnknownFormat
589 description:[NSString stringWithFormat:@"unable to unbase64 key: %@", self]];
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;
600 ckksnotice_global("ckkskey", "Resaving %@ as per request", self);
601 NSError* resaveError = nil;
602 [self saveKeyMaterialToKeychain:&resaveError];
604 ckksnotice_global("ckkskey", "Resaving %@ failed: %@", self, resaveError);
608 return !!(self.aessivkey) ? YES : NO;
611 - (BOOL)deleteKeyMaterialFromKeychain:(NSError* __autoreleasing*)error
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,
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;
628 OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
633 errorWithDomain:@"securityd"
636 NSLocalizedDescriptionKey : [NSString
637 stringWithFormat:@"Couldn't delete %@ from keychain: %d", self, (int)status]
645 + (instancetype _Nullable)keyFromKeychain:(NSString*)uuid
646 parentKeyUUID:(NSString*)parentKeyUUID
647 keyclass:(CKKSKeyClass*)keyclass
648 zoneID:(CKRecordZoneID*)zoneID
649 error:(NSError* __autoreleasing*)error
651 CKKSKeychainBackedKey* key = [[CKKSKeychainBackedKey alloc] initWithWrappedAESKey:nil
653 parentKeyUUID:parentKeyUUID
657 if(![key loadKeyMaterialFromKeychain:error]) {
666 - (NSString*)description
668 return [NSString stringWithFormat:@"<%@(%@): %@ (%@)>",
669 NSStringFromClass([self class]),
670 self.zoneID.zoneName,
675 - (NSData*)serializeAsProtobuf:(NSError* __autoreleasing*)error
677 if(![self ensureKeyLoaded:error]) {
680 CKKSSerializedKey* proto = [[CKKSSerializedKey alloc] init];
682 proto.uuid = self.uuid;
683 proto.zoneName = self.zoneID.zoneName;
684 proto.keyclass = self.keyclass;
686 [[NSData alloc] initWithBytes:self.aessivkey->key length:self.aessivkey->size];
691 + (CKKSKeychainBackedKey*)loadFromProtobuf:(NSData*)data
692 error:(NSError* __autoreleasing*)error
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
701 keyclass:(CKKSKeyClass*)key.keyclass // TODO sanitize
702 zoneID:[[CKRecordZoneID alloc] initWithZoneName:key.zoneName
703 ownerName:CKCurrentUserDefaultName]];
707 *error = [NSError errorWithDomain:CKKSErrorDomain
708 code:CKKSProtobufFailure
709 description:@"Data failed to parse as a CKKSSerializedKey"];
714 #pragma mark NSSecureCoding
716 + (BOOL)supportsSecureCoding
721 - (void)encodeWithCoder:(nonnull NSCoder*)coder
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"];
730 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
732 if ((self = [super init])) {
733 _uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"uuid"];
735 [decoder decodeObjectOfClass:[NSString class] forKey:@"parentKeyUUID"];
736 _keyclass = (CKKSKeyClass*)[decoder decodeObjectOfClass:[NSString class]
738 _zoneID = [decoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"];
740 _wrappedkey = [decoder decodeObjectOfClass:[CKKSWrappedAESSIVKey class]
741 forKey:@"wrappedkey"];
748 #pragma mark - CKKSKeychainBackedKeySet
750 @implementation CKKSKeychainBackedKeySet
752 - (instancetype)initWithTLK:(CKKSKeychainBackedKey*)tlk
753 classA:(CKKSKeychainBackedKey*)classA
754 classC:(CKKSKeychainBackedKey*)classC
755 newUpload:(BOOL)newUpload
757 if((self = [super init])) {
761 _newUpload = newUpload;
766 - (NSString*)description
768 return [NSString stringWithFormat: @"<CKKSKeychainBackedKeySet: tlk:%@, classA:%@, classC:%@, newUpload:%d>",
775 + (BOOL)supportsSecureCoding
780 - (void)encodeWithCoder:(nonnull NSCoder*)coder
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"];
788 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
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"];