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