]> git.saurik.com Git - apple/security.git/blob - tests/secdmockaks/secdmockaks.m
Security-58286.200.222.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, 0, @"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, 0, @"failed to find test item to keychain: %u", n);
165 }
166 }
167
168 - (void)createManyKeys
169 {
170 unsigned n;
171 for (n = 0; n < 50; n++) {
172 NSDictionary* keyParams = @{
173 (id)kSecAttrKeyType : (id)kSecAttrKeyTypeRSA,
174 (id)kSecAttrKeySizeInBits : @(1024)
175 };
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) };
181
182 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
183 XCTAssertEqual(result, 0, @"failed to add test key to keychain: %u", n);
184 }
185 }
186
187
188 - (void)testBackupRestoreItem
189 {
190 [self createManyItems];
191 [self createManyKeys];
192
193
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) };
199
200 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
201 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
202
203 NSMutableDictionary* dataQuery = item.mutableCopy;
204 [dataQuery removeObjectForKey:(id)kSecValueData];
205 dataQuery[(id)kSecReturnData] = @(YES);
206 CFTypeRef foundItem = NULL;
207
208 /*
209 * Create backup
210 */
211
212 CFDataRef keybag = CFDataCreate(kCFAllocatorDefault, NULL, 0);
213 CFDataRef password = CFDataCreate(kCFAllocatorDefault, NULL, 0);
214
215 CFDataRef backup = _SecKeychainCopyBackup(keybag, password);
216 XCTAssert(backup, "expected to have a backup");
217
218 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
219 XCTAssertEqual(result, 0, @"failed to delete item");
220
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);
225
226 /*
227 * Restore backup and see that item is resurected
228 */
229
230 XCTAssertEqual(0, _SecKeychainRestoreBackup(backup, keybag, password));
231
232 CFReleaseNull(backup);
233 CFReleaseNull(password);
234 CFReleaseNull(keybag);
235
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);
239
240 result = SecItemDelete((__bridge CFDictionaryRef)dataQuery);
241 XCTAssertEqual(result, 0, @"failed to delete item");
242 }
243
244 - (void)testCreateSampleDatabase
245 {
246 #if USE_KEYSTORE
247 id mock = OCMClassMock([SecMockAKS class]);
248 OCMStub([mock useGenerationCount]).andReturn(true);
249 #endif
250
251 [self createManyItems];
252 [self createManyKeys];
253
254 /*
255 sleep(600);
256 lsof -p $(pgrep xctest)
257 sqlite3 database
258 .output mydatabase.h
259 .dump
260
261 add header and footer
262 */
263
264 [self findManyItems:50];
265 }
266
267 - (void)testTestAKSGenerationCount
268 {
269 #if USE_KEYSTORE
270 id mock = OCMClassMock([SecMockAKS class]);
271 OCMStub([mock useGenerationCount]).andReturn(true);
272
273 [self createManyItems];
274 [self findManyItems:50];
275 #endif
276 }
277
278
279 - (void)loadDatabase:(const char **)dumpstring
280 {
281 const char *s;
282 unsigned n = 0;
283
284 [self removeHomeDirectory];
285 [self createKeychainDirectory];
286
287 NSString *path = CFBridgingRelease(__SecKeychainCopyPath());
288 sqlite3 *handle = NULL;
289
290 XCTAssertEqual(SQLITE_OK, sqlite3_open([path UTF8String], &handle), "create keychain");
291
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);
296 if (errmsg) {
297 sqlite3_free(errmsg);
298 }
299 }
300 XCTAssertEqual(SQLITE_OK, sqlite3_close(handle), "close sqlite");
301 }
302
303 - (void)checkIncremental
304 {
305 /*
306 * check that we made incremental vacuum mode
307 */
308
309 __block CFErrorRef localError = NULL;
310 __block bool ok = true;
311 __block int vacuumMode = -1;
312
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);
317 });
318 });
319 return ok;
320 });
321 XCTAssertEqual(ok, true, "should work to fetch auto_vacuum value: %@", localError);
322 XCTAssertEqual(vacuumMode, 2, "vacuum mode should be incremental (2)");
323
324 CFReleaseNull(localError);
325
326 }
327
328 - (void)testUpgradeFromVersion10_5
329 {
330 SecKeychainDbReset(^{
331 NSLog(@"resetting database");
332 [self loadDatabase:secdmock_db_version10_5];
333 });
334
335 NSLog(@"find items from old database");
336 [self findManyItems:50];
337
338 [self checkIncremental];
339 }
340
341 - (void)testUpgradeFromVersion11_1
342 {
343 SecKeychainDbReset(^{
344 NSLog(@"resetting database");
345 [self loadDatabase:secdmock_db_version11_1];
346 });
347
348 NSLog(@"find items from old database");
349 [self findManyItems:50];
350
351 [self checkIncremental];
352 }
353
354 #if USE_KEYSTORE
355
356 - (void)testAddKeyByReference
357 {
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) };
364
365 OSStatus result = SecItemAdd((__bridge CFDictionaryRef)item, NULL);
366 XCTAssertEqual(result, 0, @"failed to add test item to keychain");
367
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");
374
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");
378
379 result = SecItemDelete((__bridge CFDictionaryRef)refQuery);
380 XCTAssertEqual(result, 0, @"failed to delete key");
381 }
382
383 - (bool)isLockedSoon:(keyclass_t)key_class
384 {
385 if (key_class == key_class_d || key_class == key_class_dku)
386 return false;
387 if (self.lockCounter <= 0)
388 return true;
389 self.lockCounter--;
390 return false;
391 }
392
393 /*
394 * Lock in the middle of migration
395 */
396 - (void)testUpgradeFromVersion10_5WhileLocked
397 {
398 OSStatus result = 0;
399 id mock = OCMClassMock([SecMockAKS class]);
400 [[[[mock stub] andCall:@selector(isLockedSoon:) onObject:self] ignoringNonObjectArgs] isLocked:0];
401
402 SecKeychainDbReset(^{
403 NSLog(@"resetting database");
404 [self loadDatabase:secdmock_db_version10_5];
405 });
406
407 self.lockCounter = 0;
408
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
415 };
416 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
417 XCTAssertEqual(result, errSecInteractionNotAllowed, @"SEP not locked?");
418
419 XCTAssertEqual(self.lockCounter, 0, "Device didn't lock");
420
421 NSLog(@"user unlock");
422 [mock stopMocking];
423
424
425 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
426 XCTAssertEqual(result, 0, @"can't find item");
427
428
429 NSLog(@"find items from old database");
430 [self findManyItems:50];
431 }
432
433
434 - (void)testUpgradeFromVersion10_5HungSEP
435 {
436 id mock = OCMClassMock([SecMockAKS class]);
437 OSStatus result = 0;
438
439 OCMStub([mock isSEPDown]).andReturn(true);
440
441 SecKeychainDbReset(^{
442 NSLog(@"resetting database");
443 [self loadDatabase:secdmock_db_version10_5];
444 });
445
446 NSDictionary* item = @{
447 (id)kSecClass : (id)kSecClassGenericPassword,
448 (id)kSecAttrAccount : @"TestAccount-0",
449 (id)kSecAttrService : @"TestService",
450 (id)kSecAttrNoLegacy : @(YES)
451 };
452 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
453 XCTAssertEqual(result, errSecNotAvailable, @"SEP not down?");
454
455 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
456 CFErrorRef error = NULL;
457 int version = 0;
458 SecKeychainDbGetVersion(dbt, &version, &error);
459 XCTAssertEqual(error, NULL, "error getting version");
460 XCTAssertEqual(version, 0x50a, "managed to upgrade when we shouldn't have");
461
462 return true;
463 });
464
465 /* user got the SEP out of DFU */
466 NSLog(@"SEP alive");
467 [mock stopMocking];
468
469 result = SecItemCopyMatching((__bridge CFDictionaryRef)item, NULL);
470 XCTAssertEqual(result, 0, @"failed to find test item to keychain");
471
472 kc_with_dbt(true, NULL, ^bool (SecDbConnectionRef dbt) {
473 CFErrorRef error = NULL;
474 int version = 0;
475 SecKeychainDbGetVersion(dbt, &version, &error);
476 XCTAssertEqual(error, NULL, "error getting version");
477 XCTAssertEqual(version, 0x40b, "didnt managed to upgrade");
478
479 return true;
480 });
481
482 NSLog(@"find items from old database");
483 [self findManyItems:50];
484 }
485
486 #endif /* USE_KEYSTORE */
487
488 @end