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