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_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
28 #define USE_KEYSTORE 1
29 #else /* No AppleKeyStore.kext on this OS. */
30 #define USE_KEYSTORE 0
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>
42 #include <IOKit/IOKitLib.h>
43 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
52 #include "../Security_regressions.h"
54 struct test_persistent_s
{
56 CFDictionaryRef query
;
57 CFDictionaryRef query1
;
58 CFDictionaryRef query2
;
59 CFMutableDictionaryRef query3
;
60 CFMutableDictionaryRef query4
;
63 static void test_persistent(struct test_persistent_s
*p
)
66 CFNumberRef eighty
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &v_eighty
);
67 const char *v_data
= "test";
68 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
69 const void *keys
[] = {
75 kSecAttrAuthenticationType
,
76 kSecReturnPersistentRef
,
79 const void *values
[] = {
80 kSecClassInternetPassword
,
89 CFDictionaryRef item
= CFDictionaryCreate(NULL
, keys
, values
,
90 array_size(keys
), &kCFTypeDictionaryKeyCallBacks
,
91 &kCFTypeDictionaryValueCallBacks
);
94 // NUKE anything we might have left around from a previous test run so we don't crash.
96 ok_status(SecItemAdd(item
, &p
->persist
[0]), "add internet password");
97 CFTypeRef results
= NULL
;
98 CFTypeRef results2
= NULL
;
100 skip("no persistent ref", 6, ok(p
->persist
[0], "got back persistent ref"));
102 /* Create a dict with all attrs except the data. */
103 keys
[(array_size(keys
)) - 2] = kSecReturnAttributes
;
104 p
->query
= CFDictionaryCreate(NULL
, keys
, values
,
105 (array_size(keys
)) - 1, &kCFTypeDictionaryKeyCallBacks
,
106 &kCFTypeDictionaryValueCallBacks
);
107 ok_status(SecItemCopyMatching(p
->query
, &results
), "find internet password by attr");
109 const void *keys_persist
[] = {
110 kSecReturnAttributes
,
111 kSecValuePersistentRef
113 const void *values_persist
[] = {
117 p
->query2
= CFDictionaryCreate(NULL
, keys_persist
, values_persist
,
118 (array_size(keys_persist
)), &kCFTypeDictionaryKeyCallBacks
,
119 &kCFTypeDictionaryValueCallBacks
);
120 ok_status(SecItemCopyMatching(p
->query2
, &results2
), "find internet password by persistent ref");
121 ok(CFEqual(results
, results2
? results2
: CFSTR("")), "same item (attributes)");
123 CFReleaseNull(results
);
124 CFReleaseNull(results2
);
126 ok_status(SecItemDelete(p
->query
), "delete internet password");
128 ok_status(!SecItemCopyMatching(p
->query
, &results
),
129 "don't find internet password by attributes");
130 ok(!results
, "no results");
133 /* clean up left over from aborted run */
135 CFDictionaryRef cleanup
= CFDictionaryCreate(NULL
, (const void **)&kSecValuePersistentRef
,
136 &results
, 1, &kCFTypeDictionaryKeyCallBacks
,
137 &kCFTypeDictionaryValueCallBacks
);
138 SecItemDelete(cleanup
);
143 ok_status(!SecItemCopyMatching(p
->query2
, &results2
),
144 "don't find internet password by persistent ref anymore");
145 ok(!results2
, "no results");
147 CFReleaseNull(p
->persist
[0]);
149 /* Add a new item and get it's persitant ref. */
150 ok_status(SecItemAdd(item
, &p
->persist
[0]), "add internet password");
151 p
->persist
[1] = NULL
;
152 CFMutableDictionaryRef item2
= CFDictionaryCreateMutableCopy(NULL
, 0, item
);
153 CFDictionarySetValue(item2
, kSecAttrAccount
, CFSTR("johndoe-bu"));
154 // NUKE anything we might have left around from a previous test run so we don't crash.
155 SecItemDelete(item2
);
156 ok_status(SecItemAdd(item2
, &p
->persist
[1]), "add second internet password");
157 CFMutableDictionaryRef update
= NULL
;
158 CFStringRef server
= NULL
;
160 skip("no persistent ref", 3, ok(p
->persist
[0], "got back persistent ref from first internet password"));
162 is(CFGetTypeID(p
->persist
[0]), CFDataGetTypeID(), "result is a CFData");
163 p
->query3
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
,
164 &kCFTypeDictionaryValueCallBacks
);
165 CFDictionaryAddValue(p
->query3
, kSecValuePersistentRef
, p
->persist
[0]);
166 update
= CFDictionaryCreateMutable(NULL
, 0, NULL
, 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
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
,
184 &kCFTypeDictionaryValueCallBacks
);
185 CFDictionaryAddValue(p
->query4
, kSecValuePersistentRef
, p
->persist
[1]);
186 CFDictionaryAddValue(p
->query4
, kSecReturnAttributes
, kCFBooleanTrue
);
187 ok_status(SecItemCopyMatching(p
->query4
, &results2
), "find non updated internet password by persistent ref");
188 server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
189 ok(CFEqual(server
, CFSTR("zuigt.nl")), "verify second items attribute was not modified by update");
190 CFReleaseNull(results2
);
193 /* Delete the item via persitant ref. */
194 ok_status(SecItemDelete(p
->query3
), "delete via persitant ref");
195 is_status(SecItemCopyMatching(p
->query3
, &results2
), errSecItemNotFound
,
196 "don't find deleted internet password by persistent ref");
197 CFReleaseNull(results2
);
198 ok_status(SecItemCopyMatching(p
->query4
, &results2
),
199 "find non deleted internet password by persistent ref");
200 CFReleaseNull(results2
);
202 CFReleaseNull(update
);
204 CFReleaseNull(item2
);
205 CFReleaseNull(eighty
);
206 CFReleaseNull(pwdata
);
209 static void test_persistent2(struct test_persistent_s
*p
)
211 CFTypeRef results
= NULL
;
212 CFTypeRef results2
= NULL
;
214 ok_status(!SecItemCopyMatching(p
->query
, &results
),
215 "don't find internet password by attributes");
216 ok(!results
, "no results");
218 ok_status(!SecItemCopyMatching(p
->query2
, &results2
),
219 "don't find internet password by persistent ref anymore");
220 ok(!results2
, "no results");
223 ok_status(SecItemCopyMatching(p
->query4
, &results2
), "find non updated internet password by persistent ref");
224 skip("non updated internet password by persistent ref NOT FOUND!", 2, results2
);
225 ok(results2
, "non updated internet password not found");
226 CFStringRef server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
227 ok(CFEqual(server
, CFSTR("zuigt.nl")), "verify second items attribute was not modified by update");
228 CFReleaseNull(results2
);
231 is_status(SecItemCopyMatching(p
->query3
, &results2
), errSecItemNotFound
,
232 "don't find deleted internet password by persistent ref");
233 CFReleaseNull(results2
);
234 ok_status(SecItemCopyMatching(p
->query4
, &results2
),
235 "find non deleted internet password by persistent ref");
236 CFReleaseNull(results2
);
238 ok_status(SecItemDelete(p
->query4
),"Deleted internet password by persistent ref");
241 CFRelease(p
->query2
);
242 CFRelease(p
->query3
);
243 CFRelease(p
->query4
);
244 CFReleaseNull(p
->persist
[0]);
245 CFReleaseNull(p
->persist
[1]);
248 static CFMutableDictionaryRef
test_create_lockdown_identity_query(void) {
249 CFMutableDictionaryRef query
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
250 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
251 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("test-delete-me"));
252 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("lockdown-identities"));
256 static CFMutableDictionaryRef
test_create_managedconfiguration_query(void) {
257 CFMutableDictionaryRef query
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
258 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
259 CFDictionaryAddValue(query
, kSecAttrService
, CFSTR("com.apple.managedconfiguration"));
260 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("Public"));
261 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("apple"));
265 static void test_add_lockdown_identity_items(void) {
266 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
267 const char *v_data
= "lockdown identity data (which should be a cert + key)";
268 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
269 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
270 ok_status(SecItemAdd(query
, NULL
), "test_add_lockdown_identity_items");
271 CFReleaseSafe(pwdata
);
272 CFReleaseSafe(query
);
275 static void test_remove_lockdown_identity_items(void) {
276 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
277 ok_status(SecItemDelete(query
), "test_remove_lockdown_identity_items");
278 CFReleaseSafe(query
);
281 static void test_no_find_lockdown_identity_item(void) {
282 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
283 is_status(SecItemCopyMatching(query
, NULL
), errSecItemNotFound
,
284 "test_no_find_lockdown_identity_item");
285 CFReleaseSafe(query
);
288 static void test_add_managedconfiguration_item(void) {
289 CFMutableDictionaryRef query
= test_create_managedconfiguration_query();
290 const char *v_data
= "public managedconfiguration password history data";
291 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
292 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
293 ok_status(SecItemAdd(query
, NULL
), "test_add_managedconfiguration_item");
294 CFReleaseSafe(pwdata
);
295 CFReleaseSafe(query
);
298 static void test_find_managedconfiguration_item(void) {
299 CFMutableDictionaryRef query
= test_create_managedconfiguration_query();
300 ok_status(SecItemCopyMatching(query
, NULL
), "test_find_managedconfiguration_item");
301 ok_status(SecItemDelete(query
), "test_find_managedconfiguration_item (deleted)");
302 CFReleaseSafe(query
);
306 static io_connect_t
connect_to_keystore(void)
308 io_registry_entry_t apple_key_bag_service
;
309 kern_return_t result
;
310 io_connect_t keystore
= MACH_PORT_NULL
;
312 apple_key_bag_service
= IOServiceGetMatchingService(kIOMasterPortDefault
,
313 IOServiceMatching(kAppleKeyStoreServiceName
));
315 if (apple_key_bag_service
== IO_OBJECT_NULL
) {
316 fprintf(stderr
, "Failed to get service.\n");
320 result
= IOServiceOpen(apple_key_bag_service
, mach_task_self(), 0, &keystore
);
321 if (KERN_SUCCESS
!= result
)
322 fprintf(stderr
, "Failed to open keystore\n");
324 if (keystore
!= MACH_PORT_NULL
) {
325 IOReturn kernResult
= IOConnectCallMethod(keystore
,
326 kAppleKeyStoreUserClientOpen
, NULL
, 0, NULL
, 0, NULL
, NULL
,
329 fprintf(stderr
, "Failed to open AppleKeyStore: %x\n", kernResult
);
334 #define DATA_ARG(x) (x) ? CFDataGetBytePtr((x)) : NULL, (x) ? (int)CFDataGetLength((x)) : 0
336 static CFDataRef
create_keybag(keybag_handle_t bag_type
, CFDataRef password
)
338 uint64_t inputs
[] = { bag_type
};
339 uint64_t outputs
[] = {0};
340 uint32_t num_inputs
= array_size(inputs
);
341 uint32_t num_outputs
= array_size(outputs
);
344 io_connect_t keystore
;
346 unsigned char keybagdata
[4096]; //Is that big enough?
347 size_t keybagsize
=sizeof(keybagdata
);
349 keystore
=connect_to_keystore();
351 kernResult
= IOConnectCallMethod(keystore
,
352 kAppleKeyStoreKeyBagCreate
,
353 inputs
, num_inputs
, DATA_ARG(password
),
354 outputs
, &num_outputs
, NULL
, 0);
357 fprintf(stderr
, "kAppleKeyStoreKeyBagCreate: 0x%x\n", kernResult
);
361 /* Copy out keybag */
362 inputs
[0]=outputs
[0];
365 kernResult
= IOConnectCallMethod(keystore
,
366 kAppleKeyStoreKeyBagCopy
,
367 inputs
, num_inputs
, NULL
, 0,
368 NULL
, 0, keybagdata
, &keybagsize
);
371 fprintf(stderr
, "kAppleKeyStoreKeyBagCopy: 0x%x\n", kernResult
);
375 return CFDataCreate(kCFAllocatorDefault
, keybagdata
, keybagsize
);
379 /* Test low level keychain migration from device to device interface. */
380 static void tests(void)
383 CFMutableDictionaryRef lock_down_query
= test_create_lockdown_identity_query();
384 (void)SecItemDelete(lock_down_query
);
385 CFReleaseNull(lock_down_query
);
389 CFNumberRef eighty
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &v_eighty
);
390 const char *v_data
= "test";
391 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
392 CFMutableDictionaryRef query
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
393 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
394 CFDictionaryAddValue(query
, kSecAttrServer
, CFSTR("members.spamcop.net"));
395 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("smith"));
396 CFDictionaryAddValue(query
, kSecAttrPort
, eighty
);
397 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTP
);
398 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeDefault
);
399 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
400 // NUKE anything we might have left around from a previous test run so we don't crash.
401 (void)SecItemDelete(query
);
403 ok_status(SecItemAdd(query
, NULL
), "add internet password");
404 is_status(SecItemAdd(query
, NULL
), errSecDuplicateItem
,
405 "add internet password again");
407 ok_status(SecItemCopyMatching(query
, NULL
), "Found the item we added");
409 struct test_persistent_s p
= {};
412 CFDataRef backup
= NULL
, keybag
= NULL
, password
= NULL
;
414 test_add_lockdown_identity_items();
417 keybag
= create_keybag(kAppleKeyStoreBackupBag
, password
);
419 keybag
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
422 ok(backup
= _SecKeychainCopyBackup(keybag
, password
),
423 "_SecKeychainCopyBackup");
425 test_add_managedconfiguration_item();
426 test_remove_lockdown_identity_items();
428 ok_status(_SecKeychainRestoreBackup(backup
, keybag
, password
),
429 "_SecKeychainRestoreBackup");
430 CFReleaseSafe(backup
);
432 test_no_find_lockdown_identity_item();
433 test_find_managedconfiguration_item();
435 ok_status(SecItemCopyMatching(query
, NULL
),
436 "Found the item we added after restore");
438 test_persistent2(&p
);
441 CFReleaseNull(keybag
);
442 keybag
= create_keybag(kAppleKeyStoreOTABackupBag
, password
);
445 ok(backup
= _SecKeychainCopyBackup(keybag
, password
),
446 "_SecKeychainCopyBackup");
447 ok_status(_SecKeychainRestoreBackup(backup
, keybag
, password
),
448 "_SecKeychainRestoreBackup");
449 ok_status(SecItemCopyMatching(query
, NULL
),
450 "Found the item we added after restore");
451 CFReleaseNull(backup
);
453 // force tombstone to be added, since it's not the default behavior per rdar://14680869
454 CFDictionaryAddValue(query
, kSecUseTombstones
, kCFBooleanTrue
);
456 ok_status(SecItemDelete(query
), "Deleted item we added");
459 CFReleaseNull(keybag
);
460 keybag
= create_keybag(kAppleKeyStoreOTABackupBag
/* use truthiness bag once it's there */, password
);
464 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
465 ok_status(SecItemAdd(query
, NULL
), "add internet password");
467 // and non-syncable item
468 test_add_managedconfiguration_item();
470 CFDictionaryRef syncableBackup
= NULL
;
472 CFErrorRef error
= NULL
;
473 CFDictionaryRef scratch
= NULL
;
475 skip("skipping syncable backup tests", 7,
476 ok_status(_SecKeychainBackupSyncable(keybag
, password
, NULL
, &syncableBackup
), "export items"));
478 // TODO: add item, call SecServerCopyTruthInTheCloud again
480 // CFShow(syncableBackup);
483 skip("skipping syncable backup tests", 6,
484 ok_status(SecItemCopyMatching(query
, NULL
), "find item we are about to destroy"));
486 skip("skipping syncable backup tests", 5,
487 ok_status(SecItemDelete(query
), "delete item we backed up"));
489 // ensure we added a tombstone
490 CFDictionaryAddValue(query
, kSecAttrTombstone
, kCFBooleanTrue
);
491 skip("skipping syncable backup tests", 4,
492 ok_status(SecItemCopyMatching(query
, NULL
), "find tombstone for item we deleted"));
493 CFDictionaryRemoveValue(query
, kSecAttrTombstone
);
495 test_find_managedconfiguration_item(); // <- 2 tests here
497 // TODO: add a different new item - delete what's not in the syncableBackup?
499 // Do another backup after some changes
500 skip("skipping syncable backup tests", 1,
501 ok_status(_SecKeychainBackupSyncable(keybag
, password
, syncableBackup
, &scratch
), "export items after changes"));
503 skip("skipping syncable backup tests", 0,
504 ok_status(_SecKeychainRestoreSyncable(keybag
, password
, syncableBackup
), "import items"));
506 CFReleaseNull(scratch
);
507 CFReleaseNull(error
);
509 // non-syncable item should (still) be gone -> add should work
510 test_add_managedconfiguration_item();
511 test_find_managedconfiguration_item();
513 // syncable item should have not been restored, because the tombstone was newer than the item in the backup -> copy matching should fail
514 is_status(errSecItemNotFound
, SecItemCopyMatching(query
, NULL
),
515 "find restored item");
516 is_status(errSecItemNotFound
, SecItemDelete(query
), "delete restored item");
518 CFReleaseSafe(syncableBackup
);
519 CFReleaseSafe(keybag
);
520 CFReleaseSafe(eighty
);
521 CFReleaseSafe(pwdata
);
522 CFReleaseSafe(query
);
525 int si_33_keychain_backup(int argc
, char *const *argv
)