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