]> git.saurik.com Git - apple/security.git/blob - tests/secdmockaks/secdmockaks.m
Security-58286.270.3.0.1.tar.gz
[apple/security.git] / tests / secdmockaks / secdmockaks.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 "spi.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>
36 #if USE_KEYSTORE
37 #import <libaks.h>
38 #endif
39 #import <sqlite3.h>
40 #import "mockaks.h"
41
42 #import "secdmock_db_version_10_5.h"
43 #import "secdmock_db_version_11_1.h"
44
45 @interface secdmockaks : XCTestCase
46 @property NSString *testHomeDirectory;
47 @property long lockCounter;
48 @end
49
50 @implementation secdmockaks
51
52 + (void)setUp
53 {
54 [super setUp];
55
56 SecCKKSDisable();
57 /*
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.
62 */
63 #if OCTAGON
64 SecCKKSTestSetDisableSOS(true);
65 #endif
66 //securityd_init(NULL);
67 }
68
69 - (void)createKeychainDirectory
70 {
71 [[NSFileManager defaultManager] createDirectoryAtPath:[NSString stringWithFormat: @"%@/Library/Keychains", self.testHomeDirectory] withIntermediateDirectories:YES attributes:nil error:NULL];
72 }
73
74 - (void)removeHomeDirectory
75 {
76 if (self.testHomeDirectory) {
77 [[NSFileManager defaultManager] removeItemAtPath:self.testHomeDirectory error:NULL];
78 }
79 }
80
81 - (void)setUp {
82 [super setUp];
83
84 NSString* testName = [self.name componentsSeparatedByString:@" "][1];
85 testName = [testName stringByReplacingOccurrencesOfString:@"]" withString:@""];
86 secnotice("ckkstest", "Beginning test %@", testName);
87
88 // Make a new fake keychain
89 self.testHomeDirectory = [NSString stringWithFormat: @"/tmp/%@.%X", testName, arc4random()];
90 [self createKeychainDirectory];
91
92 SetCustomHomeURLString((__bridge CFStringRef) self.testHomeDirectory);
93 static dispatch_once_t onceToken;
94 dispatch_once(&onceToken, ^{
95 securityd_init(NULL);
96 });
97 SecKeychainDbReset(NULL);
98
99 // Actually load the database.
100 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; });
101 }
102
103 - (void)tearDown
104 {
105 SetCustomHomeURLString(NULL);
106 SecKeychainDbReset(^{
107 [self removeHomeDirectory];
108 self.testHomeDirectory = nil;
109 });
110 //kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) { return false; });
111 }
112
113
114 - (void)testAddDeleteItem
115 {
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) };
121
122 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
123 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
124
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");
131
132 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
133 XCTAssertEqual(result, 0, @"failed to delete item");
134 }
135
136
137 - (void)createManyItems
138 {
139 unsigned n;
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)
147 };
148 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
149 XCTAssertEqual(result, errSecSuccess, @"failed to add test item to keychain: %u", n);
150 }
151 }
152
153 - (void)findManyItems:(unsigned)searchLimit
154 {
155 unsigned n;
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)
162 };
163 OSStatus result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
164 XCTAssertEqual(result, errSecSuccess, @"failed to find test item to keychain: %u", n);
165 }
166 }
167
168 - (void)testSecItemServerDeleteAll
169 {
170 // BT root key, should not be deleted
171 NSMutableDictionary* bt = [@{
172 (id)kSecClass : (id)kSecClassGenericPassword,
173 (id)kSecAttrAccessGroup : @"com.apple.bluetooth",
174 (id)kSecAttrService : @"BluetoothGlobal",
175 (id)kSecAttrAccessible : (id)kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate,
176 (id)kSecAttrSynchronizable : @(NO),
177 (id)kSecValueData : [@"btkey" dataUsingEncoding:NSUTF8StringEncoding],
178 } mutableCopy];
179
180 // lockdown-identities, should not be deleted
181 NSMutableDictionary* ld = [@{
182 (id)kSecClass : (id)kSecClassKey,
183 (id)kSecAttrAccessGroup : @"lockdown-identities",
184 (id)kSecAttrLabel : @"com.apple.lockdown.identity.activation",
185 (id)kSecAttrAccessible : (id)kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate,
186 (id)kSecAttrSynchronizable : @(NO),
187 (id)kSecValueData : [@"ldkey" dataUsingEncoding:NSUTF8StringEncoding],
188 } mutableCopy];
189
190 // general nonsyncable item, should be deleted
191 NSMutableDictionary* s0 = [@{
192 (id)kSecClass : (id)kSecClassGenericPassword,
193 (id)kSecAttrService : @"NonsyncableService",
194 (id)kSecAttrSynchronizable : @(NO),
195 (id)kSecValueData : [@"s0pwd" dataUsingEncoding:NSUTF8StringEncoding],
196 } mutableCopy];
197
198 // general syncable item, should be deleted
199 NSMutableDictionary* s1 = [@{
200 (id)kSecClass : (id)kSecClassGenericPassword,
201 (id)kSecAttrService : @"SyncableService",
202 (id)kSecAttrSynchronizable : @(YES),
203 (id)kSecValueData : [@"s0pwd" dataUsingEncoding:NSUTF8StringEncoding],
204 } mutableCopy];
205
206 // Insert all items
207 OSStatus status;
208 status = SecItemAdd((__bridge CFDictionaryRef)bt, NULL);
209 XCTAssertEqual(status, errSecSuccess, "failed to add bt item to keychain");
210 status = SecItemAdd((__bridge CFDictionaryRef)ld, NULL);
211 XCTAssertEqual(status, errSecSuccess, "failed to add ld item to keychain");
212 status = SecItemAdd((__bridge CFDictionaryRef)s0, NULL);
213 XCTAssertEqual(status, errSecSuccess, "failed to add s0 item to keychain");
214 status = SecItemAdd((__bridge CFDictionaryRef)s1, NULL);
215 XCTAssertEqual(status, errSecSuccess, "failed to add s1 item to keychain");
216
217 // Make sure they exist now
218 bt[(id)kSecValueData] = nil;
219 ld[(id)kSecValueData] = nil;
220 s0[(id)kSecValueData] = nil;
221 s1[(id)kSecValueData] = nil;
222 status = SecItemCopyMatching((__bridge CFDictionaryRef)bt, NULL);
223 XCTAssertEqual(status, errSecSuccess, "failed to find bt item in keychain");
224 status = SecItemCopyMatching((__bridge CFDictionaryRef)ld, NULL);
225 XCTAssertEqual(status, errSecSuccess, "failed to find ld item in keychain");
226 status = SecItemCopyMatching((__bridge CFDictionaryRef)s0, NULL);
227 XCTAssertEqual(status, errSecSuccess, "failed to find s0 item in keychain");
228 status = SecItemCopyMatching((__bridge CFDictionaryRef)s1, NULL);
229 XCTAssertEqual(status, errSecSuccess, "failed to find s1 item in keychain");
230
231 // Nuke the keychain
232 CFErrorRef error = NULL;
233 _SecItemDeleteAll(&error);
234 XCTAssertEqual(error, NULL, "_SecItemDeleteAll returned an error: %@", error);
235 CFReleaseNull(error);
236
237 // Does the function work properly with an error pre-set?
238 error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, errSecItemNotFound, NULL);
239 _SecItemDeleteAll(&error);
240 XCTAssertEqual(CFErrorGetDomain(error), kCFErrorDomainOSStatus);
241 XCTAssertEqual(CFErrorGetCode(error), errSecItemNotFound);
242 CFReleaseNull(error);
243
244 // Check the relevant items are missing
245 status = SecItemCopyMatching((__bridge CFDictionaryRef)bt, NULL);
246 XCTAssertEqual(status, errSecSuccess, "failed to find bt item in keychain");
247 status = SecItemCopyMatching((__bridge CFDictionaryRef)ld, NULL);
248 XCTAssertEqual(status, errSecSuccess, "failed to find ld item in keychain");
249 status = SecItemCopyMatching((__bridge CFDictionaryRef)s0, NULL);
250 XCTAssertEqual(status, errSecItemNotFound, "unexpectedly found s0 item in keychain");
251 status = SecItemCopyMatching((__bridge CFDictionaryRef)s1, NULL);
252 XCTAssertEqual(status, errSecItemNotFound, "unexpectedly found s1 item in keychain");
253 }
254
255 - (void)createManyKeys
256 {
257 unsigned n;
258 for (n = 0; n < 50; n++) {
259 NSDictionary* keyParams = @{
260 (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA,
261 (id)kSecAttrKeySizeInBits : @(1024)
262 };
263 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
264 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
265 (id)kSecValueRef : (__bridge id)key,
266 (id)kSecAttrLabel : [NSString stringWithFormat:@"TestLabel-%u", n],
267 (id)kSecAttrNoLegacy : @(YES) };
268
269 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
270 XCTAssertEqual(result, 0, @"failed to add test key to keychain: %u", n);
271 }
272 }
273
274
275 - (void)testBackupRestoreItem
276 {
277 [self createManyItems];
278 [self createManyKeys];
279
280
281 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassGenericPassword,
282 (id)kSecValueData : [@"password" dataUsingEncoding:NSUTF8StringEncoding],
283 (id)kSecAttrAccount : @"TestAccount",
284 (id)kSecAttrService : @"TestService",
285 (id)kSecAttrNoLegacy : @(YES) };
286
287 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
288 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
289
290 NSMutableDictionary* dataQuery = item.mutableCopy;
291 [dataQuery removeObjectForKey:(id)kSecValueData];
292 dataQuery[(id)kSecReturnData] = @(YES);
293 CFTypeRef foundItem = NULL;
294
295 /*
296 * Create backup
297 */
298
299 CFDataRef keybag = CFDataCreate(kCFAllocatorDefault, NULL, 0);
300 CFDataRef password = CFDataCreate(kCFAllocatorDefault, NULL, 0);
301
302 CFDataRef backup = _SecKeychainCopyBackup(keybag, password);
303 XCTAssert(backup, "expected to have a backup");
304
305 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
306 XCTAssertEqual(result, 0, @"failed to delete item");
307
308 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
309 XCTAssertEqual(result, errSecItemNotFound,
310 @"failed to find the data for the item we just added in the keychain");
311 CFReleaseNull(foundItem);
312
313 /*
314 * Restore backup and see that item is resurected
315 */
316
317 XCTAssertEqual(0, _SecKeychainRestoreBackup(backup, keybag, password));
318
319 CFReleaseNull(backup);
320 CFReleaseNull(password);
321 CFReleaseNull(keybag);
322
323 result = SecItemCopyMatching((__bridge CFDictionaryRef)dataQuery, &foundItem);
324 XCTAssertEqual(result, 0, @"failed to find the data for the item we just added in the keychain");
325 CFReleaseNull(foundItem);
326
327 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
328 XCTAssertEqual(result, 0, @"failed to delete item");
329 }
330
331 - (void)testCreateSampleDatabase
332 {
333 #if USE_KEYSTORE
334 id mock = OCMClassMock([SecMockAKS class]);
335 OCMStub([mock useGenerationCount]).andReturn(true);
336 #endif
337
338 [self createManyItems];
339 [self createManyKeys];
340
341 /*
342 sleep(600);
343 lsof -p $(pgrep xctest)
344 sqlite3 database
345 .output mydatabase.h
346 .dump
347
348 add header and footer
349 */
350
351 [self findManyItems:50];
352 }
353
354 - (void)testTestAKSGenerationCount
355 {
356 #if USE_KEYSTORE
357 id mock = OCMClassMock([SecMockAKS class]);
358 OCMStub([mock useGenerationCount]).andReturn(true);
359
360 [self createManyItems];
361 [self findManyItems:50];
362 #endif
363 }
364
365
366 - (void)loadDatabase:(const char **)dumpstring
367 {
368 const char *s;
369 unsigned n = 0;
370
371 [self removeHomeDirectory];
372 [self createKeychainDirectory];
373
374 NSString *path = CFBridgingRelease(__SecKeychainCopyPath());
375 sqlite3 *handle = NULL;
376
377 XCTAssertEqual(SQLITE_OK, sqlite3_open([path UTF8String], &handle), "create keychain");
378
379 while ((s = dumpstring[n++]) != NULL) {
380 char * errmsg = NULL;
381 XCTAssertEqual(SQLITE_OK, sqlite3_exec(handle, s, NULL, NULL, &errmsg),
382 "exec: %s: %s", s, errmsg);
383 if (errmsg) {
384 sqlite3_free(errmsg);
385 }
386 }
387 XCTAssertEqual(SQLITE_OK, sqlite3_close(handle), "close sqlite");
388 }
389
390 - (void)checkIncremental
391 {
392 /*
393 * check that we made incremental vacuum mode
394 */
395
396 __block CFErrorRef localError = NULL;
397 __block bool ok = true;
398 __block int vacuumMode = -1;
399
400 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
401 ok &= SecDbPrepare(dbt, CFSTR("PRAGMA auto_vacuum"), &localError, ^(sqlite3_stmt *stmt) {
402 ok = SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
403 vacuumMode = sqlite3_column_int(stmt, 0);
404 });
405 });
406 return ok;
407 });
408 XCTAssertEqual(ok, true, "should work to fetch auto_vacuum value: %@", localError);
409 XCTAssertEqual(vacuumMode, 2, "vacuum mode should be incremental (2)");
410
411 CFReleaseNull(localError);
412
413 }
414
415 - (void)testUpgradeFromVersion10_5
416 {
417 SecKeychainDbReset(^{
418 NSLog(@"resetting database");
419 [self loadDatabase:secdmock_db_version10_5];
420 });
421
422 NSLog(@"find items from old database");
423 [self findManyItems:50];
424
425 [self checkIncremental];
426 }
427
428 - (void)testUpgradeFromVersion11_1
429 {
430 SecKeychainDbReset(^{
431 NSLog(@"resetting database");
432 [self loadDatabase:secdmock_db_version11_1];
433 });
434
435 NSLog(@"find items from old database");
436 [self findManyItems:50];
437
438 [self checkIncremental];
439 }
440
441 #if USE_KEYSTORE
442
443 - (void)testAddKeyByReference
444 {
445 NSDictionary* keyParams = @{ (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA, (id)kSecAttrKeySizeInBits : @(1024) };
446 SecKeyRef key = SecKeyCreateRandomKey((__bridge CFDictionaryRef)keyParams, NULL);
447 NSDictionary* item = @{ (id)kSecClass : (id)kSecClassKey,
448 (id)kSecValueRef : (__bridge id)key,
449 (id)kSecAttrLabel : @"TestLabel",
450 (id)kSecAttrNoLegacy : @(YES) };
451
452 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
453 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
454
455 NSMutableDictionary* refQuery = item.mutableCopy;
456 [refQuery removeObjectForKey:(id)kSecValueData];
457 refQuery[(id)kSecReturnRef] = @(YES);
458 CFTypeRef foundItem = NULL;
459 result = SecItemCopyMatching((__bridge CFDictionaryRef)refQuery, &foundItem);
460 XCTAssertEqual(result, 0, @"failed to find the reference for the item we just added in the keychain");
461
462 NSData* originalKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation(key, NULL);
463 NSData* foundKeyData = (__bridge_transfer NSData*)SecKeyCopyExternalRepresentation((SecKeyRef)foundItem, NULL);
464 XCTAssertEqualObjects(originalKeyData, foundKeyData, @"found key does not match the key we put in the keychain");
465
466 result = SecItemDelete((__bridge CFDictionaryRef)refQuery);
467 XCTAssertEqual(result, 0, @"failed to delete key");
468 }
469
470 - (bool)isLockedSoon:(keyclass_t)key_class
471 {
472 if (key_class == key_class_d || key_class == key_class_dku)
473 return false;
474 if (self.lockCounter <= 0)
475 return true;
476 self.lockCounter--;
477 return false;
478 }
479
480 /*
481 * Lock in the middle of migration
482 */
483 - (void)testUpgradeFromVersion10_5WhileLocked
484 {
485 OSStatus result = 0;
486 id mock = OCMClassMock([SecMockAKS class]);
487 [[[[mock stub] andCall:@selector(isLockedSoon:) onObject:self] ignoringNonObjectArgs] isLocked:0];
488
489 SecKeychainDbReset(^{
490 NSLog(@"resetting database");
491 [self loadDatabase:secdmock_db_version10_5];
492 });
493
494 self.lockCounter = 0;
495
496 NSDictionary* item = @{
497 (id)kSecClass : (id)kSecClassGenericPassword,
498 (id)kSecAttrAccount : @"TestAccount-11",
499 (id)kSecAttrService : @"TestService",
500 (id)kSecReturnData : @YES,
501 (id)kSecAttrNoLegacy : @YES
502 };
503 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
504 XCTAssertEqual(result, errSecInteractionNotAllowed, @"SEP not locked?");
505
506 XCTAssertEqual(self.lockCounter, 0, "Device didn't lock");
507
508 NSLog(@"user unlock");
509 [mock stopMocking];
510
511
512 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
513 XCTAssertEqual(result, 0, @"can't find item");
514
515
516 NSLog(@"find items from old database");
517 [self findManyItems:50];
518 }
519
520
521 - (void)testUpgradeFromVersion10_5HungSEP
522 {
523 id mock = OCMClassMock([SecMockAKS class]);
524 OSStatus result = 0;
525
526 OCMStub([mock isSEPDown]).andReturn(true);
527
528 SecKeychainDbReset(^{
529 NSLog(@"resetting database");
530 [self loadDatabase:secdmock_db_version10_5];
531 });
532
533 NSDictionary* item = @{
534 (id)kSecClass : (id)kSecClassGenericPassword,
535 (id)kSecAttrAccount : @"TestAccount-0",
536 (id)kSecAttrService : @"TestService",
537 (id)kSecAttrNoLegacy : @(YES)
538 };
539 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
540 XCTAssertEqual(result, errSecNotAvailable, @"SEP not down?");
541
542 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
543 CFErrorRef error = NULL;
544 int version = 0;
545 SecKeychainDbGetVersion(dbt, &version, &error);
546 XCTAssertEqual(error, NULL, "error getting version");
547 XCTAssertEqual(version, 0x50a, "managed to upgrade when we shouldn't have");
548
549 return true;
550 });
551
552 /* user got the SEP out of DFU */
553 NSLog(@"SEP alive");
554 [mock stopMocking];
555
556 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
557 XCTAssertEqual(result, 0, @"failed to find test item to keychain");
558
559 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
560 CFErrorRef error = NULL;
561 int version = 0;
562 SecKeychainDbGetVersion(dbt, &version, &error);
563 XCTAssertEqual(error, NULL, "error getting version");
564 XCTAssertEqual(version, 0x40b, "didnt managed to upgrade");
565
566 return true;
567 });
568
569 NSLog(@"find items from old database");
570 [self findManyItems:50];
571 }
572
573 #endif /* USE_KEYSTORE */
574
575 @end