2 * Copyright (c) 2010,2012-2014 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@
25 #include <TargetConditionals.h>
27 #if TARGET_IPHONE_SIMULATOR
28 #define USE_KEYSTORE 0
29 #else /* No AppleKeyStore.kext on this OS. */
30 #define USE_KEYSTORE 1
34 #include <CoreFoundation/CoreFoundation.h>
35 #include <Security/SecBase.h>
36 #include <Security/SecItem.h>
37 #include <Security/SecInternal.h>
38 #include <Security/SecItemPriv.h>
39 #include <utilities/array_size.h>
40 #include <utilities/SecCFWrappers.h>
43 #include <AssertMacros.h>
53 #include "../Security_regressions.h"
55 struct test_persistent_s
{
57 CFDictionaryRef query
;
58 CFDictionaryRef query1
;
59 CFDictionaryRef query2
;
60 CFMutableDictionaryRef query3
;
61 CFMutableDictionaryRef query4
;
64 static void test_persistent(struct test_persistent_s
*p
)
67 CFNumberRef eighty
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &v_eighty
);
68 const char *v_data
= "test";
69 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
70 const void *keys
[] = {
76 kSecAttrAuthenticationType
,
77 kSecReturnPersistentRef
,
80 const void *values
[] = {
81 kSecClassInternetPassword
,
90 CFDictionaryRef item
= CFDictionaryCreate(NULL
, keys
, values
,
91 array_size(keys
), &kCFTypeDictionaryKeyCallBacks
,
92 &kCFTypeDictionaryValueCallBacks
);
95 // NUKE anything we might have left around from a previous test run so we don't crash.
97 ok_status(SecItemAdd(item
, &p
->persist
[0]), "add internet password");
98 CFTypeRef results
= NULL
;
99 CFTypeRef results2
= NULL
;
101 skip("no persistent ref", 6, ok(p
->persist
[0], "got back persistent ref"));
103 /* Create a dict with all attrs except the data. */
104 keys
[(array_size(keys
)) - 2] = kSecReturnAttributes
;
105 p
->query
= CFDictionaryCreate(NULL
, keys
, values
,
106 (array_size(keys
)) - 1, &kCFTypeDictionaryKeyCallBacks
,
107 &kCFTypeDictionaryValueCallBacks
);
108 ok_status(SecItemCopyMatching(p
->query
, &results
), "find internet password by attr");
110 const void *keys_persist
[] = {
111 kSecReturnAttributes
,
112 kSecValuePersistentRef
114 const void *values_persist
[] = {
118 p
->query2
= CFDictionaryCreate(NULL
, keys_persist
, values_persist
,
119 (array_size(keys_persist
)), &kCFTypeDictionaryKeyCallBacks
,
120 &kCFTypeDictionaryValueCallBacks
);
121 ok_status(SecItemCopyMatching(p
->query2
, &results2
), "find internet password by persistent ref");
122 ok(CFEqual(results
, results2
? results2
: CFSTR("")), "same item (attributes)");
124 CFReleaseNull(results
);
125 CFReleaseNull(results2
);
127 ok_status(SecItemDelete(p
->query
), "delete internet password");
129 ok_status(!SecItemCopyMatching(p
->query
, &results
),
130 "don't find internet password by attributes");
131 ok(!results
, "no results");
134 /* clean up left over from aborted run */
136 CFDictionaryRef cleanup
= CFDictionaryCreate(NULL
, (const void **)&kSecValuePersistentRef
,
137 &results
, 1, &kCFTypeDictionaryKeyCallBacks
,
138 &kCFTypeDictionaryValueCallBacks
);
139 SecItemDelete(cleanup
);
144 ok_status(!SecItemCopyMatching(p
->query2
, &results2
),
145 "don't find internet password by persistent ref anymore");
146 ok(!results2
, "no results");
148 CFReleaseNull(p
->persist
[0]);
150 /* Add a new item and get it's persitant ref. */
151 ok_status(SecItemAdd(item
, &p
->persist
[0]), "add internet password");
152 p
->persist
[1] = NULL
;
153 CFMutableDictionaryRef item2
= CFDictionaryCreateMutableCopy(NULL
, 0, item
);
154 CFDictionarySetValue(item2
, kSecAttrAccount
, CFSTR("johndoe-bu"));
155 // NUKE anything we might have left around from a previous test run so we don't crash.
156 SecItemDelete(item2
);
157 ok_status(SecItemAdd(item2
, &p
->persist
[1]), "add second internet password");
158 CFMutableDictionaryRef update
= NULL
;
159 CFStringRef server
= NULL
;
161 skip("no persistent ref", 3, ok(p
->persist
[0], "got back persistent ref from first internet password"));
163 is(CFGetTypeID(p
->persist
[0]), CFDataGetTypeID(), "result is a CFData");
164 p
->query3
= CFDictionaryCreateMutableForCFTypes(NULL
);
165 CFDictionaryAddValue(p
->query3
, kSecValuePersistentRef
, p
->persist
[0]);
166 update
= CFDictionaryCreateMutableForCFTypes(NULL
);
167 CFDictionaryAddValue(update
, kSecAttrServer
, CFSTR("zuigt.com"));
168 ok_status(SecItemUpdate(p
->query3
, update
), "update via persitant ref");
170 /* Verify that the update really worked. */
171 CFDictionaryAddValue(p
->query3
, kSecReturnAttributes
, kCFBooleanTrue
);
172 ok_status(SecItemCopyMatching(p
->query3
, &results2
), "find updated internet password by persistent ref");
173 server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
174 ok(CFEqual(server
, CFSTR("zuigt.com")), "verify attribute was modified by update");
175 CFReleaseNull(results2
);
176 CFDictionaryRemoveValue(p
->query3
, kSecReturnAttributes
);
180 skip("no persistent ref", 2, ok(p
->persist
[1], "got back persistent ref"));
182 /* Verify that item2 wasn't affected by the update. */
183 p
->query4
= CFDictionaryCreateMutableForCFTypes(NULL
);
184 CFDictionaryAddValue(p
->query4
, kSecValuePersistentRef
, p
->persist
[1]);
185 CFDictionaryAddValue(p
->query4
, kSecReturnAttributes
, kCFBooleanTrue
);
186 ok_status(SecItemCopyMatching(p
->query4
, &results2
), "find non updated internet password by persistent ref");
187 server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
188 ok(CFEqual(server
, CFSTR("zuigt.nl")), "verify second items attribute was not modified by update");
189 CFReleaseNull(results2
);
192 /* Delete the item via persitant ref. */
193 ok_status(SecItemDelete(p
->query3
), "delete via persitant ref");
194 is_status(SecItemCopyMatching(p
->query3
, &results2
), errSecItemNotFound
,
195 "don't find deleted internet password by persistent ref");
196 CFReleaseNull(results2
);
197 ok_status(SecItemCopyMatching(p
->query4
, &results2
),
198 "find non deleted internet password by persistent ref");
199 CFReleaseNull(results2
);
201 CFReleaseNull(update
);
203 CFReleaseNull(item2
);
204 CFReleaseNull(eighty
);
205 CFReleaseNull(pwdata
);
208 static void test_persistent2(struct test_persistent_s
*p
)
210 CFTypeRef results
= NULL
;
211 CFTypeRef results2
= NULL
;
213 ok_status(!SecItemCopyMatching(p
->query
, &results
),
214 "don't find internet password by attributes");
215 ok(!results
, "no results");
217 ok_status(!SecItemCopyMatching(p
->query2
, &results2
),
218 "don't find internet password by persistent ref anymore");
219 ok(!results2
, "no results");
222 ok_status(SecItemCopyMatching(p
->query4
, &results2
), "find non updated internet password by persistent ref");
223 skip("non updated internet password by persistent ref NOT FOUND!", 2, results2
);
224 ok(results2
, "non updated internet password not found");
225 CFStringRef server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
226 ok(CFEqual(server
, CFSTR("zuigt.nl")), "verify second items attribute was not modified by update");
227 CFReleaseNull(results2
);
230 is_status(SecItemCopyMatching(p
->query3
, &results2
), errSecItemNotFound
,
231 "don't find deleted internet password by persistent ref");
232 CFReleaseNull(results2
);
233 ok_status(SecItemCopyMatching(p
->query4
, &results2
),
234 "find non deleted internet password by persistent ref");
235 CFReleaseNull(results2
);
237 ok_status(SecItemDelete(p
->query4
),"Deleted internet password by persistent ref");
240 CFRelease(p
->query2
);
241 CFRelease(p
->query3
);
242 CFRelease(p
->query4
);
243 CFReleaseNull(p
->persist
[0]);
244 CFReleaseNull(p
->persist
[1]);
247 static CFMutableDictionaryRef
test_create_lockdown_identity_query(void) {
248 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
249 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
250 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("test-delete-me"));
251 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("lockdown-identities"));
255 static CFMutableDictionaryRef
test_create_managedconfiguration_query(void) {
256 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
257 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
258 CFDictionaryAddValue(query
, kSecAttrService
, CFSTR("com.apple.managedconfiguration"));
259 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("Public"));
260 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("apple"));
264 static void test_add_lockdown_identity_items(void) {
265 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
266 const char *v_data
= "lockdown identity data (which should be a cert + key)";
267 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
268 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
269 ok_status(SecItemAdd(query
, NULL
), "test_add_lockdown_identity_items");
270 CFReleaseSafe(pwdata
);
271 CFReleaseSafe(query
);
274 static void test_remove_lockdown_identity_items(void) {
275 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
276 ok_status(SecItemDelete(query
), "test_remove_lockdown_identity_items");
277 CFReleaseSafe(query
);
280 static void test_no_find_lockdown_identity_item(void) {
281 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
282 is_status(SecItemCopyMatching(query
, NULL
), errSecItemNotFound
,
283 "test_no_find_lockdown_identity_item");
284 CFReleaseSafe(query
);
287 static CFMutableDictionaryRef
test_create_sysbound_query(void) {
288 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
289 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
290 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("sysbound"));
291 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("apple"));
295 static void test_add_sysbound_item(void) {
296 CFMutableDictionaryRef query
= test_create_sysbound_query();
297 int32_t val
= kSecSecAttrSysBoundPreserveDuringRestore
;
298 CFNumberRef num
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &val
);
299 CFDictionaryAddValue(query
, kSecAttrSysBound
, num
);
302 const char *v_data
= "sysbound identity data";
303 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
304 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
305 ok_status(SecItemAdd(query
, NULL
), "test_add_sysbound_item");
306 CFReleaseSafe(pwdata
);
307 CFReleaseSafe(query
);
310 static void test_remove_sysbound_item(void) {
311 CFMutableDictionaryRef query
= test_create_sysbound_query();
312 ok_status(SecItemDelete(query
), "test_remove_sysbound_item");
313 CFReleaseSafe(query
);
316 static void test_find_sysbound_item(OSStatus expectedCode
) {
317 CFMutableDictionaryRef query
= test_create_sysbound_query();
318 is_status(SecItemCopyMatching(query
, NULL
), expectedCode
,
319 "test_find_sysbound_item");
320 CFReleaseSafe(query
);
327 static CFMutableDictionaryRef
test_create_bt_query(bool sync
) {
328 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
329 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
330 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("com.apple.bluetooth"));
331 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, sync
? kCFBooleanTrue
: kCFBooleanFalse
);
332 CFDictionarySetValue(query
, kSecAttrAccount
, sync
? CFSTR("sync") : CFSTR("non-sync"));
336 static void test_add_bt_items(const char *data
) {
337 CFMutableDictionaryRef query
= NULL
;
339 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)data
, strlen(data
));
341 query
= test_create_bt_query(false);
342 (void)SecItemDelete(query
);
343 CFDictionarySetValue(query
, kSecValueData
, pwdata
);
344 ok_status(SecItemAdd(query
, NULL
), "test_add_bt_item(nonsync)");
345 CFReleaseSafe(query
);
347 query
= test_create_bt_query(true);
348 (void)SecItemDelete(query
);
349 CFDictionarySetValue(query
, kSecValueData
, pwdata
);
350 ok_status(SecItemAdd(query
, NULL
), "test_add_bt_item(sync)");
351 CFReleaseSafe(query
);
353 CFReleaseSafe(pwdata
);
356 static void test_find_bt_item(OSStatus expectedCode
, bool sync
, const char *data
) {
357 CFMutableDictionaryRef query
= test_create_bt_query(sync
);
358 CFDictionarySetValue(query
, kSecReturnData
, kCFBooleanTrue
);
359 CFDataRef pwdata
= NULL
;
360 is_status(SecItemCopyMatching(query
, (CFTypeRef
*)&pwdata
), expectedCode
,
361 "test_find_bt_item: %s", data
);
362 CFIndex len
= strlen(data
);
363 is(len
, CFDataGetLength(pwdata
), "length wrong(%s)", data
);
364 ok(memcmp(data
, CFDataGetBytePtr(pwdata
), len
) == 0, "length wrong(%s)", data
);
365 CFReleaseSafe(query
);
372 static void test_add_managedconfiguration_item(void) {
373 CFMutableDictionaryRef query
= test_create_managedconfiguration_query();
374 const char *v_data
= "public managedconfiguration password history data";
375 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
376 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
377 ok_status(SecItemAdd(query
, NULL
), "test_add_managedconfiguration_item");
378 CFReleaseSafe(pwdata
);
379 CFReleaseSafe(query
);
382 static void test_find_managedconfiguration_item(void) {
383 CFMutableDictionaryRef query
= test_create_managedconfiguration_query();
384 ok_status(SecItemCopyMatching(query
, NULL
), "test_find_managedconfiguration_item");
385 ok_status(SecItemDelete(query
), "test_find_managedconfiguration_item (deleted)");
386 CFReleaseSafe(query
);
390 #define DATA_ARG(x) (x) ? CFDataGetBytePtr((x)) : NULL, (x) ? (int)CFDataGetLength((x)) : 0
391 static CFDataRef
create_keybag(keybag_handle_t bag_type
, CFDataRef password
)
393 CFDataRef result
= NULL
;
397 keybag_handle_t handle
= bad_keybag_handle
;
398 require_noerr(aks_create_bag(DATA_ARG(password
), bag_type
, &handle
), out
);
399 require_noerr(aks_save_bag(handle
, &bag
, &bagLen
), out
);
401 result
= CFDataCreate(kCFAllocatorDefault
, bag
, bagLen
);
407 /* Test low level keychain migration from device to device interface. */
408 static void tests(void)
411 CFMutableDictionaryRef lock_down_query
= test_create_lockdown_identity_query();
412 (void)SecItemDelete(lock_down_query
);
413 CFReleaseNull(lock_down_query
);
417 CFNumberRef eighty
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &v_eighty
);
418 const char *v_data
= "test";
419 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
420 CFMutableDictionaryRef query
= CFDictionaryCreateMutableForCFTypes(NULL
);
421 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
422 CFDictionaryAddValue(query
, kSecAttrServer
, CFSTR("members.spamcop.net"));
423 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("smith"));
424 CFDictionaryAddValue(query
, kSecAttrPort
, eighty
);
425 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTP
);
426 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeDefault
);
427 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
428 // NUKE anything we might have left around from a previous test run so we don't crash.
429 (void)SecItemDelete(query
);
431 ok_status(SecItemAdd(query
, NULL
), "add internet password");
432 is_status(SecItemAdd(query
, NULL
), errSecDuplicateItem
,
433 "add internet password again");
435 ok_status(SecItemCopyMatching(query
, NULL
), "Found the item we added");
437 struct test_persistent_s p
= {};
440 CFDataRef backup
= NULL
, keybag
= NULL
, password
= NULL
;
442 test_add_lockdown_identity_items();
443 test_add_sysbound_item();
444 test_add_bt_items("kaka1");
447 keybag
= create_keybag(kAppleKeyStoreBackupBag
, password
);
449 keybag
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
452 ok(backup
= _SecKeychainCopyBackup(keybag
, password
),
453 "_SecKeychainCopyBackup");
455 test_add_managedconfiguration_item();
456 test_remove_lockdown_identity_items();
457 test_remove_sysbound_item();
459 test_add_bt_items("kaka2");
461 ok_status(_SecKeychainRestoreBackup(backup
, keybag
, password
),
462 "_SecKeychainRestoreBackup");
463 CFReleaseSafe(backup
);
465 test_no_find_lockdown_identity_item();
466 test_find_sysbound_item(errSecItemNotFound
);
467 test_find_managedconfiguration_item();
469 * Check that the kaka1 entry was "overwritten"
471 test_find_bt_item(errSecSuccess
, true, "kaka2");
472 test_find_bt_item(errSecSuccess
, false, "kaka1");
474 ok_status(SecItemCopyMatching(query
, NULL
),
475 "Found the item we added after restore");
477 test_persistent2(&p
);
480 CFReleaseNull(keybag
);
481 keybag
= create_keybag(kAppleKeyStoreOTABackupBag
, password
);
484 ok(backup
= _SecKeychainCopyBackup(keybag
, password
),
485 "_SecKeychainCopyBackup");
486 ok_status(_SecKeychainRestoreBackup(backup
, keybag
, password
),
487 "_SecKeychainRestoreBackup");
488 ok_status(SecItemCopyMatching(query
, NULL
),
489 "Found the item we added after restore");
490 CFReleaseNull(backup
);
492 // force tombstone to be added, since it's not the default behavior per rdar://14680869
493 CFDictionaryAddValue(query
, kSecUseTombstones
, kCFBooleanTrue
);
495 ok_status(SecItemDelete(query
), "Deleted item we added");
498 CFReleaseNull(keybag
);
499 keybag
= create_keybag(kAppleKeyStoreOTABackupBag
/* use truthiness bag once it's there */, password
);
503 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
504 ok_status(SecItemAdd(query
, NULL
), "add internet password");
506 // and non-syncable item
507 test_add_managedconfiguration_item();
509 CFDictionaryRef syncableBackup
= NULL
;
511 CFErrorRef error
= NULL
;
512 CFDictionaryRef scratch
= NULL
;
514 skip("skipping syncable backup tests", 7,
515 ok_status(_SecKeychainBackupSyncable(keybag
, password
, NULL
, &syncableBackup
), "export items"));
517 // TODO: add item, call SecServerCopyTruthInTheCloud again
519 // CFShow(syncableBackup);
522 skip("skipping syncable backup tests", 6,
523 ok_status(SecItemCopyMatching(query
, NULL
), "find item we are about to destroy"));
525 skip("skipping syncable backup tests", 5,
526 ok_status(SecItemDelete(query
), "delete item we backed up"));
528 // ensure we added a tombstone
529 CFDictionaryAddValue(query
, kSecAttrTombstone
, kCFBooleanTrue
);
530 skip("skipping syncable backup tests", 4,
531 ok_status(SecItemCopyMatching(query
, NULL
), "find tombstone for item we deleted"));
532 CFDictionaryRemoveValue(query
, kSecAttrTombstone
);
534 test_find_managedconfiguration_item(); // <- 2 tests here
536 // TODO: add a different new item - delete what's not in the syncableBackup?
538 // Do another backup after some changes
539 skip("skipping syncable backup tests", 1,
540 ok_status(_SecKeychainBackupSyncable(keybag
, password
, syncableBackup
, &scratch
), "export items after changes"));
542 skip("skipping syncable backup tests", 0,
543 ok_status(_SecKeychainRestoreSyncable(keybag
, password
, syncableBackup
), "import items"));
545 CFReleaseNull(scratch
);
546 CFReleaseNull(error
);
548 // non-syncable item should (still) be gone -> add should work
549 test_add_managedconfiguration_item();
550 test_find_managedconfiguration_item();
552 // syncable item should have not been restored, because the tombstone was newer than the item in the backup -> copy matching should fail
553 is_status(errSecItemNotFound
, SecItemCopyMatching(query
, NULL
),
554 "find restored item");
555 is_status(errSecItemNotFound
, SecItemDelete(query
), "delete restored item");
557 CFReleaseSafe(syncableBackup
);
558 CFReleaseSafe(keybag
);
559 CFReleaseSafe(eighty
);
560 CFReleaseSafe(pwdata
);
561 CFReleaseSafe(query
);
564 int si_33_keychain_backup(int argc
, char *const *argv
)