]> git.saurik.com Git - apple/security.git/blob - tests/secdmockaks/mockaksKeychain.m
Security-59754.41.1.tar.gz
[apple/security.git] / tests / secdmockaks / mockaksKeychain.m
1 /*
2 * Copyright (c) 2018 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 "SecKeybagSupport.h"
25 #import "SecDbKeychainItem.h"
26 #import "SecdTestKeychainUtilities.h"
27 #import "CKKS.h"
28 #import "SecItemPriv.h"
29 #import "SecItemServer.h"
30 #import "SecItemSchema.h"
31 #include "OSX/sec/Security/SecItemShim.h"
32 #import "server_security_helpers.h"
33 #import "spi.h"
34 #import "utilities/SecAKSWrappers.h"
35 #import <utilities/SecCFWrappers.h>
36 #import <utilities/SecFileLocations.h>
37 #import "utilities/der_plist.h"
38 #import <SecurityFoundation/SFEncryptionOperation.h>
39 #import <XCTest/XCTest.h>
40 #import <OCMock/OCMock.h>
41 #if USE_KEYSTORE
42 #if __has_include(<libaks.h>)
43 #import <libaks.h>
44 #endif // aks.h
45 #endif
46 #import <sqlite3.h>
47 #import "mockaks.h"
48
49 #import <TargetConditionals.h>
50
51 #import <LocalAuthentication/LocalAuthentication.h>
52
53 #import "secdmock_db_version_10_5.h"
54 #import "secdmock_db_version_11_1.h"
55
56 #import "mockaksxcbase.h"
57
58 @interface secdmockaks : mockaksxcbase
59 @end
60
61 @interface NSData (secdmockaks)
62 - (NSString *)hexString;
63 @end
64
65 @implementation NSData (secdmockaks)
66 - (NSString *)hexString
67 {
68 static const char tbl[] = "0123456789ABCDEF";
69 NSUInteger len = self.length;
70 char *buf = malloc(len * 2);
71 [self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
72 const uint8_t *p = (const uint8_t *)bytes;
73 for (NSUInteger i = byteRange.location; i < NSMaxRange(byteRange); i++) {
74 uint8_t byte = *p++;
75 buf[i * 2 + 0] = tbl[(byte >> 4) & 0xf];
76 buf[i * 2 + 1] = tbl[(byte >> 0) & 0xf];
77 }
78 }];
79 return [[NSString alloc] initWithBytesNoCopy:buf length:len * 2 encoding:NSUTF8StringEncoding freeWhenDone:YES];
80 }
81 @end
82
83
84 @implementation secdmockaks
85
86
87 - (void)testAddDeleteItem
88 {
89 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
90 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
91 (id)kSecAttrAccount : @"TestAccount",
92 (id)kSecAttrService : @"TestService",
93 (id)kSecUseDataProtectionKeychain : @(YES) };
94
95 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
96 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
97
98 NSMutableDictionary* dataQuery = item.mutableCopy;
99 [dataQuery removeObjectForKey:(id)kSecValueData];
100 dataQuery[(id)kSecReturnData] = @(YES);
101 CFTypeRef foundItem = NULL;
102 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
103 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
104
105 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
106 XCTAssertEqual(result, 0, @"failed to delete item");
107 }
108
109
110 - (void)createManyItems
111 {
112 unsigned n;
113 for (n = 0; n < 50; n++) {
114 NSDictionary* item = @{
115 (id)kSecClass : (id)kSecClassGenericPassword,
116 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
117 (id)kSecAttrAccount : [NSString stringWithFormat:@"TestAccount-%u", n],
118 (id)kSecAttrService : @"TestService",
119 (id)kSecUseDataProtectionKeychain : @(YES)
120 };
121 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
122 XCTAssertEqual(result, errSecSuccess, @"failed to add test item to keychain: %u", n);
123 }
124 }
125
126 - (void)createECKeyWithACL:(NSString *)label
127 {
128 NSDictionary* keyParams = @{
129 (__bridge id)kSecAttrKeyClass : (__bridge id)kSecAttrKeyClassPrivate,
130 (__bridge id)kSecAttrKeyType : (__bridge id)kSecAttrKeyTypeEC,
131 (__bridge id)kSecAttrKeySizeInBits: @(256),
132 };
133 SecAccessControlRef ref = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, 0, NULL);
134 XCTAssertNotEqual(ref, NULL, @"SAC");
135
136 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
137 XCTAssertNotEqual(key, NULL, @"failed to create test key to keychain");
138
139 NSDictionary* item = @{
140 (__bridge id)kSecClass: (__bridge id)kSecClassKey,
141 (__bridge id)kSecValueRef: (__bridge id)key,
142 (__bridge id)kSecAttrLabel: label,
143 (__bridge id)kSecUseDataProtectionKeychain: @(YES),
144 (__bridge id)kSecAttrAccessControl: (__bridge id)ref,
145 };
146
147 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
148 XCTAssertEqual(result, 0, @"failed to add test key to keychain");
149 }
150
151 - (void)createManyACLKeyItems
152 {
153 unsigned n;
154 for (n = 0; n < 50; n++) {
155 [self createECKeyWithACL: [NSString stringWithFormat:@"TestLabel-EC-%u", n]];
156 }
157 }
158
159 - (void)findManyItems:(unsigned)searchLimit
160 {
161 unsigned n;
162 for (n = 0; n < searchLimit; n++) {
163 NSDictionary* item = @{
164 (id)kSecClass : (id)kSecClassGenericPassword,
165 (id)kSecAttrAccount : [NSString stringWithFormat:@"TestAccount-%u", n],
166 (id)kSecAttrService : @"TestService",
167 (id)kSecUseDataProtectionKeychain : @(YES)
168 };
169 OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
170 XCTAssertEqual(result, errSecSuccess, @"failed to find test item to keychain: %u", n);
171 }
172 }
173
174 - (void)findNoItems:(unsigned)searchLimit
175 {
176 unsigned n;
177 for (n = 0; n < searchLimit; n++) {
178 NSDictionary* item = @{
179 (id)kSecClass : (id)kSecClassGenericPassword,
180 (id)kSecAttrAccount : [NSString stringWithFormat:@"TestAccount-%u", n],
181 (id)kSecAttrService : @"TestService",
182 (id)kSecUseDataProtectionKeychain : @(YES)
183 };
184 OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
185 XCTAssertEqual(result, errSecItemNotFound, @"not expecting to find an item (%d): %u", (int)result, n);
186 }
187 }
188
189 - (void)deleteAllItems
190 {
191 NSDictionary* item = @{
192 (id)kSecClass : (id)kSecClassGenericPassword,
193 (id)kSecAttrService : @"TestService",
194 (id)kSecUseDataProtectionKeychain : @(YES)
195 };
196 OSStatus result = SecItemDelete((__bridge CFDictionaryRef)item);
197 XCTAssertEqual(result, 0, @"failed to delete all test items");
198 }
199
200 - (void)testSecItemServerDeleteAll
201 {
202
203 [self addAccessGroup:@"com.apple.bluetooth"];
204 [self addAccessGroup:@"lockdown-identities"];
205 [self addAccessGroup:@"apple"];
206
207 // BT root key, should not be deleted
208 NSMutableDictionary* bt = [@{
209 (id)kSecClass : (id)kSecClassGenericPassword,
210 (id)kSecAttrAccessGroup : @"com.apple.bluetooth",
211 (id)kSecAttrService : @"BluetoothGlobal",
212 (id)kSecAttrAccessible : (id)kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate,
213 (id)kSecAttrSynchronizable : @(NO),
214 (id)kSecValueData : [@"btkey" dataUsingEncoding:NSUTF8StringEncoding],
215 } mutableCopy];
216
217 // lockdown-identities, should not be deleted
218 NSMutableDictionary* ld = [@{
219 (id)kSecClass : (id)kSecClassKey,
220 (id)kSecAttrAccessGroup : @"lockdown-identities",
221 (id)kSecAttrLabel : @"com.apple.lockdown.identity.activation",
222 (id)kSecAttrAccessible : (id)kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate,
223 (id)kSecAttrSynchronizable : @(NO),
224 (id)kSecValueData : [@"ldkey" dataUsingEncoding:NSUTF8StringEncoding],
225 } mutableCopy];
226
227 // general nonsyncable item, should be deleted
228 NSMutableDictionary* s0 = [@{
229 (id)kSecClass : (id)kSecClassGenericPassword,
230 (id)kSecAttrService : @"NonsyncableService",
231 (id)kSecAttrSynchronizable : @(NO),
232 (id)kSecValueData : [@"s0pwd" dataUsingEncoding:NSUTF8StringEncoding],
233 } mutableCopy];
234
235 // general syncable item, should be deleted
236 NSMutableDictionary* s1 = [@{
237 (id)kSecClass : (id)kSecClassGenericPassword,
238 (id)kSecAttrService : @"SyncableService",
239 (id)kSecAttrSynchronizable : @(YES),
240 (id)kSecValueData : [@"s0pwd" dataUsingEncoding:NSUTF8StringEncoding],
241 } mutableCopy];
242
243 // Insert all items
244 OSStatus status;
245 status = SecItemAdd((__bridge CFDictionaryRef)bt, NULL);
246 XCTAssertEqual(status, errSecSuccess, "failed to add bt item to keychain");
247 status = SecItemAdd((__bridge CFDictionaryRef)ld, NULL);
248 XCTAssertEqual(status, errSecSuccess, "failed to add ld item to keychain");
249 status = SecItemAdd((__bridge CFDictionaryRef)s0, NULL);
250 XCTAssertEqual(status, errSecSuccess, "failed to add s0 item to keychain");
251 status = SecItemAdd((__bridge CFDictionaryRef)s1, NULL);
252 XCTAssertEqual(status, errSecSuccess, "failed to add s1 item to keychain");
253
254 // Make sure they exist now
255 bt[(id)kSecValueData] = nil;
256 ld[(id)kSecValueData] = nil;
257 s0[(id)kSecValueData] = nil;
258 s1[(id)kSecValueData] = nil;
259 status = SecItemCopyMatching((__bridge CFDictionaryRef)bt, NULL);
260 XCTAssertEqual(status, errSecSuccess, "failed to find bt item in keychain");
261 status = SecItemCopyMatching((__bridge CFDictionaryRef)ld, NULL);
262 XCTAssertEqual(status, errSecSuccess, "failed to find ld item in keychain");
263 status = SecItemCopyMatching((__bridge CFDictionaryRef)s0, NULL);
264 XCTAssertEqual(status, errSecSuccess, "failed to find s0 item in keychain");
265 status = SecItemCopyMatching((__bridge CFDictionaryRef)s1, NULL);
266 XCTAssertEqual(status, errSecSuccess, "failed to find s1 item in keychain");
267
268 // Nuke the keychain
269 CFErrorRef error = NULL;
270 _SecItemDeleteAll(&error);
271 XCTAssertEqual(error, NULL, "_SecItemDeleteAll returned an error: %@", error);
272 CFReleaseNull(error);
273
274 // Does the function work properly with an error pre-set?
275 error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, errSecItemNotFound, NULL);
276 _SecItemDeleteAll(&error);
277 XCTAssertEqual(CFErrorGetDomain(error), kCFErrorDomainOSStatus);
278 XCTAssertEqual(CFErrorGetCode(error), errSecItemNotFound);
279 CFReleaseNull(error);
280
281 // Check the relevant items are missing
282 status = SecItemCopyMatching((__bridge CFDictionaryRef)bt, NULL);
283 XCTAssertEqual(status, errSecSuccess, "failed to find bt item in keychain");
284 status = SecItemCopyMatching((__bridge CFDictionaryRef)ld, NULL);
285 XCTAssertEqual(status, errSecSuccess, "failed to find ld item in keychain");
286 status = SecItemCopyMatching((__bridge CFDictionaryRef)s0, NULL);
287 XCTAssertEqual(status, errSecItemNotFound, "unexpectedly found s0 item in keychain");
288 status = SecItemCopyMatching((__bridge CFDictionaryRef)s1, NULL);
289 XCTAssertEqual(status, errSecItemNotFound, "unexpectedly found s1 item in keychain");
290 }
291
292 - (void)createManyKeys
293 {
294 unsigned n;
295 for (n = 0; n < 50; n++) {
296 NSDictionary* keyParams = @{
297 (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA,
298 (id)kSecAttrKeySizeInBits : @(1024)
299 };
300 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
301 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
302 (id)kSecValueRef : (__bridge id)key,
303 (id)kSecAttrLabel : [NSString stringWithFormat:@"TestLabel-%u", n],
304 (id)kSecUseDataProtectionKeychain : @(YES) };
305
306 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
307 XCTAssertEqual(result, 0, @"failed to add test key to keychain: %u", n);
308 }
309 }
310
311
312 - (void)testBackupRestoreItem
313 {
314 [self createManyItems];
315 [self createManyKeys];
316 [self createManyACLKeyItems];
317
318
319 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
320 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
321 (id)kSecAttrAccount : @"TestAccount",
322 (id)kSecAttrService : @"TestService",
323 (id)kSecUseDataProtectionKeychain : @(YES) };
324
325 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
326 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
327
328 NSMutableDictionary* dataQuery = item.mutableCopy;
329 [dataQuery removeObjectForKey:(id)kSecValueData];
330 dataQuery[(id)kSecReturnData] = @(YES);
331 CFTypeRef foundItem = NULL;
332
333 /*
334 * Create backup
335 */
336
337 CFDataRef keybag = CFDataCreate(kCFAllocatorDefault, NULL, 0);
338 CFDataRef password = CFDataCreate(kCFAllocatorDefault, NULL, 0);
339
340 CFDataRef backup = _SecKeychainCopyBackup(keybag, password);
341 XCTAssert(backup, "expected to have a backup");
342
343 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
344 XCTAssertEqual(result, 0, @"failed to delete item");
345
346 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
347 XCTAssertEqual(result, errSecItemNotFound,
348 @"failed to find the data for the item we just added in the keychain");
349 CFReleaseNull(foundItem);
350
351 /*
352 * Restore backup and see that item is resurected
353 */
354
355 XCTAssertEqual(0, _SecKeychainRestoreBackup(backup, keybag, password));
356
357 CFReleaseNull(backup);
358 CFReleaseNull(password);
359 CFReleaseNull(keybag);
360
361 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
362 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
363 CFReleaseNull(foundItem);
364
365 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
366 XCTAssertEqual(result, 0, @"failed to delete item");
367 }
368
369 - (void)testCreateSampleDatabase
370 {
371 // The keychain code only does the right thing with generation count if TARGET_HAS_KEYSTORE
372 #if TARGET_HAS_KEYSTORE
373 id mock = OCMClassMock([SecMockAKS class]);
374 OCMStub([mock useGenerationCount]).andReturn(true);
375 #endif
376
377 [self createManyItems];
378 [self createManyKeys];
379 [self createManyACLKeyItems];
380
381 /*
382 sleep(600);
383 lsof -p $(pgrep xctest)
384 sqlite3 database
385 .output mydatabase.h
386 .dump
387
388 add header and footer
389 */
390
391 [self findManyItems:50];
392
393 #if TARGET_HAS_KEYSTORE
394 [mock stopMocking];
395 #endif
396 }
397
398 - (void)testTestAKSGenerationCount
399 {
400 #if TARGET_HAS_KEYSTORE
401 id mock = OCMClassMock([SecMockAKS class]);
402 OCMStub([mock useGenerationCount]).andReturn(true);
403
404 [self createManyItems];
405 [self findManyItems:50];
406
407 [mock stopMocking];
408 #endif
409 }
410
411
412 - (void)loadDatabase:(const char **)dumpstring
413 {
414 const char *s;
415 unsigned n = 0;
416
417 // We need a fresh directory to plop down our SQLite data
418 SetCustomHomeURLString((__bridge CFStringRef)[self createKeychainDirectoryWithSubPath:@"loadManualDB"]);
419 NSString* path = CFBridgingRelease(__SecKeychainCopyPath());
420 // On macOS the full path gets created, on iOS not (yet?)
421 [self createKeychainDirectoryWithSubPath:@"loadManualDB/Library/Keychains"];
422
423 sqlite3 *handle = NULL;
424
425 XCTAssertEqual(SQLITE_OK, sqlite3_open([path UTF8String], &handle), "create keychain");
426
427 while ((s = dumpstring[n++]) != NULL) {
428 char * errmsg = NULL;
429 XCTAssertEqual(SQLITE_OK, sqlite3_exec(handle, s, NULL, NULL, &errmsg),
430 "exec: %s: %s", s, errmsg);
431 if (errmsg) {
432 sqlite3_free(errmsg);
433 }
434 }
435 XCTAssertEqual(SQLITE_OK, sqlite3_close(handle), "close sqlite");
436 }
437
438 - (void)checkIncremental
439 {
440 /*
441 * check that we made incremental vacuum mode
442 */
443
444 __block CFErrorRef localError = NULL;
445 __block bool ok = true;
446 __block int vacuumMode = -1;
447
448 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
449 ok &= SecDbPrepare(dbt, CFSTR("PRAGMA auto_vacuum"), &localError, ^(sqlite3_stmt *stmt) {
450 ok = SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
451 vacuumMode = sqlite3_column_int(stmt, 0);
452 });
453 });
454 return ok;
455 });
456 XCTAssertEqual(ok, true, "should work to fetch auto_vacuum value: %@", localError);
457 XCTAssertEqual(vacuumMode, 2, "vacuum mode should be incremental (2)");
458
459 CFReleaseNull(localError);
460
461 }
462
463 - (void)testUpgradeFromVersion10_5
464 {
465 SecKeychainDbReset(^{
466 NSLog(@"resetting database");
467 [self loadDatabase:secdmock_db_version10_5];
468 });
469
470 NSLog(@"find items from old database");
471 [self findManyItems:50];
472
473 [self checkIncremental];
474 }
475
476 - (void)testUpgradeFromVersion11_1
477 {
478 SecKeychainDbReset(^{
479 NSLog(@"resetting database");
480 [self loadDatabase:secdmock_db_version11_1];
481 });
482
483 NSLog(@"find items from old database");
484 [self findManyItems:50];
485
486 [self checkIncremental];
487 }
488
489 #if TARGET_OS_IOS
490
491 - (void)testPersonaBasic
492 {
493 SecSecuritySetPersonaMusr(NULL);
494
495 [self createManyItems];
496 [self findManyItems:10];
497
498 SecSecuritySetPersonaMusr(CFSTR("99C5D3CC-2C2D-47C4-9A1C-976EC047BF3C"));
499
500 [self findNoItems:10];
501 [self createManyItems];
502 [self findManyItems:10];
503
504 [self deleteAllItems];
505 [self findNoItems:10];
506
507 SecSecuritySetPersonaMusr(NULL);
508
509 [self findManyItems:10];
510 [self deleteAllItems];
511 [self findNoItems:10];
512
513 SecSecuritySetPersonaMusr(CFSTR("8E90C6BE-0AF6-40D6-8A4B-D46E4CF6A622"));
514
515 [self createManyItems];
516
517 SecSecuritySetPersonaMusr(CFSTR("38B615CA-02C2-4733-A71C-1ABA46D27B13"));
518
519 [self findNoItems:10];
520 [self createManyItems];
521
522 SecSecuritySetPersonaMusr(NULL);
523
524 XCTestExpectation *expection = [self expectationWithDescription:@"wait for deletion"];
525
526 _SecKeychainDeleteMultiUser(@"8E90C6BE-0AF6-40D6-8A4B-D46E4CF6A622", ^(bool status, NSError *error) {
527 [expection fulfill];
528 });
529
530 [self waitForExpectations:@[expection] timeout:10.0];
531
532 /* check that delete worked ... */
533 SecSecuritySetPersonaMusr(CFSTR("8E90C6BE-0AF6-40D6-8A4B-D46E4CF6A622"));
534 [self findNoItems:10];
535
536 /* ... but didn't delete another account */
537 SecSecuritySetPersonaMusr(CFSTR("38B615CA-02C2-4733-A71C-1ABA46D27B13"));
538 [self findManyItems:10];
539
540 SecSecuritySetPersonaMusr(NULL);
541
542
543 [self findNoItems:10];
544 }
545
546 #endif /* TARGET_OS_IOS */
547
548 #if USE_KEYSTORE
549
550 - (void)testAddKeyByReference
551 {
552 NSDictionary* keyParams = @{ (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA, (id)kSecAttrKeySizeInBits : @(1024) };
553 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
554 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
555 (id)kSecValueRef : (__bridge id)key,
556 (id)kSecAttrLabel : @"TestLabel",
557 (id)kSecUseDataProtectionKeychain : @(YES) };
558
559 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
560 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
561
562 NSMutableDictionary* refQuery = item.mutableCopy;
563 [refQuery removeObjectForKey:(id)kSecValueData];
564 refQuery[(id)kSecReturnRef] = @(YES);
565 CFTypeRef foundItem = NULL;
566 result = SecItemCopyMatching((__bridge CFDictionaryRef)refQuery, &foundItem);
567 XCTAssertEqual(result, 0, @"failed to find the reference for the item we just added in the keychain");
568
569 NSData* originalKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation(key, NULL);
570 NSData* foundKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation((SecKeyRef)foundItem, NULL);
571 XCTAssertEqualObjects(originalKeyData, foundKeyData, @"found key does not match the key we put in the keychain");
572
573 result = SecItemDelete((__bridge CFDictionaryRef)refQuery);
574 XCTAssertEqual(result, 0, @"failed to delete key");
575 }
576
577 - (bool)isLockedSoon:(keyclass_t)key_class
578 {
579 if (key_class == key_class_d || key_class == key_class_dku)
580 return false;
581 if (self.lockCounter <= 0)
582 return true;
583 self.lockCounter--;
584 return false;
585 }
586
587 /*
588 * Lock in the middle of migration
589 */
590 - (void)testUpgradeFromVersion10_5WhileLocked
591 {
592 OSStatus result = 0;
593 id mock = OCMClassMock([SecMockAKS class]);
594 [[[[mock stub] andCall:@selector(isLockedSoon:) onObject:self] ignoringNonObjectArgs] isLocked:0];
595
596 SecKeychainDbReset(^{
597 NSLog(@"resetting database");
598 [self loadDatabase:secdmock_db_version10_5];
599 });
600
601 self.lockCounter = 0;
602
603 NSDictionary* item = @{
604 (id)kSecClass : (id)kSecClassGenericPassword,
605 (id)kSecAttrAccount : @"TestAccount-11",
606 (id)kSecAttrService : @"TestService",
607 (id)kSecReturnData : @YES,
608 (id)kSecUseDataProtectionKeychain : @YES
609 };
610 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
611 XCTAssertEqual(result, errSecInteractionNotAllowed, @"SEP not locked?");
612
613 XCTAssertEqual(self.lockCounter, 0, "Device didn't lock");
614
615 NSLog(@"user unlock");
616 [mock stopMocking];
617
618 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
619 XCTAssertEqual(result, 0, @"can't find item");
620
621
622 NSLog(@"find items from old database");
623 [self findManyItems:50];
624 }
625
626
627 - (void)testUpgradeFromVersion10_5HungSEP
628 {
629 id mock = OCMClassMock([SecMockAKS class]);
630 OSStatus result = 0;
631
632 OCMStub([mock isSEPDown]).andReturn(true);
633
634 SecKeychainDbReset(^{
635 NSLog(@"resetting database");
636 [self loadDatabase:secdmock_db_version10_5];
637 });
638
639 NSDictionary* item = @{
640 (id)kSecClass : (id)kSecClassGenericPassword,
641 (id)kSecAttrAccount : @"TestAccount-0",
642 (id)kSecAttrService : @"TestService",
643 (id)kSecUseDataProtectionKeychain : @(YES)
644 };
645 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
646 XCTAssertEqual(result, errSecNotAvailable, @"SEP not down?");
647
648 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
649 CFErrorRef error = NULL;
650 int version = 0;
651 SecKeychainDbGetVersion(dbt, &version, &error);
652 XCTAssertEqual(error, NULL, "error getting version");
653 XCTAssertEqual(version, 0x50a, "managed to upgrade when we shouldn't have");
654
655 return true;
656 });
657
658 /* user got the SEP out of DFU */
659 NSLog(@"SEP alive");
660 [mock stopMocking];
661
662 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
663 XCTAssertEqual(result, 0, @"failed to find test item to keychain");
664
665 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
666 CFErrorRef error = NULL;
667 int version = 0;
668 SecKeychainDbGetVersion(dbt, &version, &error);
669 XCTAssertEqual(error, NULL, "error getting version");
670 const SecDbSchema *schema = current_schema();
671 int schemaVersion = (((schema)->minorVersion) << 8) | ((schema)->majorVersion);
672 XCTAssertEqual(version, schemaVersion, "didn't manage to upgrade");
673
674 return true;
675 });
676
677 NSLog(@"find items from old database");
678 [self findManyItems:50];
679 }
680
681 // We used to try and parse a CFTypeRef as a 32 bit int before trying 64, but that fails if keytype is weird.
682 - (void)testBindObjectIntParsing
683 {
684 NSMutableDictionary* query = [@{ (id)kSecClass : (id)kSecClassKey,
685 (id)kSecAttrCanEncrypt : @(YES),
686 (id)kSecAttrCanDecrypt : @(YES),
687 (id)kSecAttrCanDerive : @(NO),
688 (id)kSecAttrCanSign : @(NO),
689 (id)kSecAttrCanVerify : @(NO),
690 (id)kSecAttrCanWrap : @(NO),
691 (id)kSecAttrCanUnwrap : @(NO),
692 (id)kSecAttrKeySizeInBits : @(256),
693 (id)kSecAttrEffectiveKeySize : @(256),
694 (id)kSecValueData : [@"a" dataUsingEncoding:NSUTF8StringEncoding],
695 (id)kSecAttrKeyType : [NSNumber numberWithUnsignedInt:0x80000001L], // durr
696 } mutableCopy];
697
698 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess, "Succeeded in adding key to keychain");
699
700 query[(id)kSecValueData] = nil;
701 NSDictionary* update = @{(id)kSecValueData : [@"b" dataUsingEncoding:NSUTF8StringEncoding]};
702 XCTAssertEqual(SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update), errSecSuccess, "Successfully updated key in keychain");
703
704 CFTypeRef output = NULL;
705 query[(id)kSecReturnAttributes] = @(YES);
706 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, &output), errSecSuccess, "Found key in keychain");
707 XCTAssertNotNil((__bridge id)output, "got output from SICM");
708 XCTAssertEqual(CFGetTypeID(output), CFDictionaryGetTypeID(), "output is a dictionary");
709 XCTAssertEqual(CFDictionaryGetValue(output, (id)kSecAttrKeyType), (__bridge CFNumberRef)[NSNumber numberWithUnsignedInt:0x80000001L], "keytype is unchanged");
710 }
711
712 - (NSData *)objectToDER:(NSDictionary *)dict
713 {
714 CFPropertyListRef object = (__bridge CFPropertyListRef)dict;
715 CFErrorRef error = NULL;
716
717 size_t size = der_sizeof_plist(object, &error);
718 if (!size) {
719 return NULL;
720 }
721 NSMutableData *data = [NSMutableData dataWithLength:size];
722 uint8_t *der = [data mutableBytes];
723 uint8_t *der_end = der + size;
724 uint8_t *der_start = der_encode_plist(object, &error, der, der_end);
725 if (!der_start) {
726 return NULL;
727 }
728 return data;
729 }
730
731 #if !TARGET_OS_WATCH
732 /* this should be enabled for watch too, but that causes a crash in the mock aks layer */
733
734 - (void)testUpgradeWithBadACLKey
735 {
736 SecAccessControlRef ref = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, 0, NULL);
737 XCTAssertNotEqual(ref, NULL, @"SAC");
738
739 NSDictionary *canaryItem = @{
740 (__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassInternetPassword,
741 (__bridge NSString *)kSecAttrServer : @"server-here",
742 (__bridge NSString *)kSecAttrAccount : @"foo",
743 (__bridge NSString *)kSecAttrPort : @80,
744 (__bridge NSString *)kSecAttrProtocol : @"http",
745 (__bridge NSString *)kSecAttrAuthenticationType : @"dflt",
746 (__bridge NSString *)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
747 };
748
749 NSDictionary* canaryQuery = @{
750 (id)kSecClass : (id)kSecClassInternetPassword,
751 (__bridge NSString *)kSecAttrServer : @"server-here",
752 (__bridge NSString *)kSecAttrAccount : @"foo",
753 (__bridge NSString *)kSecAttrPort : @80,
754 (__bridge NSString *)kSecAttrProtocol : @"http",
755 (__bridge NSString *)kSecAttrAuthenticationType : @"dflt",
756 (id)kSecUseDataProtectionKeychain : @(YES),
757 };
758
759 NSDictionary* baseQuery = @{
760 (id)kSecClass : (id)kSecClassGenericPassword,
761 (id)kSecAttrAccount : @"TestAccount-0",
762 (id)kSecAttrService : @"TestService",
763 (id)kSecUseDataProtectionKeychain : @(YES),
764 };
765
766 NSMutableDictionary* query = [baseQuery mutableCopy];
767 [query addEntriesFromDictionary:@{
768 (id)kSecReturnAttributes: @YES,
769 (id)kSecReturnData: @YES,
770 }];
771
772 NSMutableDictionary* add = [baseQuery mutableCopy];
773 [add addEntriesFromDictionary:@{
774 (__bridge id)kSecValueData: [@"foo" dataUsingEncoding:NSUTF8StringEncoding],
775 (__bridge id)kSecAttrAccessControl: (__bridge id)ref,
776 }];
777
778 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)canaryItem, NULL), errSecSuccess, "should successfully add canaryItem");
779 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)canaryQuery, NULL), errSecSuccess, "should successfully get canaryItem");
780
781 __block NSUInteger counter = 0;
782 __block bool mutatedDatabase = true;
783 while (mutatedDatabase) {
784 mutatedDatabase = false;
785
786 if (counter == 0) {
787 /* corruption of version is not great if its > 7 */
788 counter++;
789 }
790
791 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
792 CFErrorRef localError = NULL;
793 (void)SecDbExec(dbt, CFSTR("DELETE FROM genp"), &localError);
794 CFReleaseNull(localError);
795 return true;
796 });
797 SecKeychainDbReset(NULL);
798
799 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound, "should successfully get item");
800 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)add, NULL), errSecSuccess, "should successfully add item");
801 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess, "should successfully get item");
802
803 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)canaryQuery, NULL), errSecSuccess,
804 "should successfully get canaryItem pre %d", (int)counter);
805
806 // lower database and destroy the item
807 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
808 CFErrorRef localError2 = NULL;
809 __block bool ok = true;
810 int version = 0;
811
812 SecKeychainDbGetVersion(dbt, &version, &localError2);
813 CFReleaseNull(localError2);
814
815 // force a minor (if more then zero), otherwise pick major
816 NSString *downgradeString = nil;
817 if ((version & (0xff00)) != 0) {
818 downgradeString = [NSString stringWithFormat:@"UPDATE tversion SET version='%d', minor='%d'",
819 (version & 0xff), 0];
820 } else {
821 downgradeString = [NSString stringWithFormat:@"UPDATE tversion SET version='%d', minor='%d'",
822 ((version & 0xff) - 1), 0];
823 }
824
825 ok = SecDbExec(dbt, (__bridge CFStringRef)downgradeString, &localError2);
826 XCTAssertTrue(ok, "downgrade should be successful: %@", localError2);
827 CFReleaseNull(localError2);
828
829 __block NSData *data = nil;
830 __block unsigned steps = 0;
831 ok &= SecDbPrepare(dbt, CFSTR("SELECT data FROM genp"), &localError2, ^(sqlite3_stmt *stmt) {
832 ok = SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
833 steps++;
834
835 void *ptr = (uint8_t *)sqlite3_column_blob(stmt, 0);
836 size_t length = sqlite3_column_bytes(stmt, 0);
837 XCTAssertNotEqual(ptr, NULL, "should have ptr");
838 XCTAssertNotEqual(length, 0, "should have data");
839 data = [NSData dataWithBytes:ptr length:length];
840 });
841 });
842 XCTAssertTrue(ok, "copy should be successful: %@", localError2);
843 XCTAssertEqual(steps, 1, "steps should be 1, since we should only have one genp");
844 XCTAssertNotNil(data, "should find the row");
845 CFReleaseNull(localError2);
846
847
848 NSMutableData *mutatedData = [data mutableCopy];
849
850 // This used to loop over all (~850!) bytes of the data but that's too slow. We now do first 50, last 50 and 20 random bytes
851 if (counter < 50) {
852 mutatedDatabase = true;
853 ((uint8_t *)[mutatedData mutableBytes])[counter] = 'X';
854 ++counter;
855 } else if (counter < 70) {
856 mutatedDatabase = true;
857 size_t idx = 50 + arc4random_uniform((uint32_t)(mutatedData.length - 100));
858 ((uint8_t *)[mutatedData mutableBytes])[idx] = 'X';
859 ++counter;
860 } else if (counter < 120) {
861 mutatedDatabase = true;
862 size_t idx = mutatedData.length - (counter - 70 - 1);
863 ((uint8_t *)[mutatedData mutableBytes])[idx] = 'X';
864 ++counter;
865 } else {
866 counter = 0;
867 }
868
869 NSString *mutateString = [NSString stringWithFormat:@"UPDATE genp SET data=x'%@'",
870 [mutatedData hexString]];
871
872 ok &= SecDbPrepare(dbt, (__bridge CFStringRef)mutateString, &localError2, ^(sqlite3_stmt *stmt) {
873 ok = SecDbStep(dbt, stmt, NULL, NULL);
874 });
875 XCTAssertTrue(ok, "corruption should be successful: %@", localError2);
876 CFReleaseNull(localError2);
877
878 return ok;
879 });
880
881 // force it to reload
882 SecKeychainDbReset(NULL);
883
884 //dont care about result, we might have done a good number on this item
885 (void)SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
886 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)canaryQuery, NULL), errSecSuccess,
887 "should successfully get canaryItem final %d", (int)counter);
888 }
889
890 NSLog(@"count: %lu", (unsigned long)counter);
891
892 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)canaryQuery, NULL), errSecSuccess,
893 "should successfully get canaryItem final %d", (int)counter);
894 }
895 #endif /* !TARGET_OS_WATCH */
896
897 - (void)testInteractionFailuresFromReferenceKeys
898 {
899 SecAccessControlRef ref = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlocked, 0, NULL);
900 XCTAssertNotEqual(ref, NULL, @"SAC");
901
902 NSDictionary* query = @{
903 (id)kSecClass : (id)kSecClassGenericPassword,
904 (id)kSecAttrAccount : @"TestAccount-0",
905 (id)kSecAttrService : @"TestService",
906 (id)kSecUseDataProtectionKeychain : @(YES),
907 (id)kSecReturnAttributes: @YES,
908 (id)kSecReturnData: @YES,
909 };
910
911 NSDictionary* add = @{
912 (id)kSecClass : (id)kSecClassGenericPassword,
913 (id)kSecAttrAccount : @"TestAccount-0",
914 (id)kSecAttrService : @"TestService",
915 (id)kSecUseDataProtectionKeychain : @(YES),
916 (id)kSecValueData: [@"foo" dataUsingEncoding:NSUTF8StringEncoding],
917 (id)kSecAttrAccessControl: (__bridge id)ref,
918 };
919
920
921 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecItemNotFound, "should successfully get item");
922 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)add, NULL), errSecSuccess, "should successfully add item");
923 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess, "should successfully get item");
924
925 [SecMockAKS failNextDecryptRefKey:[NSError errorWithDomain:@"foo" code:kAKSReturnNoPermission userInfo:nil]];
926 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecInteractionNotAllowed, "should successfully get item");
927
928 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess, "should successfully get item");
929 }
930
931 // This test fails before rdar://problem/60028419 because the keystore fails to check for errSecInteractionNotAllowed,
932 // tries to recreate the "broken" metadata key and if the keychain unlocks at the exact right moment succeeds and causes
933 // data loss.
934 - (void)testMetadataKeyRaceConditionFixed
935 {
936 NSMutableDictionary* query = [@{
937 (id)kSecClass : (id)kSecClassGenericPassword,
938 (id)kSecAttrAccount : @"TestAccount-0",
939 (id)kSecAttrService : @"TestService",
940 (id)kSecValueData : [@"data" dataUsingEncoding:NSUTF8StringEncoding],
941 (id)kSecUseDataProtectionKeychain : @(YES),
942 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
943 } mutableCopy];
944
945 // Create item 1
946 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
947
948 // Drop metadata keys
949 SecKeychainDbReset(NULL);
950 [SecMockAKS setOperationsUntilUnlock:1]; // The first call is the metadata key unwrap to allow encrypting the item
951 [SecMockAKS lockClassA];
952
953 query[(id)kSecAttrAccount] = @"TestAcount-1";
954 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecInteractionNotAllowed);
955
956 query[(id)kSecAttrAccount] = @"TestAccount-0";
957 query[(id)kSecValueData] = nil;
958 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL), errSecSuccess);
959
960 [SecMockAKS setOperationsUntilUnlock:-1];
961 }
962
963 // A test for if LAContext can reasonably be used
964 // Note that this test can currently _only_ run on iOS in the simulator; on the actual platforms it tries to access
965 // real AKS/biometrics, which fails in automation environments.
966 #if TARGET_OS_OSX || TARGET_OS_SIMULATOR
967 - (void)testLAContext
968 {
969 NSMutableDictionary* simplequery = [@{
970 (id)kSecClass : (id)kSecClassGenericPassword,
971 (id)kSecAttrAccount : @"TestAccount-0",
972 (id)kSecAttrService : @"TestService",
973 (id)kSecValueData : [@"data" dataUsingEncoding:NSUTF8StringEncoding],
974 (id)kSecUseDataProtectionKeychain : @(YES),
975 (id)kSecAttrAccessible : (id)kSecAttrAccessibleWhenUnlocked,
976 } mutableCopy];
977 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)simplequery, NULL), errSecSuccess, "Succeeded in adding simple item to keychain");
978
979 CFErrorRef cferror = NULL;
980 SecAccessControlRef access = SecAccessControlCreateWithFlags(nil,
981 kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
982 kSecAccessControlUserPresence,
983 &cferror);
984
985 XCTAssertNil((__bridge NSError*)cferror, "Should be no error creating an access control");
986
987 LAContext* context = [[LAContext alloc] init];
988
989 #if TARGET_OS_IPHONE || TARGET_OS_OSX
990 // This field is only usable on iPhone/macOS. It isn't stricly necessary for this test, though.
991 context.touchIDAuthenticationAllowableReuseDuration = 10;
992 #endif
993
994 NSMutableDictionary* query = [@{
995 (id)kSecClass : (id)kSecClassGenericPassword,
996 (id)kSecAttrAccount : @"TestAccount-LAContext",
997 (id)kSecAttrService : @"TestService",
998 (id)kSecValueData : [@"data" dataUsingEncoding:NSUTF8StringEncoding],
999 (id)kSecUseDataProtectionKeychain : @(YES),
1000 (id)kSecAttrAccessControl : (__bridge id)access,
1001 (id)kSecUseAuthenticationContext : context,
1002 } mutableCopy];
1003
1004 XCTAssertEqual(SecItemAdd((__bridge CFDictionaryRef)query, NULL), errSecSuccess, "Succeeded in adding LA-protected item to keychain");
1005
1006 NSMutableDictionary* findquery = [@{
1007 (id)kSecClass : (id)kSecClassGenericPassword,
1008 (id)kSecAttrAccount : @"TestAccount-LAContext",
1009 (id)kSecAttrService : @"TestService",
1010 (id)kSecUseDataProtectionKeychain : @(YES),
1011 (id)kSecUseAuthenticationContext : context,
1012 (id)kSecReturnAttributes: @YES,
1013 (id)kSecReturnData: @YES,
1014 } mutableCopy];
1015
1016 CFTypeRef output = NULL;
1017 XCTAssertEqual(SecItemCopyMatching((__bridge CFDictionaryRef)findquery, &output), errSecSuccess, "Found key in keychain");
1018
1019 XCTAssertNotNil((__bridge id)output, "Should have received something back from keychain");
1020 CFReleaseNull(output);
1021 }
1022 #endif // TARGET_OS_OSX || TARGET_OS_SIMULATOR
1023
1024 #endif /* USE_KEYSTORE */
1025
1026 @end