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