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 secerror("CKKSKeychainBackedKey: 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 secerror("CKKSKeychainBackedKey: 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 self.wrappedkey = [wrappingKey wrapAESKey:self.aessivkey error:&localError];
122 if(self.wrappedkey == nil) {
123 secerror("CKKSKeychainBackedKey: couldn't wrap key: %@", localError);
128 self.parentKeyUUID = wrappingKey.uuid;
130 return (self.wrappedkey != nil);
133 - (bool)unwrapSelfWithAESKey:(CKKSAESSIVKey*)unwrappingKey
134 error:(NSError* __autoreleasing*)error
136 _aessivkey = [unwrappingKey unwrapAESKey:self.wrappedkey error:error];
137 return (_aessivkey != nil);
140 + (CKKSKeychainBackedKey* _Nullable)randomKeyWrappedByParent:(CKKSKeychainBackedKey*)parentKey
141 error:(NSError* __autoreleasing*)error
144 [self randomKeyWrappedByParent:parentKey keyclass:parentKey.keyclass error:error];
147 + (CKKSKeychainBackedKey* _Nullable)randomKeyWrappedByParent:(CKKSKeychainBackedKey*)parentKey
148 keyclass:(CKKSKeyClass*)keyclass
149 error:(NSError* __autoreleasing*)error
151 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
152 if(aessivkey == nil) {
156 CKKSKeychainBackedKey* key =
157 [[CKKSKeychainBackedKey alloc] initWrappedBy:parentKey
159 uuid:[[NSUUID UUID] UUIDString]
161 zoneID:parentKey.zoneID];
165 + (instancetype _Nullable)randomKeyWrappedBySelf:(CKRecordZoneID*)zoneID
166 error:(NSError* __autoreleasing*)error
168 CKKSAESSIVKey* aessivkey = [CKKSAESSIVKey randomKey:error];
169 if(aessivkey == nil) {
173 NSString* uuid = [[NSUUID UUID] UUIDString];
175 CKKSKeychainBackedKey* key =
176 [[CKKSKeychainBackedKey alloc] initSelfWrappedWithAESKey:aessivkey
178 keyclass:SecCKKSKeyClassTLK
183 - (CKKSAESSIVKey*)ensureKeyLoaded:(NSError* __autoreleasing*)error
186 return self.aessivkey;
189 // Attempt to load this key from the keychain
190 if([self loadKeyMaterialFromKeychain:error]) {
191 return self.aessivkey;
197 - (bool)trySelfWrappedKeyCandidate:(CKKSAESSIVKey*)candidate
198 error:(NSError* __autoreleasing*)error
200 if(![self wrapsSelf]) {
202 *error = [NSError errorWithDomain:CKKSErrorDomain
203 code:CKKSKeyNotSelfWrapped
205 NSLocalizedDescriptionKey : [NSString
206 stringWithFormat:@"%@ is not self-wrapped", self]
212 CKKSAESSIVKey* unwrapped = [candidate unwrapAESKey:self.wrappedkey error:error];
213 if(unwrapped && [unwrapped isEqual:candidate]) {
214 _aessivkey = unwrapped;
221 - (CKKSWrappedAESSIVKey*)wrapAESKey:(CKKSAESSIVKey*)keyToWrap
222 error:(NSError* __autoreleasing*)error
224 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
225 CKKSWrappedAESSIVKey* wrappedkey = [key wrapAESKey:keyToWrap error:error];
229 - (CKKSAESSIVKey*)unwrapAESKey:(CKKSWrappedAESSIVKey*)keyToUnwrap
230 error:(NSError* __autoreleasing*)error
232 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
233 CKKSAESSIVKey* unwrappedkey = [key unwrapAESKey:keyToUnwrap error:error];
237 - (NSData*)encryptData:(NSData*)plaintext
238 authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
239 error:(NSError* __autoreleasing*)error
241 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
242 NSData* data = [key encryptData:plaintext authenticatedData:ad error:error];
246 - (NSData*)decryptData:(NSData*)ciphertext
247 authenticatedData:(NSDictionary<NSString*, NSData*>*)ad
248 error:(NSError* __autoreleasing*)error
250 CKKSAESSIVKey* key = [self ensureKeyLoaded:error];
251 NSData* data = [key decryptData:ciphertext authenticatedData:ad error:error];
255 /* Functions to load and save keys from the keychain (where we get to store actual key material!) */
256 - (BOOL)saveKeyMaterialToKeychain:(NSError* __autoreleasing*)error
258 return [self saveKeyMaterialToKeychain:true error:error];
261 - (BOOL)saveKeyMaterialToKeychain:(bool)stashTLK error:(NSError* __autoreleasing*)error
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.
266 if(![self ensureKeyLoaded:error]) {
267 // No key material, nothing to save to keychain.
271 // iOS keychains can't store symmetric keys, so we're reduced to storing this key as a password
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,
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;
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;
299 query[(id)kSecAttrAccessible] = (id)kSecAttrAccessibleWhenUnlocked;
302 NSError* localError = nil;
303 [CKKSKeychainBackedKey setKeyMaterialInKeychain:query error:&localError];
305 if(localError && error) {
306 *error = [NSError errorWithDomain:@"securityd"
309 NSLocalizedDescriptionKey :
310 [NSString stringWithFormat:@"Couldn't save %@ to keychain: %d",
312 (int)localError.code],
313 NSUnderlyingErrorKey : localError,
317 // TLKs are synchronizable. Stash them nonsyncably nearby.
318 // Don't report errors here.
319 if(stashTLK && [self.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
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,
332 query[(id)kSecAttrSynchronizable] = (id)kCFBooleanFalse;
334 NSError* stashError = nil;
335 [CKKSKeychainBackedKey setKeyMaterialInKeychain:query error:&localError];
338 secerror("CKKSKeychainBackedKey: Couldn't stash %@ to keychain: %@", self, stashError);
342 return (localError == nil) ? YES : NO;
345 + (NSDictionary*)setKeyMaterialInKeychain:(NSDictionary*)query
346 error:(NSError* __autoreleasing*)error
348 CFTypeRef result = NULL;
349 OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, &result);
351 NSError* localerror = nil;
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];
366 NSMutableDictionary* updateQuery = [query mutableCopy];
367 updateQuery[(id)kSecClass] = nil;
369 status = SecItemUpdate((__bridge CFDictionaryRef)findQuery, (__bridge CFDictionaryRef)updateQuery);
372 localerror = [NSError
373 errorWithDomain:@"securityd"
375 description:[NSString stringWithFormat:@"SecItemUpdate: %d", (int)status]];
378 localerror = [NSError
379 errorWithDomain:@"securityd"
381 description:[NSString stringWithFormat:@"SecItemAdd: %d", (int)status]];
385 CFReleaseNull(result);
393 NSDictionary* resultDict = CFBridgingRelease(result);
397 + (NSDictionary*)queryKeyMaterialInKeychain:(NSDictionary*)query
398 error:(NSError* __autoreleasing*)error
400 CFTypeRef result = NULL;
401 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
404 CFReleaseNull(result);
408 errorWithDomain:@"securityd"
411 NSLocalizedDescriptionKey :
412 [NSString stringWithFormat:@"SecItemCopyMatching: %d", (int)status]
418 NSDictionary* resultDict = CFBridgingRelease(result);
422 + (NSDictionary*)fetchKeyMaterialItemFromKeychain:(CKKSKeychainBackedKey*)key
423 resave:(bool*)resavePtr
424 error:(NSError* __autoreleasing*)error
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,
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;
443 NSError* localError = nil;
444 NSDictionary* result = [self queryKeyMaterialInKeychain:query error:&localError];
445 NSError* originalError = localError;
447 // If we found the item or errored in some interesting way, return.
451 if(localError && localError.code != errSecItemNotFound) {
453 *error = [NSError errorWithDomain:@"securityd"
456 NSLocalizedDescriptionKey :
457 [NSString stringWithFormat:@"Couldn't load %@ from keychain: %d",
459 (int)localError.code],
460 NSUnderlyingErrorKey : localError,
467 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
468 //didn't find a regular tlk? how about a piggy?
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,
482 result = [self queryKeyMaterialInKeychain:query error:&localError];
483 if(localError == nil) {
484 secnotice("CKKSKeychainBackedKey", "loaded a piggy TLK (%@)", key.uuid);
493 if(localError && localError.code != errSecItemNotFound) {
495 *error = [NSError errorWithDomain:@"securityd"
498 NSLocalizedDescriptionKey : [NSString
499 stringWithFormat:@"Couldn't load %@ from keychain: %d",
501 (int)localError.code],
502 NSUnderlyingErrorKey : localError,
511 // Try to load a stashed TLK
512 if([key.keyclass isEqualToString:SecCKKSKeyClassTLK]) {
515 // Try to look for the non-syncable stashed tlk and resurrect it.
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,
528 result = [self queryKeyMaterialInKeychain:query error:&localError];
529 if(localError == nil) {
530 secnotice("CKKSKeychainBackedKey", "loaded a stashed TLK (%@)", key.uuid);
539 if(localError && localError.code != errSecItemNotFound) {
541 *error = [NSError errorWithDomain:@"securityd"
544 NSLocalizedDescriptionKey : [NSString
545 stringWithFormat:@"Couldn't load %@ from keychain: %d",
547 (int)localError.code],
548 NSUnderlyingErrorKey : localError,
555 // We didn't early-return. Use whatever error the original fetch produced.
557 *error = [NSError errorWithDomain:@"securityd"
558 code:originalError ? originalError.code : errSecParam
559 description:[NSString stringWithFormat:@"Couldn't load %@ from keychain: %d",
561 (int)originalError.code]
562 underlying:originalError];
568 - (BOOL)loadKeyMaterialFromKeychain:(NSError* __autoreleasing*)error
571 NSDictionary* result = [CKKSKeychainBackedKey fetchKeyMaterialItemFromKeychain:self
578 NSData* b64keymaterial = result[(id)kSecValueData];
579 NSMutableData* keymaterial =
580 [[NSMutableData alloc] initWithBase64EncodedData:b64keymaterial options:0];
582 secnotice("CKKSKeychainBackedKey", "Unable to unbase64 key: %@", self);
585 errorWithDomain:CKKSErrorDomain
586 code:CKKSKeyUnknownFormat
587 description:[NSString stringWithFormat:@"unable to unbase64 key: %@", self]];
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;
598 secnotice("CKKSKeychainBackedKey", "Resaving %@ as per request", self);
599 NSError* resaveError = nil;
600 [self saveKeyMaterialToKeychain:&resaveError];
602 secnotice("CKKSKeychainBackedKey", "Resaving %@ failed: %@", self, resaveError);
606 return !!(self.aessivkey) ? YES : NO;
609 - (BOOL)deleteKeyMaterialFromKeychain:(NSError* __autoreleasing*)error
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,
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;
626 OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
631 errorWithDomain:@"securityd"
634 NSLocalizedDescriptionKey : [NSString
635 stringWithFormat:@"Couldn't delete %@ from keychain: %d", self, (int)status]
643 + (instancetype _Nullable)keyFromKeychain:(NSString*)uuid
644 parentKeyUUID:(NSString*)parentKeyUUID
645 keyclass:(CKKSKeyClass*)keyclass
646 zoneID:(CKRecordZoneID*)zoneID
647 error:(NSError* __autoreleasing*)error
649 CKKSKeychainBackedKey* key = [[CKKSKeychainBackedKey alloc] initWithWrappedAESKey:nil
651 parentKeyUUID:parentKeyUUID
655 if(![key loadKeyMaterialFromKeychain:error]) {
664 - (NSString*)description
666 return [NSString stringWithFormat:@"<%@(%@): %@ (%@)>",
667 NSStringFromClass([self class]),
668 self.zoneID.zoneName,
673 - (NSData*)serializeAsProtobuf:(NSError* __autoreleasing*)error
675 if(![self ensureKeyLoaded:error]) {
678 CKKSSerializedKey* proto = [[CKKSSerializedKey alloc] init];
680 proto.uuid = self.uuid;
681 proto.zoneName = self.zoneID.zoneName;
682 proto.keyclass = self.keyclass;
684 [[NSData alloc] initWithBytes:self.aessivkey->key length:self.aessivkey->size];
689 + (CKKSKeychainBackedKey*)loadFromProtobuf:(NSData*)data
690 error:(NSError* __autoreleasing*)error
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
699 keyclass:(CKKSKeyClass*)key.keyclass // TODO sanitize
700 zoneID:[[CKRecordZoneID alloc] initWithZoneName:key.zoneName
701 ownerName:CKCurrentUserDefaultName]];
705 *error = [NSError errorWithDomain:CKKSErrorDomain
706 code:CKKSProtobufFailure
707 description:@"Data failed to parse as a CKKSSerializedKey"];
712 #pragma mark NSSecureCoding
714 + (BOOL)supportsSecureCoding
719 - (void)encodeWithCoder:(nonnull NSCoder*)coder
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"];
728 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
732 _uuid = [decoder decodeObjectOfClass:[NSString class] forKey:@"uuid"];
734 [decoder decodeObjectOfClass:[NSString class] forKey:@"parentKeyUUID"];
735 _keyclass = (CKKSKeyClass*)[decoder decodeObjectOfClass:[NSString class]
737 _zoneID = [decoder decodeObjectOfClass:[CKRecordZoneID class] forKey:@"zoneID"];
739 _wrappedkey = [decoder decodeObjectOfClass:[CKKSWrappedAESSIVKey class]
740 forKey:@"wrappedkey"];
747 #pragma mark - CKKSKeychainBackedKeySet
749 @implementation CKKSKeychainBackedKeySet
751 - (instancetype)initWithTLK:(CKKSKeychainBackedKey*)tlk
752 classA:(CKKSKeychainBackedKey*)classA
753 classC:(CKKSKeychainBackedKey*)classC
754 newUpload:(BOOL)newUpload
756 if((self = [super init])) {
760 _newUpload = newUpload;
765 - (NSString*)description
767 return [NSString stringWithFormat: @"<CKKSKeychainBackedKeySet: tlk:%@, classA:%@, classC:%@, newUpload:%d>",
774 + (BOOL)supportsSecureCoding
779 - (void)encodeWithCoder:(nonnull NSCoder*)coder
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"];
787 - (nullable instancetype)initWithCoder:(nonnull NSCoder*)decoder
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"];