2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "SecKeybagSupport.h"
25 #import "SecDbKeychainItem.h"
26 #import "SecdTestKeychainUtilities.h"
28 #import "SecItemPriv.h"
29 #import "SecItemServer.h"
31 #import <utilities/SecCFWrappers.h>
32 #import <utilities/SecFileLocations.h>
33 #import <SecurityFoundation/SFEncryptionOperation.h>
34 #import <XCTest/XCTest.h>
35 #import <OCMock/OCMock.h>
42 #import "secdmock_db_version_10_5.h"
43 #import "secdmock_db_version_11_1.h"
45 @interface secdmockaks : XCTestCase
46 @property NSString *testHomeDirectory;
47 @property long lockCounter;
50 @implementation secdmockaks
58 * Disable all of SOS syncing since that triggers retains of database
59 * and these tests muck around with the database over and over again, so
60 * that leads to the vnode delete kevent trap triggering for sqlite
61 * over and over again.
64 SecCKKSTestSetDisableSOS(true);
66 //securityd_init(NULL);
69 - (void)createKeychainDirectory
71 [[NSFileManager defaultManager] createDirectoryAtPath:[NSString stringWithFormat: @"%@/Library/Keychains", self.testHomeDirectory] withIntermediateDirectories:YES attributes:nil error:NULL];
74 - (void)removeHomeDirectory
76 if (self.testHomeDirectory) {
77 [[NSFileManager defaultManager] removeItemAtPath:self.testHomeDirectory error:NULL];
84 NSString* testName = [self.name componentsSeparatedByString:@" "][1];
85 testName = [testName stringByReplacingOccurrencesOfString:@"]" withString:@""];
86 secnotice("ckkstest", "Beginning test %@", testName);
88 // Make a new fake keychain
89 self.testHomeDirectory = [NSString stringWithFormat: @"/tmp/%@.%X", testName, arc4random()];
90 [self createKeychainDirectory];
92 SetCustomHomeURLString((__bridge CFStringRef) self.testHomeDirectory);
93 static dispatch_once_t onceToken;
94 dispatch_once(&onceToken, ^{
97 SecKeychainDbReset(NULL);
99 // Actually load the database.
100 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; });
105 SetCustomHomeURLString(NULL);
106 SecKeychainDbReset(^{
107 [self removeHomeDirectory];
108 self.testHomeDirectory = nil;
110 //kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; });
114 - (void)testAddDeleteItem
116 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
117 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
118 (id)kSecAttrAccount : @"TestAccount",
119 (id)kSecAttrService : @"TestService",
120 (id)kSecAttrNoLegacy : @(YES) };
122 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
123 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
125 NSMutableDictionary* dataQuery = item.mutableCopy;
126 [dataQuery removeObjectForKey:(id)kSecValueData];
127 dataQuery[(id)kSecReturnData] = @(YES);
128 CFTypeRef foundItem = NULL;
129 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
130 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
132 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
133 XCTAssertEqual(result, 0, @"failed to delete item");
137 - (void)createManyItems
140 for (n = 0; n < 50; n++) {
141 NSDictionary* item = @{
142 (id)kSecClass : (id)kSecClassGenericPassword,
143 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
144 (id)kSecAttrAccount : [NSString stringWithFormat:@"TestAccount-%u", n],
145 (id)kSecAttrService : @"TestService",
146 (id)kSecAttrNoLegacy : @(YES)
148 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
149 XCTAssertEqual(result, 0, @"failed to add test item to keychain: %u", n);
153 - (void)findManyItems:(unsigned)searchLimit
156 for (n = 0; n < searchLimit; n++) {
157 NSDictionary* item = @{
158 (id)kSecClass : (id)kSecClassGenericPassword,
159 (id)kSecAttrAccount : [NSString stringWithFormat:@"TestAccount-%u", n],
160 (id)kSecAttrService : @"TestService",
161 (id)kSecAttrNoLegacy : @(YES)
163 OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
164 XCTAssertEqual(result, 0, @"failed to find test item to keychain: %u", n);
168 - (void)createManyKeys
171 for (n = 0; n < 50; n++) {
172 NSDictionary* keyParams = @{
173 (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA,
174 (id)kSecAttrKeySizeInBits : @(1024)
176 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
177 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
178 (id)kSecValueRef : (__bridge id)key,
179 (id)kSecAttrLabel : [NSString stringWithFormat:@"TestLabel-%u", n],
180 (id)kSecAttrNoLegacy : @(YES) };
182 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
183 XCTAssertEqual(result, 0, @"failed to add test key to keychain: %u", n);
188 - (void)testBackupRestoreItem
190 [self createManyItems];
191 [self createManyKeys];
194 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
195 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
196 (id)kSecAttrAccount : @"TestAccount",
197 (id)kSecAttrService : @"TestService",
198 (id)kSecAttrNoLegacy : @(YES) };
200 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
201 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
203 NSMutableDictionary* dataQuery = item.mutableCopy;
204 [dataQuery removeObjectForKey:(id)kSecValueData];
205 dataQuery[(id)kSecReturnData] = @(YES);
206 CFTypeRef foundItem = NULL;
212 CFDataRef keybag = CFDataCreate(kCFAllocatorDefault, NULL, 0);
213 CFDataRef password = CFDataCreate(kCFAllocatorDefault, NULL, 0);
215 CFDataRef backup = _SecKeychainCopyBackup(keybag, password);
216 XCTAssert(backup, "expected to have a backup");
218 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
219 XCTAssertEqual(result, 0, @"failed to delete item");
221 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
222 XCTAssertEqual(result, errSecItemNotFound,
223 @"failed to find the data for the item we just added in the keychain");
224 CFReleaseNull(foundItem);
227 * Restore backup and see that item is resurected
230 XCTAssertEqual(0, _SecKeychainRestoreBackup(backup, keybag, password));
232 CFReleaseNull(backup);
233 CFReleaseNull(password);
234 CFReleaseNull(keybag);
236 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
237 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
238 CFReleaseNull(foundItem);
240 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
241 XCTAssertEqual(result, 0, @"failed to delete item");
244 - (void)testCreateSampleDatabase
247 id mock = OCMClassMock([SecMockAKS class]);
248 OCMStub([mock useGenerationCount]).andReturn(true);
251 [self createManyItems];
252 [self createManyKeys];
256 lsof -p $(pgrep xctest)
261 add header and footer
264 [self findManyItems:50];
267 - (void)testTestAKSGenerationCount
270 id mock = OCMClassMock([SecMockAKS class]);
271 OCMStub([mock useGenerationCount]).andReturn(true);
273 [self createManyItems];
274 [self findManyItems:50];
279 - (void)loadDatabase:(const char **)dumpstring
284 [self removeHomeDirectory];
285 [self createKeychainDirectory];
287 NSString *path = CFBridgingRelease(__SecKeychainCopyPath());
288 sqlite3 *handle = NULL;
290 XCTAssertEqual(SQLITE_OK, sqlite3_open([path UTF8String], &handle), "create keychain");
292 while ((s = dumpstring[n++]) != NULL) {
293 char * errmsg = NULL;
294 XCTAssertEqual(SQLITE_OK, sqlite3_exec(handle, s, NULL, NULL, &errmsg),
295 "exec: %s: %s", s, errmsg);
297 sqlite3_free(errmsg);
300 XCTAssertEqual(SQLITE_OK, sqlite3_close(handle), "close sqlite");
303 - (void)checkIncremental
306 * check that we made incremental vacuum mode
309 __block CFErrorRef localError = NULL;
310 __block bool ok = true;
311 __block int vacuumMode = -1;
313 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
314 ok &= SecDbPrepare(dbt, CFSTR("PRAGMA auto_vacuum"), &localError, ^(sqlite3_stmt *stmt) {
315 ok = SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
316 vacuumMode = sqlite3_column_int(stmt, 0);
321 XCTAssertEqual(ok, true, "should work to fetch auto_vacuum value: %@", localError);
322 XCTAssertEqual(vacuumMode, 2, "vacuum mode should be incremental (2)");
324 CFReleaseNull(localError);
328 - (void)testUpgradeFromVersion10_5
330 SecKeychainDbReset(^{
331 NSLog(@"resetting database");
332 [self loadDatabase:secdmock_db_version10_5];
335 NSLog(@"find items from old database");
336 [self findManyItems:50];
338 [self checkIncremental];
341 - (void)testUpgradeFromVersion11_1
343 SecKeychainDbReset(^{
344 NSLog(@"resetting database");
345 [self loadDatabase:secdmock_db_version11_1];
348 NSLog(@"find items from old database");
349 [self findManyItems:50];
351 [self checkIncremental];
356 - (void)testAddKeyByReference
358 NSDictionary* keyParams = @{ (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA, (id)kSecAttrKeySizeInBits : @(1024) };
359 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
360 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
361 (id)kSecValueRef : (__bridge id)key,
362 (id)kSecAttrLabel : @"TestLabel",
363 (id)kSecAttrNoLegacy : @(YES) };
365 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
366 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
368 NSMutableDictionary* refQuery = item.mutableCopy;
369 [refQuery removeObjectForKey:(id)kSecValueData];
370 refQuery[(id)kSecReturnRef] = @(YES);
371 CFTypeRef foundItem = NULL;
372 result = SecItemCopyMatching((__bridge CFDictionaryRef)refQuery, &foundItem);
373 XCTAssertEqual(result, 0, @"failed to find the reference for the item we just added in the keychain");
375 NSData* originalKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation(key, NULL);
376 NSData* foundKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation((SecKeyRef)foundItem, NULL);
377 XCTAssertEqualObjects(originalKeyData, foundKeyData, @"found key does not match the key we put in the keychain");
379 result = SecItemDelete((__bridge CFDictionaryRef)refQuery);
380 XCTAssertEqual(result, 0, @"failed to delete key");
383 - (bool)isLockedSoon:(keyclass_t)key_class
385 if (key_class == key_class_d || key_class == key_class_dku)
387 if (self.lockCounter <= 0)
394 * Lock in the middle of migration
396 - (void)testUpgradeFromVersion10_5WhileLocked
399 id mock = OCMClassMock([SecMockAKS class]);
400 [[[[mock stub] andCall:@selector(isLockedSoon:) onObject:self] ignoringNonObjectArgs] isLocked:0];
402 SecKeychainDbReset(^{
403 NSLog(@"resetting database");
404 [self loadDatabase:secdmock_db_version10_5];
407 self.lockCounter = 0;
409 NSDictionary* item = @{
410 (id)kSecClass : (id)kSecClassGenericPassword,
411 (id)kSecAttrAccount : @"TestAccount-11",
412 (id)kSecAttrService : @"TestService",
413 (id)kSecReturnData : @YES,
414 (id)kSecAttrNoLegacy : @YES
416 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
417 XCTAssertEqual(result, errSecInteractionNotAllowed, @"SEP not locked?");
419 XCTAssertEqual(self.lockCounter, 0, "Device didn't lock");
421 NSLog(@"user unlock");
425 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
426 XCTAssertEqual(result, 0, @"can't find item");
429 NSLog(@"find items from old database");
430 [self findManyItems:50];
434 - (void)testUpgradeFromVersion10_5HungSEP
436 id mock = OCMClassMock([SecMockAKS class]);
439 OCMStub([mock isSEPDown]).andReturn(true);
441 SecKeychainDbReset(^{
442 NSLog(@"resetting database");
443 [self loadDatabase:secdmock_db_version10_5];
446 NSDictionary* item = @{
447 (id)kSecClass : (id)kSecClassGenericPassword,
448 (id)kSecAttrAccount : @"TestAccount-0",
449 (id)kSecAttrService : @"TestService",
450 (id)kSecAttrNoLegacy : @(YES)
452 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
453 XCTAssertEqual(result, errSecNotAvailable, @"SEP not down?");
455 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
456 CFErrorRef error = NULL;
458 SecKeychainDbGetVersion(dbt, &version, &error);
459 XCTAssertEqual(error, NULL, "error getting version");
460 XCTAssertEqual(version, 0x50a, "managed to upgrade when we shouldn't have");
465 /* user got the SEP out of DFU */
469 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
470 XCTAssertEqual(result, 0, @"failed to find test item to keychain");
472 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
473 CFErrorRef error = NULL;
475 SecKeychainDbGetVersion(dbt, &version, &error);
476 XCTAssertEqual(error, NULL, "error getting version");
477 XCTAssertEqual(version, 0x40b, "didnt managed to upgrade");
482 NSLog(@"find items from old database");
483 [self findManyItems:50];
486 #endif /* USE_KEYSTORE */