]> git.saurik.com Git - apple/security.git/blob - keychain/CoreDataKeychain/SecCDKeychain.m
Security-59306.11.20.tar.gz
[apple/security.git] / keychain / CoreDataKeychain / SecCDKeychain.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 #import <TargetConditionals.h>
25
26 #if !TARGET_OS_BRIDGE
27
28 #import "SecCDKeychain.h"
29 #import "SecCDKeychainManagedItem+CoreDataClass.h"
30 #import "SecCDKeychainManagedLookupEntry+CoreDataClass.h"
31 #import "SecCDKeychainManagedItemType+CoreDataClass.h"
32 #import "SecCDKeychainManagedAccessControlEntity+CoreDataClass.h"
33 #import "SecFileLocations.h"
34 #import "SecItemServer.h"
35 #import "SecItem.h"
36 #import "SecItemPriv.h"
37 #import <Security/SecBase.h>
38 #import "SFKeychainServer.h"
39 #import "CloudKitCategories.h"
40 #import "securityd_client.h"
41 #import "SecKeybagSupport.h"
42 #import "SecKeybagSupport.h"
43 #import "keychain/categories/NSError+UsefulConstructors.h"
44 #import <SecurityFoundation/SFKeychain.h>
45 #import <SecurityFoundation/SFDigestOperation.h>
46 #import <SecurityFoundation/SFKey.h>
47 #import <SecurityFoundation/SFEncryptionOperation.h>
48 #import <CoreData/NSPersistentStoreCoordinator_Private.h>
49 #if USE_KEYSTORE
50 #if __has_include(<libaks_ref_key.h>)
51 #import <libaks_ref_key.h>
52 #endif
53 #endif
54 #import <Foundation/NSData_Private.h>
55 #import <notify.h>
56
57 static NSString* const SecCDKeychainErrorDomain = @"com.apple.security.cdkeychain";
58
59 static NSString* const SecCDKeychainEntityLookupEntry = @"LookupEntry";
60 static NSString* const SecCDKeychainEntityItem = @"Item";
61 static NSString* const SecCDKeychainEntityItemType = @"ItemType";
62 static NSString* const SecCDKeychainEntityTypeAccessControlEntity = @"AccessControlEntity";
63
64 static NSString* const SecCDKeychainItemMetadataSHA256 = @"SecCDKeychainItemMetadataSHA256";
65
66 static const NSInteger SecCDKeychainErrorDeserializing = 1;
67 static const NSInteger SecCDKeychainErrorInternal = 2;
68 //static const NSInteger SecCDKeychainErrorMetadataDoesNotMatch = 3;
69
70 SecCDKeychainLookupValueType* const SecCDKeychainLookupValueTypeString = (SecCDKeychainLookupValueType*)@"SecCDKeychainLookupValueTypeString";
71 SecCDKeychainLookupValueType* const SecCDKeychainLookupValueTypeData = (SecCDKeychainLookupValueType*)@"SecCDKeychainLookupValueTypeData";
72 SecCDKeychainLookupValueType* const SecCDKeychainLookupValueTypeNumber = (SecCDKeychainLookupValueType*)@"SecCDKeychainLookupValueTypeNumber";
73 SecCDKeychainLookupValueType* const SecCDKeychainLookupValueTypeDate = (SecCDKeychainLookupValueType*)@"SecCDKeychainLookupValueTypeDate";
74
75 @interface SecCDKeychainItem ()
76
77 - (instancetype)initWithManagedItem:(SecCDKeychainManagedItem*)managedItem keychain:(SecCDKeychain*)keychain error:(NSError**)error;
78
79 - (NSString*)primaryKeyStringRepresentationWithError:(NSError**)error;
80 - (NSData*)encryptedSecretDataWithAttributeData:(NSData*)attributeData keybag:(keybag_handle_t)keybag error:(NSError**)error;
81
82 @end
83
84 @interface SecCDKeychainItemMetadata ()
85
86 @property (readonly, copy) NSSet<SecCDKeychainLookupTuple*>* lookupAttributesSet;
87 @property (readonly, copy) NSData* managedDataBlob;
88
89 - (instancetype)initWithItemType:(SecCDKeychainItemType*)itemType persistentID:(NSUUID*)persistentID attributes:(NSDictionary*)attributes lookupAttributes:(NSArray<SecCDKeychainLookupTuple*>*)lookupAttributes owner:(SecCDKeychainAccessControlEntity*)owner keyclass:(keyclass_t)keyclass;
90 - (instancetype)initWithManagedItem:(SecCDKeychainManagedItem*)managedItem keychain:(SecCDKeychain*)keychain error:(NSError**)error;
91
92 @end
93
94 @interface SecCDKeychainLookupTuple ()
95
96 + (instancetype)lookupTupleWithManagedLookupEntry:(SecCDKeychainManagedLookupEntry*)lookupEntry;
97
98 @end
99
100 @interface SecCDKeychainItemType ()
101
102 - (SecCDKeychainManagedItemType*)managedItemTypeWithContext:(NSManagedObjectContext*)managedObjectContext error:(NSError**)error;
103
104 @end
105
106 @interface SecCDKeychainAccessControlEntity ()
107
108 - (instancetype)initWithManagedEntity:(SecCDKeychainManagedAccessControlEntity*)managedAccessControlEntity;
109
110 @end
111
112 @interface SecCDKeychainItemWrappedSecretData : NSObject <NSSecureCoding>
113
114 @property (readonly) SFAuthenticatedCiphertext* ciphertext;
115 @property (readonly) NSData* wrappedKeyData;
116 @property (readonly) NSData* refKeyBlob;
117
118 - (instancetype)init NS_UNAVAILABLE;
119 - (instancetype)initWithCiphertext:(SFAuthenticatedCiphertext*)ciphertext wrappedKeyData:(NSData*)wrappedKeyData refKeyBlob:(NSData*)refKeyBlob;
120
121 @end
122
123 @implementation SecCDKeychainItemWrappedSecretData {
124 SFAuthenticatedCiphertext* _ciphertext;
125 NSData* _wrappedKeyData;
126 NSData* _refKeyBlob;
127 }
128
129 @synthesize ciphertext = _ciphertext;
130 @synthesize wrappedKeyData = _wrappedKeyData;
131 @synthesize refKeyBlob = _refKeyBlob;
132
133 + (BOOL)supportsSecureCoding
134 {
135 return YES;
136 }
137
138 - (instancetype)initWithCiphertext:(SFAuthenticatedCiphertext*)ciphertext wrappedKeyData:(NSData*)wrappedKeyData refKeyBlob:(NSData*)refKeyBlob
139 {
140 if (self = [super init]) {
141 _ciphertext = ciphertext;
142 _wrappedKeyData = wrappedKeyData.copy;
143 _refKeyBlob = refKeyBlob.copy;
144 }
145
146 return self;
147 }
148
149 - (instancetype)initWithCoder:(NSCoder*)coder
150 {
151 if (self = [super init]) {
152 _ciphertext = [coder decodeObjectOfClass:[SFAuthenticatedCiphertext class] forKey:@"SecCDKeychainItemCiphertext"];
153 _wrappedKeyData = [coder decodeObjectOfClass:[NSData class] forKey:@"SecCDKeychainItemWrappedKey"];
154 _refKeyBlob = [coder decodeObjectOfClass:[NSData class] forKey:@"SecCDKeychainItemRefKeyBlob"];
155
156 if (!_ciphertext || !_wrappedKeyData || !_refKeyBlob) {
157 self = nil;
158 secerror("SecCDKeychain: failed to deserialize wrapped secret data");
159 [coder failWithError:[NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorDeserializing userInfo:@{NSLocalizedDescriptionKey : @"failed to deserialize wrapped secret data"}]];
160 }
161 }
162
163 return self;
164 }
165
166 - (void)encodeWithCoder:(NSCoder*)coder
167 {
168 [coder encodeObject:_ciphertext forKey:@"SecCDKeychainItemCiphertext"];
169 [coder encodeObject:_wrappedKeyData forKey:@"SecCDKeychainItemWrappedKey"];
170 [coder encodeObject:_refKeyBlob forKey:@"SecCDKeychainItemRefKeyBlob"];
171 }
172
173 @end
174
175 #if USE_KEYSTORE
176
177 @implementation SecAKSRefKey {
178 aks_ref_key_t _refKey;
179 }
180
181 - (instancetype)initWithKeybag:(keyclass_t)keybag keyclass:(keyclass_t)keyclass
182 {
183 if (self = [super init]) {
184 if (aks_ref_key_create(keybag, keyclass, key_type_sym, NULL, 0, &_refKey) != kAKSReturnSuccess) {
185 self = nil;
186 }
187 }
188
189 return self;
190 }
191
192 - (instancetype)initWithBlob:(NSData*)blob keybag:(keybag_handle_t)keybag
193 {
194 if (self = [super init]) {
195 if (aks_ref_key_create_with_blob(keybag, blob.bytes, blob.length, &_refKey) != kAKSReturnSuccess) {
196 self = nil;
197 }
198 }
199
200 return self;
201 }
202
203 - (void)dealloc
204 {
205 aks_ref_key_free(&_refKey);
206 }
207
208 - (NSData*)wrappedDataForKey:(SFAESKey*)key
209 {
210 void* wrappedKeyBytes = NULL;
211 size_t wrappedKeyLength = 0;
212 if (aks_ref_key_wrap(_refKey, NULL, 0, key.keyData.bytes, key.keyData.length, &wrappedKeyBytes, &wrappedKeyLength) == kAKSReturnSuccess) {
213 return [NSData dataWithBytesNoCopy:wrappedKeyBytes length:wrappedKeyLength];
214 }
215
216 return nil;
217 }
218
219 - (SFAESKey*)keyWithWrappedData:(NSData*)wrappedKeyData
220 {
221 void* unwrappedKeyBytes = NULL;
222 size_t unwrappedKeyLength = 0;
223 int aksResult = aks_ref_key_unwrap(_refKey, NULL, 0, wrappedKeyData.bytes, wrappedKeyData.length, &unwrappedKeyBytes, &unwrappedKeyLength);
224 if (aksResult != kAKSReturnSuccess || !unwrappedKeyBytes) {
225 return nil;
226 }
227
228 NSData* keyData = [NSData dataWithBytesNoCopy:unwrappedKeyBytes length:unwrappedKeyLength];
229 NSError* error = nil;
230 SFAESKey* key = [[SFAESKey alloc] initWithData:keyData specifier:[[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256] error:&error];
231 if (!key) {
232 secerror("SecCDKeychain: error creating AES key from unwrapped item key data with error: %@", error);
233 }
234
235 return key;
236 }
237
238 - (NSData*)refKeyBlob
239 {
240 size_t refKeyBlobLength = 0;
241 const uint8_t* refKeyBlobBytes = aks_ref_key_get_blob(_refKey, &refKeyBlobLength);
242 return [NSData dataWithBytes:refKeyBlobBytes length:refKeyBlobLength];
243 }
244
245 @end
246
247 #endif // USE_KEYSTORE
248
249 @implementation SecCDKeychain {
250 NSURL* _persistentStoreBaseURL;
251 NSPersistentStoreCoordinator* _persistentStoreCoordinator;
252 NSManagedObjectContext* _managedObjectContext;
253 NSMutableDictionary* _managedItemTypeDict;
254 NSMutableDictionary* _itemTypeDict;
255 bool _encryptDatabase;
256 dispatch_queue_t _queue;
257 NSArray* _classAPersistentStores;
258 }
259
260 + (SecCDKeychainLookupValueType*)lookupValueTypeForObject:(id)object
261 {
262 if ([object isKindOfClass:[NSString class]]) {
263 return SecCDKeychainLookupValueTypeString;
264 }
265 else if ([object isKindOfClass:[NSData class]]) {
266 return SecCDKeychainLookupValueTypeData;
267 }
268 else if ([object isKindOfClass:[NSDate class]]) {
269 return SecCDKeychainLookupValueTypeDate;
270 }
271 else if ([object isKindOfClass:[NSNumber class]]) {
272 return SecCDKeychainLookupValueTypeNumber;
273 }
274 else {
275 return nil;
276 }
277 }
278
279 - (instancetype)initWithStorageURL:(NSURL*)persistentStoreURL modelURL:(NSURL*)managedObjectURL encryptDatabase:(bool)encryptDatabase
280 {
281 if (!persistentStoreURL) {
282 secerror("SecCDKeychain: no persistent store URL, so we can't create or open a database");
283 return nil;
284 }
285 if (!managedObjectURL) {
286 secerror("SecCDKeychain: no managed object model URL, so we can't create or open a database");
287 return nil;
288 }
289
290 if (self = [super init]) {
291 _queue = dispatch_queue_create("SecCDKeychain", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
292
293 _persistentStoreBaseURL = persistentStoreURL.copy;
294 _encryptDatabase = encryptDatabase;
295
296 NSManagedObjectModel* managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:managedObjectURL];
297 _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObjectModel];
298 }
299
300 return self;
301 }
302
303 - (NSData*)_onQueueGetDatabaseKeyDataWithError:(NSError**)error
304 {
305 NSData* keyData = nil;
306 NSDictionary* databaseKeyQuery = @{ (id)kSecClass : (id)kSecClassGenericPassword,
307 (id)kSecAttrAccessGroup : @"com.apple.security.securityd",
308 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
309 (id)kSecUseDataProtectionKeychain : @(YES),
310 (id)kSecAttrService : @"com.apple.security.keychain.ak",
311 (id)kSecReturnData : @(YES) };
312
313 CFTypeRef result = NULL;
314 OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)databaseKeyQuery, &result);
315
316 if (status == errSecItemNotFound) {
317 NSMutableDictionary* addKeyQuery = databaseKeyQuery.mutableCopy;
318 [addKeyQuery removeObjectForKey:(id)kSecReturnData];
319
320 uint8_t* keyBytes = malloc(16);
321 if (SecRandomCopyBytes(NULL, 16, keyBytes) != 0) {
322 secerror("SecCDKeychain: failed to create random key for CD database encryption - this means we won't be able to create a database");
323 if (error) {
324 *error = [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : @"failed to create random key for CD database encryption"}];
325 }
326 return nil;
327 }
328
329 keyData = [NSData _newZeroingDataWithBytesNoCopy:keyBytes length:16 deallocator:nil];
330 addKeyQuery[(id)kSecValueData] = keyData;
331 status = SecItemAdd((__bridge CFDictionaryRef)addKeyQuery, NULL);
332 if (status == errSecSuccess) {
333 return keyData;
334 }
335 else {
336 secerror("SecCDKeychain: failed to save encryption key to keychain, so bailing on database creation; status: %d", (int)status);
337 CFErrorRef cfError = NULL;
338 SecError(status, &cfError, CFSTR("failed to save encryption key to keychain, so bailing on database creation"));
339 if (error) {
340 *error = CFBridgingRelease(cfError);
341 cfError = NULL;
342 }
343 else {
344 CFReleaseNull(cfError);
345 }
346 return nil;
347 }
348 }
349 else if (status == errSecInteractionNotAllowed) {
350 //// <rdar://problem/38972671> add SFKeychainErrorDeviceLocked
351
352 secerror("SecCDKeychain: can't create a class A store right now because the keychain is locked");
353 CFErrorRef cfError = NULL;
354 SecError(status, &cfError, CFSTR("can't create a class A store right now because the keychain is locked"));
355 if (error) {
356 *error = CFBridgingRelease(cfError);
357 cfError = NULL;
358 }
359 else {
360 CFReleaseNull(cfError);
361 }
362 return nil;
363 }
364 else if (status == errSecSuccess) {
365 if ([(__bridge id)result isKindOfClass:[NSData class]]) {
366 return (__bridge_transfer NSData*)result;
367 }
368 else {
369 if (error) {
370 *error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"result of keychain query for database key is wrong kind of class: %@", [(__bridge id)result class]]}];
371 }
372
373 CFReleaseNull(result);
374 return nil;
375 }
376 }
377 else {
378 secerror("failed to save or retrieve key for CD database encryption - this means we won't be able to create a database; status: %d", (int)status);
379 if (error) {
380 *error = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:@{NSLocalizedDescriptionKey : @"failed to save or retrieve key for CD database encryption"}];
381 }
382
383 CFReleaseNull(result);
384 return nil;
385 }
386 }
387
388 - (NSManagedObjectContext*)_onQueueGetManagedObjectContextWithError:(NSError* __autoreleasing *)error
389 {
390 dispatch_assert_queue(_queue);
391
392 if (!_managedObjectContext) {
393 NSURL* akStoreURL = [_persistentStoreBaseURL URLByAppendingPathExtension:@"ak"];
394
395 NSData* keyData = [self _onQueueGetDatabaseKeyDataWithError:error];
396 if (!keyData) {
397 return nil;
398 }
399
400 if (_encryptDatabase) {
401 NSPersistentStore* classAStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:akStoreURL options:@{NSSQLiteSEEKeychainItemOption : keyData} error:error];
402 _classAPersistentStores = @[classAStore];
403 }
404 else {
405 NSPersistentStore* classAStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:akStoreURL options:nil error:error];
406 _classAPersistentStores = @[classAStore];
407 }
408
409 NSManagedObjectContext* managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
410 managedObjectContext.persistentStoreCoordinator = _persistentStoreCoordinator;
411
412 // the first time around, setup our items types; grab old ones from the database and register any new ones
413 // we can skip for subsequent loads of the store in the same run of securityd
414 if (!_managedItemTypeDict || !_itemTypeDict) {
415 _managedItemTypeDict = [NSMutableDictionary dictionary];
416 _itemTypeDict = [NSMutableDictionary dictionary];
417 [managedObjectContext performBlockAndWait:^{
418 NSFetchRequest* managedItemTypeFetchRequest = [SecCDKeychainManagedItemType fetchRequest];
419 NSArray<SecCDKeychainManagedItemType*>* managedItemTypes = [managedObjectContext executeFetchRequest:managedItemTypeFetchRequest error:error];
420 if (managedItemTypes.count == 0) {
421 // TODO do some error handling here
422 }
423 else {
424 for (SecCDKeychainManagedItemType* managedItemType in managedItemTypes) {
425 NSMutableDictionary* itemTypeVersionDict = self->_managedItemTypeDict[managedItemType.name];
426 if (!itemTypeVersionDict) {
427 itemTypeVersionDict = [NSMutableDictionary dictionary];
428 self->_managedItemTypeDict[managedItemType.name] = itemTypeVersionDict;
429 }
430 itemTypeVersionDict[@(managedItemType.version)] = managedItemType;
431 }
432 }
433
434 [self registerItemType:[SecCDKeychainItemTypeCredential itemType] withManagedObjectContext:managedObjectContext];
435 }];
436 }
437
438 _managedObjectContext = managedObjectContext;
439
440 int token = 0;
441 __weak __typeof(self) weakSelf = self;
442 notify_register_dispatch(kUserKeybagStateChangeNotification, &token, _queue, ^(int t) {
443 bool locked = true;
444 CFErrorRef blockError = NULL;
445 if (!SecAKSGetIsLocked(&locked, &blockError)) {
446 secerror("SecDbKeychainMetadataKeyStore: error getting lock state: %@", blockError);
447 CFReleaseNull(blockError);
448 }
449
450 if (locked) {
451 [weakSelf _onQueueDropClassAPersistentStore];
452 }
453 });
454 }
455
456 return _managedObjectContext;
457 }
458
459 - (void)_onQueueDropClassAPersistentStore
460 {
461 for (NSPersistentStore* store in _classAPersistentStores) {
462 NSError* error = nil;
463 if (![_persistentStoreCoordinator removePersistentStore:store error:&error]) {
464 secerror("SecCDKeychain: failed to remove persistent store with error: %@", error);
465 }
466 }
467
468 _classAPersistentStores = nil;
469 _managedObjectContext = nil;
470 _persistentStoreCoordinator = nil;
471 }
472
473 - (dispatch_queue_t)_queue
474 {
475 return _queue;
476 }
477
478 - (void)performOnManagedObjectQueue:(void (^)(NSManagedObjectContext* context, NSError* error))block
479 {
480 __weak __typeof(self) weakSelf = self;
481 dispatch_async(_queue, ^{
482 NSError* error = nil;
483 NSManagedObjectContext* context = [weakSelf _onQueueGetManagedObjectContextWithError:&error];
484 if (context) {
485 [context performBlock:^{
486 block(context, nil);
487 }];
488 }
489 else {
490 if (!error) {
491 error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal description:@"unknown error retrieving managed object context"];
492 }
493 block(nil, error);
494 }
495 });
496 }
497
498 - (void)performOnManagedObjectQueueAndWait:(void (^)(NSManagedObjectContext* context, NSError* error))block
499 {
500 dispatch_sync(_queue, ^{
501 NSError* error = nil;
502 NSManagedObjectContext* context = [self _onQueueGetManagedObjectContextWithError:&error];
503 if (context) {
504 [context performBlockAndWait:^{
505 block(context, nil);
506 }];
507 }
508 else {
509 block(nil, error);
510 }
511 });
512 }
513
514 - (NSString*)primaryKeyNameForItemTypeName:(NSString*)itemTypeName
515 {
516 return [NSString stringWithFormat:@"SecCDKeychain-PrimaryKey-%@", itemTypeName];
517 }
518
519 - (bool)validateItemOwner:(SecCDKeychainAccessControlEntity*)owner withConnection:(SFKeychainServerConnection*)connection withError:(NSError**)error
520 {
521 // in the future we may support more owner types than just an access group, but for now, access group or bust
522
523 NSError* localError = nil;
524 NSString* accessGroup = owner.entityType == SecCDKeychainAccessControlEntityTypeAccessGroup ? owner.stringRepresentation : nil;
525 if (accessGroup) {
526 if ([connection.clientAccessGroups containsObject:accessGroup]) {
527 return true;
528 }
529 else {
530 localError = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInvalidAccessGroup userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"client not in access group: %@", accessGroup]}];
531 }
532 }
533 else {
534 localError = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorMissingAccessGroup userInfo:@{NSLocalizedDescriptionKey : @"keychain item missing access group"}];
535 }
536
537 if (error) {
538 *error = localError;
539 }
540 secerror("SecCDKeychain: failed to validate item owner with error: %@", localError);
541 return false;
542 }
543
544 - (void)insertItems:(NSArray<SecCDKeychainItem*>*)items withConnection:(SFKeychainServerConnection*)connection completionHandler:(void (^)(bool success, NSError* error))completionHandler
545 {
546 __weak __typeof(self) weakSelf = self;
547 [self performOnManagedObjectQueue:^(NSManagedObjectContext* managedObjectContext, NSError* managedObjectError) {
548 __strong __typeof(self) strongSelf = weakSelf;
549 if (!strongSelf) {
550 secerror("SecCDKeychain: attempt to insert items into deallocated keychain instance");
551 completionHandler(false, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : @"attempt to insert items into deallocated keychain instance"}]);
552 return;
553 }
554
555 if (!managedObjectContext) {
556 secerror("SecCDKeychain: insertItems: could not get managed object context");
557 completionHandler(false, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal userInfo:@{ NSLocalizedDescriptionKey : @"insertItems: could not get managed object context", NSUnderlyingErrorKey : managedObjectError }]);
558 return;
559 }
560
561 __block NSError* error = nil;
562 __block bool success = true;
563 for (SecCDKeychainItem* item in items) {
564 if (![self validateItemOwner:item.owner withConnection:connection withError:&error]) {
565 success = false;
566 break;
567 }
568
569 NSString* itemTypeName = item.itemType.name;
570 NSString* primaryKeyValue = [item primaryKeyStringRepresentationWithError:&error];
571 if (primaryKeyValue) {
572 NSFetchRequest* primaryKeyFetchRequest = [SecCDKeychainManagedLookupEntry fetchRequest];
573 primaryKeyFetchRequest.predicate = [NSPredicate predicateWithFormat:@"lookupKey == %@ AND lookupValue == %@", [self primaryKeyNameForItemTypeName:itemTypeName], primaryKeyValue];
574 SecCDKeychainManagedLookupEntry* managedLookupEntry = [[managedObjectContext executeFetchRequest:primaryKeyFetchRequest error:&error] firstObject];
575 if (managedLookupEntry) {
576 secerror("SecCDKeychain: failed to unique item (%@) using primary keys: %@", item, item.itemType.primaryKeys);
577 success = false;
578 error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorDuplicateItem userInfo:@{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"failed to unique item (%@) using primary keys: %@", item, item.itemType.primaryKeys] }];
579 break;
580 }
581 }
582 else {
583 secerror("SecCDKeychain: error creating primary key string representation for item: %@; error: %@", item, error);
584 success = false;
585 break;
586 }
587
588 SecCDKeychainManagedItem* managedItem = [self managedItemWithItem:item withManagedObjectContext:managedObjectContext error:&error];
589 if (!managedItem) {
590 secerror("SecCDKeychain: error creating managed item for insertion: %@; item: %@", error, item);
591 success = false;
592 break;
593 }
594
595 // add a lookup entry for the primary key
596 SecCDKeychainManagedLookupEntry* primaryKeyLookupEntry = [NSEntityDescription insertNewObjectForEntityForName:SecCDKeychainEntityLookupEntry inManagedObjectContext:managedObjectContext];
597 primaryKeyLookupEntry.itemTypeName = itemTypeName;
598 primaryKeyLookupEntry.lookupKey = [self primaryKeyNameForItemTypeName:itemTypeName];
599 primaryKeyLookupEntry.lookupValue = primaryKeyValue;
600 primaryKeyLookupEntry.lookupValueType = SecCDKeychainLookupValueTypeString;
601 primaryKeyLookupEntry.systemEntry = YES;
602 [primaryKeyLookupEntry addMatchingItemsObject:managedItem];
603 secdebug("SecCDKeychain", "added primary key: %@ value: %@", primaryKeyLookupEntry.lookupKey, primaryKeyLookupEntry.lookupValue);
604
605 // and add lookup entries for all the things we're supposed to be able to lookup on
606 for (SecCDKeychainLookupTuple* lookupTuple in item.lookupAttributes) {
607 NSFetchRequest* lookupEntryFetchRequest = [SecCDKeychainManagedLookupEntry fetchRequest];
608 lookupEntryFetchRequest.predicate = [NSPredicate predicateWithFormat:@"lookupKey == %@ AND lookupValueType == %@ AND lookupValue == %@", lookupTuple.key, lookupTuple.valueType, lookupTuple.value];
609 SecCDKeychainManagedLookupEntry* lookupEntry = [[managedObjectContext executeFetchRequest:lookupEntryFetchRequest error:&error] firstObject];
610 if (error) {
611 secerror("SecCDKeychain: error fetching lookup entry during item insertion: %@", (__bridge CFErrorRef)error);
612 success = false;
613 break;
614 }
615 else if (!lookupEntry) {
616 // we didn't get an error, but we didn't find a lookup entry
617 // that means there simply wasn't one in the db, so we should create one
618 lookupEntry = [NSEntityDescription insertNewObjectForEntityForName:SecCDKeychainEntityLookupEntry inManagedObjectContext:managedObjectContext];
619 lookupEntry.itemTypeName = itemTypeName;
620 lookupEntry.lookupKey = lookupTuple.key;
621 lookupEntry.lookupValue = lookupTuple.stringRepresentation;
622 lookupEntry.lookupValueType = lookupTuple.valueType;
623 secdebug("SecCDKeychain", "added item for key (%@) : value (%@)", lookupEntry.lookupKey, lookupEntry.lookupValue);
624 }
625
626 [lookupEntry addMatchingItemsObject:managedItem];
627 }
628
629 if (!success) {
630 break;
631 }
632 }
633
634 if (success) {
635 secnotice("SecCDKeychain", "saving managed object context for items: %@", items);
636 success = [managedObjectContext save:&error];
637 if (error) {
638 secerror("SecCDKeychain: saving managed object context failed with error: %@", error);
639 }
640 else {
641 secnotice("SecCDKeychain", "saving managed object context succeeded");
642 }
643 }
644
645 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
646 completionHandler(success, error);
647 });
648 }];
649 }
650
651 - (SecCDKeychainManagedItem*)fetchManagedItemForPersistentID:(NSUUID*)persistentID withManagedObjectContext:(NSManagedObjectContext*)managedObjectContext error:(NSError**)error
652 {
653 NSError* localError = nil;
654 NSFetchRequest* lookupEntryFetchRequest = [SecCDKeychainManagedItem fetchRequest];
655 lookupEntryFetchRequest.predicate = [NSPredicate predicateWithFormat:@"persistentID == %@", persistentID];
656 SecCDKeychainManagedItem* managedItem = [[managedObjectContext executeFetchRequest:lookupEntryFetchRequest error:&localError] firstObject];
657
658 if (error) {
659 *error = localError;
660 }
661
662 return managedItem;
663 }
664
665 - (void)fetchItemForPersistentID:(NSUUID*)persistentID withConnection:(SFKeychainServerConnection*)connection completionHandler:(void (^)(SecCDKeychainItem* item, NSError* error))completionHandler
666 {
667 __weak __typeof(self) weakSelf = self;
668 [self performOnManagedObjectQueue:^(NSManagedObjectContext* managedObjectContext, NSError* managedObjectError) {
669 __strong __typeof(self) strongSelf = weakSelf;
670 if (!strongSelf) {
671 secerror("SecCDKeychain: attempt to fetch item from deallocated keychain instance");
672 completionHandler(false, [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : @"attempt to fetch item from deallocated keychain instance"}]);
673 return;
674 }
675
676 if (!managedObjectContext) {
677 secerror("SecCDKeychain: fetchItemForPersistentID: could not get managed object context");
678 completionHandler(false, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal userInfo:@{ NSLocalizedDescriptionKey : @"fetchItemForPersistentID: could not get managed object context", NSUnderlyingErrorKey : managedObjectError }]);
679 return;
680 }
681
682 NSError* error = nil;
683 SecCDKeychainItem* fetchedItem = nil;
684 SecCDKeychainManagedItem* managedItem = [self fetchManagedItemForPersistentID:persistentID withManagedObjectContext:managedObjectContext error:&error];
685 if (error) {
686 secerror("SecCDKeychain: error fetching item for persistent id: %@; error: %@", persistentID, error);
687 }
688 else if (!managedItem) {
689 // we didn't get an error, but we didn't find an item
690 // that means there simply wasn't one in the db, so this lookup finds nothing
691 error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorItemNotFound userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"did not find any keychain items matching persistent ID: %@", persistentID.UUIDString]}];
692 }
693 else {
694 fetchedItem = [[SecCDKeychainItem alloc] initWithManagedItem:managedItem keychain:self error:&error];
695 if (!fetchedItem || error) {
696 secerror("SecCDKeychain: failed to create SecCDKeychainItem from managed item with error: %@", error);
697 fetchedItem = nil;
698 }
699 else if (![self validateItemOwner:fetchedItem.owner withConnection:connection withError:nil]) { // if we fail owner validation, we don't return an error; we pretend it didn't exist
700 fetchedItem = nil;
701 error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorItemNotFound userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"did not find any keychain items matching persistent ID: %@", persistentID.UUIDString]}];
702 }
703 }
704
705 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
706 completionHandler(fetchedItem, error);
707 });
708 }];
709 }
710
711 - (void)fetchItemsWithValue:(NSString*)value forLookupKey:(NSString*)lookupKey ofType:(SecCDKeychainLookupValueType*)lookupValueType withConnection:(SFKeychainServerConnection*)connection completionHandler:(void (^)(NSArray<SecCDKeychainItemMetadata*>* items, NSError* error))completionHandler
712 {
713 __weak __typeof(self) weakSelf = self;
714 [self performOnManagedObjectQueue:^(NSManagedObjectContext* managedObjectContext, NSError* managedObjectError) {
715 __strong __typeof(self) strongSelf = weakSelf;
716 if (!strongSelf) {
717 secerror("SecCDKeychain: attempt to fetch items from deallocated keychain instance");
718 completionHandler(false, [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : @"attempt to lookup items from deallocated keychain instance"}]);
719 return;
720 }
721
722 if (!managedObjectContext) {
723 secerror("SecCDKeychain: fetchItemsWithValue: could not get managed object context");
724 completionHandler(false, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal userInfo:@{ NSLocalizedDescriptionKey : @"fetchItemsWithValue: could not get managed object context", NSUnderlyingErrorKey : managedObjectError }]);
725 return;
726 }
727
728 NSError* error = nil;
729 NSMutableArray* fetchedItems = [[NSMutableArray alloc] init];
730 NSFetchRequest* lookupEntryFetchRequest = [SecCDKeychainManagedLookupEntry fetchRequest];
731 lookupEntryFetchRequest.predicate = [NSPredicate predicateWithFormat:@"lookupKey == %@ AND lookupValueType == %@ AND lookupValue == %@", lookupKey, lookupValueType, value];
732 NSArray* lookupEntries = [managedObjectContext executeFetchRequest:lookupEntryFetchRequest error:&error];
733 if (error) {
734 secerror("SecCDKeychain: error fetching lookup entry during item lookup: %@", error);
735 fetchedItems = nil;
736 }
737 else if (lookupEntries.count == 0) {
738 // we didn't get an error, but we didn't find a lookup entry
739 // that means there simply wasn't one in the db, so this lookup finds nothing
740 CFErrorRef cfError = NULL;
741 SecError(errSecItemNotFound, &cfError, CFSTR("did not find any keychain items matching query"));
742 error = CFBridgingRelease(cfError);
743 }
744 else {
745 for (SecCDKeychainManagedLookupEntry* lookupEntry in lookupEntries) {
746 for (SecCDKeychainManagedItem* item in lookupEntry.matchingItems) {
747 SecCDKeychainItemMetadata* fetchedItem = [[SecCDKeychainItemMetadata alloc] initWithManagedItem:item keychain:self error:&error];
748 if (!fetchedItem || error) {
749 secerror("SecCDKeychain: failed to create SecCDKeychainItemMetadata from managed item with error: %@", error);
750 fetchedItems = nil;
751 break;
752 }
753 else if (![self validateItemOwner:fetchedItem.owner withConnection:connection withError:nil]) { // not an error; just pretend it's not there
754 continue;
755 }
756
757 [fetchedItems addObject:fetchedItem];
758 }
759 }
760 }
761
762 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
763 completionHandler(fetchedItems, error);
764 });
765 }];
766 }
767
768 - (void)deleteItemWithPersistentID:(NSUUID*)persistentID withConnection:(SFKeychainServerConnection*)connection completionHandler:(void (^)(bool success, NSError* _Nullable error))completionHandler
769 {
770 __weak __typeof(self) weakSelf = self;
771 [self performOnManagedObjectQueue:^(NSManagedObjectContext* managedObjectContext, NSError* managedObjectError) {
772 __strong __typeof(self) strongSelf = weakSelf;
773 if (!strongSelf) {
774 secerror("SecCDKeychain: attempt to fetch items from deallocated keychain instance");
775 completionHandler(false, [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : @"attempt to insert items into deallocated keychain instance"}]);
776 return;
777 }
778
779 if (!managedObjectContext) {
780 secerror("SecCDKeychain: deleteItemWithPersistentID: could not get managed object context");
781 completionHandler(false, [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorInternal userInfo:@{ NSLocalizedDescriptionKey : @"deleteItemWIthPersistentID: could not get managed object context", NSUnderlyingErrorKey : managedObjectError }]);
782 return;
783 }
784
785 NSError* error = nil;
786 SecCDKeychainManagedItem* managedItem = [self fetchManagedItemForPersistentID:persistentID withManagedObjectContext:managedObjectContext error:&error];
787 bool success = false;
788 if (managedItem && !error) {
789 SecCDKeychainAccessControlEntity* owner = [[SecCDKeychainAccessControlEntity alloc] initWithManagedEntity:managedItem.owner];
790 if ([self validateItemOwner:owner withConnection:connection withError:nil]) { // don't pass error here because we will treat it below as simply "item could not be found"
791 for (SecCDKeychainManagedLookupEntry* lookupEntry in managedItem.lookupEntries.copy) {
792 [lookupEntry removeMatchingItemsObject:managedItem];
793 if (lookupEntry.matchingItems.count == 0) {
794 [managedObjectContext deleteObject:lookupEntry];
795 }
796 }
797
798 SecCDKeychainManagedAccessControlEntity* managedOwner = managedItem.owner;
799 [managedOwner removeOwnedItemsObject:managedItem];
800 [managedOwner removeAccessedItemsObject:managedItem];
801 if (managedOwner.ownedItems.count == 0 && managedOwner.accessedItems == 0) {
802 [managedObjectContext deleteObject:managedOwner];
803 }
804
805 for (SecCDKeychainManagedAccessControlEntity* accessControlEntity in managedItem.accessControlList) {
806 [accessControlEntity removeAccessedItemsObject:managedItem];
807 if (accessControlEntity.ownedItems.count == 0 && accessControlEntity.accessedItems == 0) {
808 [managedObjectContext deleteObject:accessControlEntity];
809 }
810 }
811
812 [managedObjectContext deleteObject:managedItem];
813 success = [managedObjectContext save:&error];
814 }
815 else {
816 success = false;
817 }
818 }
819
820 if (!success && !error) {
821 secerror("SecCDKeychain: attempt to delete item with persistant identifier that could not be found: %@", persistentID);
822 error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorItemNotFound userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"attempt to delete item with persistant identifier that could not be found: %@", persistentID]}];
823 }
824
825 dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
826 completionHandler(success, error);
827 });
828 }];
829 }
830
831 - (void)registerItemType:(SecCDKeychainItemType*)itemType withManagedObjectContext:(NSManagedObjectContext*)managedObjectContext
832 {
833 _itemTypeDict[itemType.name] = itemType;
834
835 NSMutableDictionary* itemTypeVersionDict = _managedItemTypeDict[itemType.name];
836 if (!itemTypeVersionDict) {
837 itemTypeVersionDict = [NSMutableDictionary dictionary];
838 _managedItemTypeDict[itemType.name] = itemTypeVersionDict;
839 }
840
841 if (!itemTypeVersionDict[@(itemType.version)]) {
842 NSError* error = nil;
843 SecCDKeychainManagedItemType* managedItemType = [itemType managedItemTypeWithContext:managedObjectContext error:&error];
844 if (managedItemType) {
845 itemTypeVersionDict[@(itemType.version)] = managedItemType;
846 [managedObjectContext save:&error];
847 }
848
849 if (!managedItemType || error) {
850 secerror("SecCDKeychain: error registering managedItemType for for itemType: %@", itemType);
851 }
852 }
853 }
854
855 // this method is only for use by tests because built-in types should get registered at initial store setup
856 - (void)_registerItemTypeForTesting:(SecCDKeychainItemType*)itemType
857 {
858 [self performOnManagedObjectQueueAndWait:^(NSManagedObjectContext* managedObjectContext, NSError* managedObjectError) {
859 if (!managedObjectContext) {
860 secerror("SecCDKeychain: _registerItemTypeForTesting: could not get managed object context");
861 return;
862 }
863
864 [self registerItemType:itemType withManagedObjectContext:managedObjectContext];
865 }];
866 }
867
868 - (SecCDKeychainItemType*)itemTypeForItemTypeName:(NSString*)itemTypeName
869 {
870 return _itemTypeDict[itemTypeName];
871 }
872
873 - (SecCDKeychainManagedItem*)managedItemWithItem:(SecCDKeychainItem*)item withManagedObjectContext:(NSManagedObjectContext*)managedObjectContext error:(NSError**)error
874 {
875 NSError* localError = nil;
876 SecCDKeychainManagedItem* managedItem = [NSEntityDescription insertNewObjectForEntityForName:SecCDKeychainEntityItem inManagedObjectContext:managedObjectContext];
877 managedItem.itemType = [[_managedItemTypeDict valueForKey:item.itemType.name] objectForKey:@(item.itemType.version)];
878 managedItem.persistentID = item.metadata.persistentID;
879
880 NSData* attributeData = [NSPropertyListSerialization dataWithPropertyList:item.attributes format:NSPropertyListBinaryFormat_v1_0 options:0 error:&localError];
881 managedItem.metadata = attributeData;
882
883 SecCDKeychainManagedAccessControlEntity* owner = [NSEntityDescription insertNewObjectForEntityForName:SecCDKeychainEntityTypeAccessControlEntity inManagedObjectContext:managedObjectContext];
884 owner.type = (int32_t)item.owner.entityType;
885 owner.stringRepresentation = item.owner.stringRepresentation;
886 managedItem.owner = owner;
887 [owner addOwnedItemsObject:managedItem];
888 [owner addAccessedItemsObject:managedItem];
889
890 // today, we only support the device keybag
891 // someday, that will have to change
892 managedItem.data = [item encryptedSecretDataWithAttributeData:attributeData keybag:KEYBAG_DEVICE error:&localError];
893
894 if (error) {
895 *error = localError;
896 }
897
898 return localError ? nil : managedItem;
899 }
900
901 @end
902
903 @implementation SecCDKeychainItemMetadata {
904 SecCDKeychainItemType* _itemType;
905 SecCDKeychainAccessControlEntity* _owner;
906 NSUUID* _persistentID;
907 NSDictionary* _attributes;
908 NSSet<SecCDKeychainLookupTuple*>* _lookupAttributes;
909 NSData* _managedDataBlob; // hold onto this to verify metadata been tampered with
910 keyclass_t _keyclass;
911 }
912
913 @synthesize itemType = _itemType;
914 @synthesize owner = _owner;
915 @synthesize persistentID = _persistentID;
916 @synthesize attributes = _attributes;
917 @synthesize lookupAttributesSet = _lookupAttributes;
918 @synthesize managedDataBlob = _managedDataBlob;
919 @synthesize keyclass = _keyclass;
920
921 - (instancetype)initWithItemType:(SecCDKeychainItemType*)itemType persistentID:(NSUUID*)persistentID attributes:(NSDictionary*)attributes lookupAttributes:(NSArray<SecCDKeychainLookupTuple*>*)lookupAttributes owner:(SecCDKeychainAccessControlEntity*)owner keyclass:(keyclass_t)keyclass
922 {
923 if (self = [super init]) {
924 _itemType = itemType;
925 _owner = owner;
926 _persistentID = persistentID.copy;
927 _attributes = attributes.copy;
928 _keyclass = keyclass;
929
930 if (lookupAttributes) {
931 _lookupAttributes = [NSSet setWithArray:lookupAttributes];
932 }
933 else {
934 NSMutableSet* newLookupAttributes = [[NSMutableSet alloc] init];
935 [_attributes enumerateKeysAndObjectsUsingBlock:^(NSString* key, id value, BOOL* stop) {
936 SecCDKeychainLookupTuple* lookupTuple = [SecCDKeychainLookupTuple lookupTupleWithKey:key value:value];
937 [newLookupAttributes addObject:lookupTuple];
938 }];
939 _lookupAttributes = newLookupAttributes.copy;
940 }
941 }
942
943 return self;
944 }
945
946 - (instancetype)initWithManagedItem:(SecCDKeychainManagedItem*)managedItem keychain:(SecCDKeychain*)keychain error:(NSError**)error
947 {
948 if (self = [super init]) {
949 _itemType = [keychain itemTypeForItemTypeName:managedItem.itemType.name];
950 _persistentID = managedItem.persistentID.copy;
951 _managedDataBlob = managedItem.metadata;
952 _attributes = [NSPropertyListSerialization propertyListWithData:_managedDataBlob options:NSPropertyListImmutable format:NULL error:error];
953 _owner = [[SecCDKeychainAccessControlEntity alloc] initWithManagedEntity:managedItem.owner];
954
955 NSMutableSet* lookupAttributes = [NSMutableSet set];
956 for (SecCDKeychainManagedLookupEntry* lookupEntry in managedItem.lookupEntries) {
957 if (!lookupEntry.systemEntry) {
958 [lookupAttributes addObject:[SecCDKeychainLookupTuple lookupTupleWithManagedLookupEntry:lookupEntry]];
959 }
960 }
961 _lookupAttributes = lookupAttributes.copy;
962
963 if (!_itemType || !_persistentID || !_owner || ![_attributes isKindOfClass:[NSDictionary class]]) {
964 if (error) {
965 *error = [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorDeserializing userInfo:@{NSLocalizedDescriptionKey : [NSString stringWithFormat:@"failed to deserialize SecCDKeychainItemMetadata with item type (%@) persistentID: %@ owner: %@", _itemType, _persistentID, _owner]}];
966 }
967 self = nil;;
968 }
969 }
970
971 return self;
972 }
973
974 - (BOOL)isEqual:(SecCDKeychainItemMetadata*)object
975 {
976 return [_itemType isEqual:object.itemType] &&
977 [_owner isEqual:object.owner] &&
978 [_persistentID isEqual:object.persistentID] &&
979 [_attributes isEqual:object.attributes] &&
980 [_lookupAttributes isEqual:object.lookupAttributesSet];
981 }
982
983 - (NSString*)description
984 {
985 return [NSString stringWithFormat:@"%@: itemType:(%@) owner:(%@) persistentID:(%@)\n attributes: %@\n lookup attributes: %@", [super description], self.itemType, self.owner, self.persistentID, self.attributes, self.lookupAttributes];
986 }
987
988 - (void)fetchFullItemWithKeychain:(SecCDKeychain*)keychain withConnection:(SFKeychainServerConnection*)connection completionHandler:(void (^)(SecCDKeychainItem* _Nullable item, NSError* _Nullable error))completionHandler
989 {
990 [keychain fetchItemForPersistentID:_persistentID withConnection:connection completionHandler:completionHandler];
991 }
992
993 - (NSArray<SecCDKeychainLookupTuple*>*)lookupAttributes
994 {
995 return _lookupAttributes.allObjects;
996 }
997
998 - (NSArray*)primaryKeys
999 {
1000 return _itemType.primaryKeys;
1001 }
1002
1003 @end
1004
1005 @implementation SecCDKeychainItem {
1006 SecCDKeychainItemMetadata* _metadata;
1007 NSData* _encryptedSecretData;
1008 NSDictionary* _secrets;
1009 }
1010
1011 @synthesize metadata = _metadata;
1012 @synthesize secrets = _secrets;
1013
1014 - (instancetype)initItemType:(SecCDKeychainItemType*)itemType withPersistentID:(NSUUID*)persistentID attributes:(NSDictionary*)attributes lookupAttributes:(NSArray<SecCDKeychainLookupTuple*>*)lookupAttributes secrets:(NSDictionary*)secrets owner:(SecCDKeychainAccessControlEntity*)owner keyclass:(keyclass_t)keyclass
1015 {
1016 if (self = [super init]) {
1017 _secrets = secrets.copy;
1018 _metadata = [[SecCDKeychainItemMetadata alloc] initWithItemType:itemType persistentID:persistentID attributes:attributes lookupAttributes:lookupAttributes owner:owner keyclass:keyclass];
1019 if (!_metadata) {
1020 self = nil;
1021 }
1022 }
1023
1024 return self;
1025 }
1026
1027 - (instancetype)initWithManagedItem:(SecCDKeychainManagedItem*)managedItem keychain:(SecCDKeychain*)keychain error:(NSError**)error
1028 {
1029 if (self = [super init]) {
1030 NSError* localError;
1031 _metadata = [[SecCDKeychainItemMetadata alloc] initWithManagedItem:(SecCDKeychainManagedItem*)managedItem keychain:keychain error:&localError];
1032 if (!_metadata) {
1033 if (error) {
1034 // TODO: add a negative unit test
1035
1036 *error = [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorDeserializing userInfo:@{NSLocalizedDescriptionKey : @"could not create SecCDKeychainItem from managed item - managed item was malformed"}];
1037 }
1038 return nil;
1039 }
1040
1041 _secrets = [self secretsFromEncryptedData:managedItem.data withKeybag:KEYBAG_DEVICE error:&localError];
1042 if (!_secrets) {
1043 if (error) {
1044 *error = [NSError errorWithDomain:SFKeychainErrorDomain code:SFKeychainErrorItemDecryptionFailed userInfo:@{ NSLocalizedDescriptionKey : @"could not decrypt secrets for item", NSUnderlyingErrorKey : localError }];
1045 }
1046 return nil;
1047 }
1048 }
1049
1050 return self;
1051 }
1052
1053 - (BOOL)isEqual:(SecCDKeychainItem*)object
1054 {
1055 return [object isKindOfClass:[SecCDKeychainItem class]]
1056 && [_metadata isEqual:object.metadata]
1057 && [_secrets isEqual:object.secrets];
1058 }
1059
1060 - (NSString*)description
1061 {
1062 return [NSString stringWithFormat:@"%@: itemType:(%@) persistentID:(%@)\n owner: %@\n attributes: %@\n lookup attributes: %@\nprimaryKeys: %@", [super description], self.itemType, self.persistentID, self.owner, self.attributes, self.lookupAttributes, self.primaryKeys];
1063 }
1064
1065 - (SecCDKeychainItemType*)itemType
1066 {
1067 return _metadata.itemType;
1068 }
1069
1070 - (SecCDKeychainAccessControlEntity*)owner
1071 {
1072 return _metadata.owner;
1073 }
1074
1075 - (NSUUID*)persistentID
1076 {
1077 return _metadata.persistentID;
1078 }
1079
1080 - (NSDictionary*)attributes
1081 {
1082 return _metadata.attributes;
1083 }
1084
1085 - (NSArray<SecCDKeychainLookupTuple*>*)lookupAttributes
1086 {
1087 return _metadata.lookupAttributes;
1088 }
1089
1090 - (NSArray*)primaryKeys
1091 {
1092 return _metadata.primaryKeys;
1093 }
1094
1095 - (NSString*)primaryKeyStringRepresentationWithError:(NSError**)error
1096 {
1097 NSDictionary* attributes = _metadata.attributes;
1098 NSArray* primaryKeys = _metadata.primaryKeys.count > 0 ? _metadata.primaryKeys : attributes.allKeys;
1099 NSArray* sortedPrimaryKeys = [primaryKeys sortedArrayUsingComparator:^NSComparisonResult(NSString* firstKey, NSString* secondKey) {
1100 return [firstKey compare:secondKey options:NSForcedOrderingSearch];
1101 }];
1102
1103 SFSHA256DigestOperation* digest = [[SFSHA256DigestOperation alloc] init];
1104 [digest addData:[_metadata.owner.stringRepresentation dataUsingEncoding:NSUTF8StringEncoding]];
1105
1106 for (NSString* key in sortedPrimaryKeys) {
1107 [digest addData:[key dataUsingEncoding:NSUTF8StringEncoding]];
1108
1109 id value = attributes[key];
1110 if ([value isKindOfClass:[NSData class]]) {
1111 [digest addData:value];
1112 }
1113 else if ([value isKindOfClass:[NSString class]]) {
1114 [digest addData:[value dataUsingEncoding:NSUTF8StringEncoding]];
1115 }
1116 else {
1117 NSData* valueData = [NSKeyedArchiver archivedDataWithRootObject:value requiringSecureCoding:YES error:error];
1118 if (valueData) {
1119 [digest addData:valueData];
1120 }
1121 else {
1122 return nil;
1123 }
1124 }
1125 }
1126
1127 return [[digest hashValue] base64EncodedStringWithOptions:0];
1128 }
1129
1130 - (NSData*)encryptedSecretDataWithAttributeData:(NSData*)attributeData keybag:(keybag_handle_t)keybag error:(NSError**)error
1131 {
1132 #if USE_KEYSTORE
1133 NSError* localError = nil;
1134 NSString* errorDescription = nil;
1135
1136 SFAESKeySpecifier* keySpecifier = [[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256];
1137 SFAESKey* randomKey = [[SFAESKey alloc] initRandomKeyWithSpecifier:keySpecifier error:&localError];
1138 if (!randomKey) {
1139 secerror("SecCDKeychain: failed to create random key for encrypting item with error: %@", localError);
1140 errorDescription = @"failed to create random key for encrypting item";
1141 }
1142
1143 NSData* itemSecrets = [NSPropertyListSerialization dataWithPropertyList:_secrets format:NSPropertyListBinaryFormat_v1_0 options:0 error:&localError];
1144 if (!itemSecrets) {
1145 secerror("SecCDKeychain: failed to serialize item secrets dictionary with error: %@", localError);
1146 errorDescription = @"failed to serialize item secrets dictionary";
1147 }
1148
1149 SFAuthenticatedEncryptionOperation* encryptionOperation = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:keySpecifier];
1150 SFAuthenticatedCiphertext* ciphertext = nil;
1151 if (randomKey && itemSecrets) {
1152 NSData* attributeDataSHA256 = [SFSHA256DigestOperation digest:attributeData];
1153 ciphertext = [encryptionOperation encrypt:itemSecrets withKey:randomKey additionalAuthenticatedData:attributeDataSHA256 error:&localError];
1154 if (!ciphertext) {
1155 secerror("SecCDKeychain: failed to encrypt item secret data with error: %@", localError);
1156 errorDescription = @"failed to encrypt item secret data";
1157 }
1158 }
1159
1160 SecAKSRefKey* refKey = nil;
1161 NSData* refKeyBlobData = nil;
1162
1163 if (ciphertext) {
1164 refKey = [[SecAKSRefKey alloc] initWithKeybag:keybag keyclass:_metadata.keyclass];
1165 refKeyBlobData = refKey.refKeyBlob;
1166 if (!refKey || !refKeyBlobData) {
1167 secerror("SecCDKeychain: failed to create refKey");
1168 errorDescription = @"failed to create refKey";
1169 }
1170 }
1171
1172 NSData* wrappedKeyData = nil;
1173 if (refKey && refKeyBlobData) {
1174 wrappedKeyData = [refKey wrappedDataForKey:randomKey];
1175 if (!wrappedKeyData) {
1176 secerror("SecCDKeychain: failed to encrypt item");
1177 errorDescription = @"failed to encrypt item";
1178 }
1179 }
1180
1181 if (wrappedKeyData) {
1182 SecCDKeychainItemWrappedSecretData* wrappedSecretData = [[SecCDKeychainItemWrappedSecretData alloc] initWithCiphertext:ciphertext wrappedKeyData:wrappedKeyData refKeyBlob:refKeyBlobData];
1183 NSData* wrappedSecretDataBlob = [NSKeyedArchiver archivedDataWithRootObject:wrappedSecretData requiringSecureCoding:YES error:&localError];
1184 if (wrappedSecretDataBlob) {
1185 return wrappedSecretDataBlob;
1186 }
1187 else {
1188 secerror("SecCDKeychain: failed to serialize item secret data blob with error: %@", localError);
1189 errorDescription = @"failed to serialize item secret data blob";
1190 }
1191 }
1192
1193 if (error) {
1194 NSDictionary* userInfo = localError ? @{ NSUnderlyingErrorKey : localError, NSLocalizedDescriptionKey : errorDescription } : @{NSLocalizedDescriptionKey : errorDescription};
1195 *error = [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorInternal userInfo:userInfo];
1196 }
1197
1198 #endif
1199 return nil;
1200 }
1201
1202 - (NSDictionary*)secretsFromEncryptedData:(NSData*)secretData withKeybag:(keybag_handle_t)keybag error:(NSError**)error
1203 {
1204 #if USE_KEYSTORE
1205 SecCDKeychainItemWrappedSecretData* wrappedSecretData = [NSKeyedUnarchiver unarchivedObjectOfClass:[SecCDKeychainItemWrappedSecretData class] fromData:secretData error:error];
1206 if (!wrappedSecretData) {
1207 secerror("SecCDKeychain: failed to deserialize item wrapped secret data");
1208 return nil;
1209 }
1210
1211 SecAKSRefKey* refKey = [[SecAKSRefKey alloc] initWithBlob:wrappedSecretData.refKeyBlob keybag:keybag];
1212 if (!refKey) {
1213 secerror("SecCDKeychain: failed to create refKey for unwrapping item secrets");
1214 if (error) {
1215 *error = [NSError errorWithDomain:SecCDKeychainErrorDomain code:SecCDKeychainErrorInternal userInfo:@{NSLocalizedDescriptionKey : @"failed to create refKey for unwrapping item secrets"}];
1216 }
1217 return nil;
1218 }
1219
1220 SFAESKey* key = [refKey keyWithWrappedData:wrappedSecretData.wrappedKeyData];
1221 if (!key) {
1222 secerror("SecCDKeychain: failed to create item key for decryption");
1223 return nil;
1224 }
1225
1226 SFAESKeySpecifier* keySpecifier = [[SFAESKeySpecifier alloc] initWithBitSize:SFAESKeyBitSize256];
1227 SFAuthenticatedEncryptionOperation* encryptionOperation = [[SFAuthenticatedEncryptionOperation alloc] initWithKeySpecifier:keySpecifier];
1228 NSData* metadataSHA256 = [SFSHA256DigestOperation digest:_metadata.managedDataBlob];
1229 NSData* decryptedSecretData = [encryptionOperation decrypt:wrappedSecretData.ciphertext withKey:key additionalAuthenticatedData:metadataSHA256 error:error];
1230 if (!decryptedSecretData) {
1231 secerror("SecCDKeychain: failed to decrypt item secret data");
1232 return nil;
1233 }
1234
1235 NSDictionary* secrets = [NSPropertyListSerialization propertyListWithData:decryptedSecretData options:0 format:NULL error:error];
1236 if (![secrets isKindOfClass:[NSDictionary class]]) {
1237 secerror("SecCDKeychain: failed to deserialize item decrypted secret data");
1238 return nil;
1239 }
1240
1241 return secrets;
1242 #else
1243 return nil;
1244 #endif
1245 }
1246
1247 // TODO: get back to this as part of CKKS integration work
1248
1249 //- (NSDictionary*)attributesPropertyListWithError:(NSError**)error
1250 //{
1251 // __block NSDictionary* (^dictionaryToPropertyList)(NSDictionary*) = NULL;
1252 // __block NSArray* (^arrayToPropertyList)(NSArray*) = NULL;
1253 //
1254 // dictionaryToPropertyList = ^(NSDictionary* dict) {
1255 // NSMutableDictionary* propertyList = [NSMutableDictionary dictionary];
1256 // [dict enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
1257 // Class objectClass = [object class];
1258 // if ([objectClass isKindOfClass:[NSString class]] || [objectClass isKindOfClass:[NSData class]] || [objectClass isKindOfClass:[NSDate class]] || [objectClass isKindOfClass:[NSNumber class]]) {
1259 // propertyList[key] = object;
1260 // }
1261 // else if ([objectClass isKindOfClass:[NSDictionary class]]){
1262 // NSDictionary* objectAsPropertyList = dictionaryToPropertyList(object);
1263 // if (objectAsPropertyList) {
1264 // propertyList[key] = objectAsPropertyList;
1265 // }
1266 // else {
1267 // *stop = YES;
1268 // }
1269 // }
1270 // else if ([objectClass isKindOfClass:[NSArray class]]) {
1271 // NSArray* objectAsPropertyList = arrayToPropertyList(object);
1272 // if (objectAsPropertyList) {
1273 // propertyList[key] = objectAsPropertyList;
1274 // }
1275 // else {
1276 // *stop = YES;
1277 // }
1278 // }
1279 // else if ([object conformsToProtocol:@protocol(NSSecureCoding)]) {
1280 // NSData*
1281 // }
1282 // }];
1283 // }
1284 //}
1285
1286 @end
1287
1288 @implementation SecCDKeychainLookupTuple {
1289 NSString* _key;
1290 id<NSCopying, NSObject> _value;
1291 SecCDKeychainLookupValueType* _valueType;
1292 }
1293
1294 @synthesize key = _key;
1295 @synthesize value = _value;
1296 @synthesize valueType = _valueType;
1297
1298 + (instancetype)lookupTupleWithKey:(NSString*)key value:(id<NSCopying, NSObject>)value
1299 {
1300 return [[self alloc] initWithKey:key value:value];
1301 }
1302
1303 + (instancetype)lookupTupleWithManagedLookupEntry:(SecCDKeychainManagedLookupEntry*)lookupEntry
1304 {
1305 NSString* valueString = lookupEntry.lookupValue;
1306 id value;
1307 NSString* valueType = lookupEntry.lookupValueType;
1308 if ([valueType isEqualToString:SecCDKeychainLookupValueTypeString]) {
1309 value = valueString;
1310 }
1311 else {
1312 NSData* valueData = [[NSData alloc] initWithBase64EncodedString:valueString options:0];
1313
1314 if ([valueType isEqualToString:SecCDKeychainLookupValueTypeData]) {
1315 value = valueData;
1316 }
1317 else if ([valueType isEqualToString:SecCDKeychainLookupValueTypeDate]) {
1318 // TODO: error parameter
1319 value = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSDate class] fromData:valueData error:nil];
1320 }
1321 else if ([valueType isEqualToString:SecCDKeychainLookupValueTypeNumber]) {
1322 value = [NSKeyedUnarchiver unarchivedObjectOfClass:[NSNumber class] fromData:valueData error:nil];
1323 }
1324 else {
1325 // TODO: error here
1326 value = nil;
1327 }
1328 }
1329
1330 return value ? [[self alloc] initWithKey:lookupEntry.lookupKey value:value] : nil;
1331 }
1332
1333 - (instancetype)initWithKey:(NSString*)key value:(id<NSCopying, NSObject>)value
1334 {
1335 if (self = [super init]) {
1336 SecCDKeychainLookupValueType* valueType = [SecCDKeychain lookupValueTypeForObject:value];
1337 BOOL zeroLengthValue = ([valueType isEqualToString:SecCDKeychainLookupValueTypeString] && [(NSString*)value length] == 0) || ([valueType isEqualToString:SecCDKeychainLookupValueTypeData] && [(NSData*)value length] == 0);
1338 if (valueType && !zeroLengthValue) {
1339 _key = key.copy;
1340 _value = [value copyWithZone:nil];
1341 _valueType = valueType.copy;
1342 }
1343 else {
1344 // TODO: add an error parameter to this method
1345 self = nil;
1346 }
1347 }
1348
1349 return self;
1350 }
1351
1352 - (BOOL)isEqual:(SecCDKeychainLookupTuple*)object
1353 {
1354 return [_key isEqualToString:object.key] && [_value isEqual:object.value] && [_valueType isEqualToString:object.valueType];
1355 }
1356
1357 - (NSUInteger)hash
1358 {
1359 return _key.hash ^ _value.hash ^ _valueType.hash;
1360 }
1361
1362 - (NSString*)description
1363 {
1364 return [NSString stringWithFormat:@"%@ : %@ [%@]", _key, _value, _valueType];
1365 }
1366
1367 - (NSString*)stringRepresentation
1368 {
1369 if ([_valueType isEqualToString:SecCDKeychainLookupValueTypeString]) {
1370 return (NSString*)_value;
1371 }
1372 else if ([_valueType isEqualToString:SecCDKeychainLookupValueTypeData]) {
1373 return [(NSData*)_value base64EncodedStringWithOptions:0];
1374 }
1375 else {
1376 return [[NSKeyedArchiver archivedDataWithRootObject:_value requiringSecureCoding:YES error:nil] base64EncodedStringWithOptions:0];
1377 }
1378 }
1379
1380 @end
1381
1382 @implementation SecCDKeychainItemType {
1383 NSString* _name;
1384 int32_t _version;
1385 NSSet* _primaryKeys;
1386 NSSet* _syncableKeys;
1387 SecCDKeychainManagedItemType* _managedItemType;
1388 }
1389
1390 @synthesize name = _name;
1391 @synthesize version = _version;
1392
1393 + (instancetype)itemType
1394 {
1395 return nil;
1396 }
1397
1398 + (nullable instancetype)itemTypeForVersion:(int32_t)version
1399 {
1400 return [self itemType];
1401 }
1402
1403 - (instancetype)_initWithName:(NSString*)name version:(int32_t)version primaryKeys:(NSArray*)primaryKeys syncableKeys:(NSArray*)syncableKeys
1404 {
1405 if (self = [super init]) {
1406 _name = name.copy;
1407 _version = version;
1408 _primaryKeys = [NSSet setWithArray:primaryKeys];
1409 _syncableKeys = [NSSet setWithArray:syncableKeys];
1410 }
1411
1412 return self;
1413 }
1414
1415 - (BOOL)isEqual:(SecCDKeychainItemType*)object
1416 {
1417 return [object isKindOfClass:[SecCDKeychainItemType class]] &&
1418 [_name isEqualToString:object.name] &&
1419 _version == object.version &&
1420 [_primaryKeys isEqualToSet:object->_primaryKeys] &&
1421 [_syncableKeys isEqualToSet:object->_syncableKeys];
1422 }
1423
1424 - (NSString*)description
1425 {
1426 return [NSString stringWithFormat:@"%@ | %d", _name, _version];
1427 }
1428
1429 - (NSString*)debugDescription
1430 {
1431 return [NSString stringWithFormat:@"%@\n name: %@\n version: %d\n primaryKeys: %@\n syncableKeys: %@", [super debugDescription], _name, _version, _primaryKeys, _syncableKeys];
1432 }
1433
1434 - (NSArray*)primaryKeys
1435 {
1436 return _primaryKeys.allObjects;
1437 }
1438
1439 - (NSArray*)syncableKeys
1440 {
1441 return _syncableKeys.allObjects;
1442 }
1443
1444 - (SecCDKeychainManagedItemType*)managedItemTypeWithContext:(NSManagedObjectContext*)managedObjectContext error:(NSError**)error
1445 {
1446 NSError* localError = nil;
1447 SecCDKeychainManagedItemType* managedItemType = [NSEntityDescription insertNewObjectForEntityForName:SecCDKeychainEntityItemType inManagedObjectContext:managedObjectContext];
1448 managedItemType.name = _name;
1449 managedItemType.version = _version;
1450 managedItemType.primaryKeys = [NSPropertyListSerialization dataWithPropertyList:_primaryKeys.allObjects format:NSPropertyListBinaryFormat_v1_0 options:0 error:&localError];
1451 managedItemType.syncableKeys = [NSPropertyListSerialization dataWithPropertyList:_syncableKeys.allObjects format:NSPropertyListBinaryFormat_v1_0 options:0 error:&localError];
1452
1453 if (error) {
1454 *error = localError;
1455 }
1456
1457 return localError ? nil : managedItemType;
1458 }
1459
1460 @end
1461
1462 @implementation SecCDKeychainAccessControlEntity {
1463 SecCDKeychainAccessControlEntityType _entityType;
1464 NSString* _stringRepresentation;
1465 }
1466
1467 @synthesize entityType = _entityType;
1468 @synthesize stringRepresentation = _stringRepresentation;
1469
1470 + (instancetype)accessControlEntityWithType:(SecCDKeychainAccessControlEntityType)type stringRepresentation:(NSString*)stringRepresentation
1471 {
1472 return [[self alloc] _initWithEntityType:type stringRepresentation:stringRepresentation];
1473 }
1474
1475 - (instancetype)_initWithEntityType:(SecCDKeychainAccessControlEntityType)type stringRepresentation:(NSString*)stringRepresentation
1476 {
1477 if (self = [super init]) {
1478 _entityType = type;
1479 _stringRepresentation = stringRepresentation.copy;
1480 }
1481
1482 return self;
1483 }
1484
1485 - (instancetype)initWithManagedEntity:(SecCDKeychainManagedAccessControlEntity*)managedAccessControlEntity
1486 {
1487 return [self _initWithEntityType:managedAccessControlEntity.type stringRepresentation:managedAccessControlEntity.stringRepresentation];
1488 }
1489
1490 - (BOOL)isEqual:(SecCDKeychainAccessControlEntity*)object
1491 {
1492 return _entityType == object.entityType && [_stringRepresentation isEqualToString:object.stringRepresentation];
1493 }
1494
1495 @end
1496
1497 #endif // TARGET_OS_BRIDGE