2 * si-33-keychain-backup.c
5 * Created by Michael Brouwer on 1/30/10.
6 * Copyright 2010 Apple Inc. All rights reserved.
10 #include <TargetConditionals.h>
12 #if TARGET_OS_EMBEDDED && !TARGET_IPHONE_SIMULATOR
13 #define USE_KEYSTORE 1
14 #else /* No AppleKeyStore.kext on this OS. */
15 #define USE_KEYSTORE 0
19 #include <CoreFoundation/CoreFoundation.h>
20 #include <Security/SecBase.h>
21 #include <Security/SecItem.h>
22 #include <Security/SecInternal.h>
23 #include <Security/SecItemPriv.h>
24 #include <utilities/array_size.h>
27 #include <IOKit/IOKitLib.h>
28 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
37 #include "Security_regressions.h"
39 struct test_persistent_s
{
41 CFDictionaryRef query
;
42 CFDictionaryRef query1
;
43 CFDictionaryRef query2
;
44 CFMutableDictionaryRef query3
;
45 CFMutableDictionaryRef query4
;
48 static void test_persistent(struct test_persistent_s
*p
)
51 CFNumberRef eighty
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &v_eighty
);
52 const char *v_data
= "test";
53 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
54 const void *keys
[] = {
60 kSecAttrAuthenticationType
,
61 kSecReturnPersistentRef
,
64 const void *values
[] = {
65 kSecClassInternetPassword
,
74 CFDictionaryRef item
= CFDictionaryCreate(NULL
, keys
, values
,
75 array_size(keys
), &kCFTypeDictionaryKeyCallBacks
,
76 &kCFTypeDictionaryValueCallBacks
);
79 ok_status(SecItemAdd(item
, &p
->persist
[0]), "add internet password");
80 CFTypeRef results
= NULL
;
81 CFTypeRef results2
= NULL
;
83 skip("no persistent ref", 3, ok(p
->persist
[0], "got back persistent ref"));
85 /* Create a dict with all attrs except the data. */
86 keys
[(array_size(keys
)) - 2] = kSecReturnAttributes
;
87 p
->query
= CFDictionaryCreate(NULL
, keys
, values
,
88 (array_size(keys
)) - 1, &kCFTypeDictionaryKeyCallBacks
,
89 &kCFTypeDictionaryValueCallBacks
);
90 ok_status(SecItemCopyMatching(p
->query
, &results
), "find internet password by attr");
92 const void *keys_persist
[] = {
94 kSecValuePersistentRef
96 const void *values_persist
[] = {
100 p
->query2
= CFDictionaryCreate(NULL
, keys_persist
, values_persist
,
101 (array_size(keys_persist
)), &kCFTypeDictionaryKeyCallBacks
,
102 &kCFTypeDictionaryValueCallBacks
);
103 ok_status(SecItemCopyMatching(p
->query2
, &results2
), "find internet password by persistent ref");
104 ok(CFEqual(results
, results2
? results2
: CFSTR("")), "same item (attributes)");
106 CFReleaseNull(results
);
107 CFReleaseNull(results2
);
109 ok_status(SecItemDelete(p
->query
), "delete internet password");
111 ok_status(!SecItemCopyMatching(p
->query
, &results
),
112 "don't find internet password by attributes");
113 ok(!results
, "no results");
115 /* clean up left over from aborted run */
117 CFDictionaryRef cleanup
= CFDictionaryCreate(NULL
, &kSecValuePersistentRef
,
118 &results
, 1, &kCFTypeDictionaryKeyCallBacks
,
119 &kCFTypeDictionaryValueCallBacks
);
120 SecItemDelete(cleanup
);
125 ok_status(!SecItemCopyMatching(p
->query2
, &results2
),
126 "don't find internet password by persistent ref anymore");
127 ok(!results2
, "no results");
129 CFReleaseNull(p
->persist
[0]);
131 /* Add a new item and get it's persitant ref. */
132 ok_status(SecItemAdd(item
, &p
->persist
[0]), "add internet password");
133 p
->persist
[1] = NULL
;
134 CFMutableDictionaryRef item2
= CFDictionaryCreateMutableCopy(NULL
, 0, item
);
135 CFDictionarySetValue(item2
, kSecAttrAccount
, CFSTR("johndoe-bu"));
136 ok_status(SecItemAdd(item2
, &p
->persist
[1]), "add second internet password");
137 is(CFGetTypeID(p
->persist
[0]), CFDataGetTypeID(), "result is a CFData");
138 p
->query3
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
,
139 &kCFTypeDictionaryValueCallBacks
);
140 CFDictionaryAddValue(p
->query3
, kSecValuePersistentRef
, p
->persist
[0]);
141 CFMutableDictionaryRef update
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
142 CFDictionaryAddValue(update
, kSecAttrServer
, CFSTR("zuigt.com"));
143 ok_status(SecItemUpdate(p
->query3
, update
), "update via persitant ref");
145 /* Verify that the update really worked. */
146 CFDictionaryAddValue(p
->query3
, kSecReturnAttributes
, kCFBooleanTrue
);
147 ok_status(SecItemCopyMatching(p
->query3
, &results2
), "find updated internet password by persistent ref");
148 CFStringRef server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
149 ok(CFEqual(server
, CFSTR("zuigt.com")), "verify attribute was modified by update");
150 CFReleaseNull(results2
);
151 CFDictionaryRemoveValue(p
->query3
, kSecReturnAttributes
);
153 /* Verify that item2 wasn't affected by the update. */
154 p
->query4
= CFDictionaryCreateMutable(NULL
, 0, &kCFTypeDictionaryKeyCallBacks
,
155 &kCFTypeDictionaryValueCallBacks
);
156 CFDictionaryAddValue(p
->query4
, kSecValuePersistentRef
, p
->persist
[1]);
157 CFDictionaryAddValue(p
->query4
, kSecReturnAttributes
, kCFBooleanTrue
);
158 ok_status(SecItemCopyMatching(p
->query4
, &results2
), "find non updated internet password by persistent ref");
159 server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
160 ok(CFEqual(server
, CFSTR("zuigt.nl")), "verify second items attribute was not modified by update");
161 CFReleaseNull(results2
);
163 /* Delete the item via persitant ref. */
164 ok_status(SecItemDelete(p
->query3
), "delete via persitant ref");
165 is_status(SecItemCopyMatching(p
->query3
, &results2
), errSecItemNotFound
,
166 "don't find deleted internet password by persistent ref");
167 CFReleaseNull(results2
);
168 ok_status(SecItemCopyMatching(p
->query4
, &results2
),
169 "find non deleted internet password by persistent ref");
170 CFReleaseNull(results2
);
174 CFReleaseNull(item2
);
175 CFReleaseNull(eighty
);
176 CFReleaseNull(pwdata
);
179 static void test_persistent2(struct test_persistent_s
*p
)
181 CFTypeRef results
= NULL
;
182 CFTypeRef results2
= NULL
;
184 ok_status(!SecItemCopyMatching(p
->query
, &results
),
185 "don't find internet password by attributes");
186 ok(!results
, "no results");
188 ok_status(!SecItemCopyMatching(p
->query2
, &results2
),
189 "don't find internet password by persistent ref anymore");
190 ok(!results2
, "no results");
193 ok_status(SecItemCopyMatching(p
->query4
, &results2
), "find non updated internet password by persistent ref");
194 skip("non updated internet password by persistent ref NOT FOUND!", 2, results2
);
195 ok(results2
, "non updated internet password not found");
196 CFStringRef server
= CFDictionaryGetValue(results2
, kSecAttrServer
);
197 ok(CFEqual(server
, CFSTR("zuigt.nl")), "verify second items attribute was not modified by update");
198 CFReleaseNull(results2
);
201 is_status(SecItemCopyMatching(p
->query3
, &results2
), errSecItemNotFound
,
202 "don't find deleted internet password by persistent ref");
203 CFReleaseNull(results2
);
204 ok_status(SecItemCopyMatching(p
->query4
, &results2
),
205 "find non deleted internet password by persistent ref");
206 CFReleaseNull(results2
);
208 ok_status(SecItemDelete(p
->query4
),"Deleted internet password by persistent ref");
211 CFRelease(p
->query2
);
212 CFRelease(p
->query3
);
213 CFRelease(p
->query4
);
214 CFReleaseNull(p
->persist
[0]);
215 CFReleaseNull(p
->persist
[1]);
218 static CFMutableDictionaryRef
test_create_lockdown_identity_query(void) {
219 CFMutableDictionaryRef query
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
220 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
221 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("lockdown-identities"));
225 static CFMutableDictionaryRef
test_create_managedconfiguration_query(void) {
226 CFMutableDictionaryRef query
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
227 CFDictionaryAddValue(query
, kSecClass
, kSecClassGenericPassword
);
228 CFDictionaryAddValue(query
, kSecAttrService
, CFSTR("com.apple.managedconfiguration"));
229 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("Public"));
230 CFDictionaryAddValue(query
, kSecAttrAccessGroup
, CFSTR("apple"));
234 static void test_add_lockdown_identity_items(void) {
235 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
236 const char *v_data
= "lockdown identity data (which should be a cert + key)";
237 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
238 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
239 ok_status(SecItemAdd(query
, NULL
), "test_add_lockdown_identity_items");
240 CFReleaseSafe(pwdata
);
241 CFReleaseSafe(query
);
244 static void test_remove_lockdown_identity_items(void) {
245 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
246 ok_status(SecItemDelete(query
), "test_remove_lockdown_identity_items");
247 CFReleaseSafe(query
);
250 static void test_no_find_lockdown_identity_item(void) {
251 CFMutableDictionaryRef query
= test_create_lockdown_identity_query();
252 is_status(SecItemCopyMatching(query
, NULL
), errSecItemNotFound
,
253 "test_no_find_lockdown_identity_item");
254 CFReleaseSafe(query
);
257 static void test_add_managedconfiguration_item(void) {
258 CFMutableDictionaryRef query
= test_create_managedconfiguration_query();
259 const char *v_data
= "public managedconfiguration password history data";
260 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
261 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
262 ok_status(SecItemAdd(query
, NULL
), "test_add_managedconfiguration_item");
263 CFReleaseSafe(pwdata
);
264 CFReleaseSafe(query
);
267 static void test_find_managedconfiguration_item(void) {
268 CFMutableDictionaryRef query
= test_create_managedconfiguration_query();
269 ok_status(SecItemCopyMatching(query
, NULL
), "test_find_managedconfiguration_item");
270 ok_status(SecItemDelete(query
), "test_find_managedconfiguration_item (deleted)");
271 CFReleaseSafe(query
);
275 static io_connect_t
connect_to_keystore(void)
277 io_registry_entry_t apple_key_bag_service
;
278 kern_return_t result
;
279 io_connect_t keystore
= MACH_PORT_NULL
;
281 apple_key_bag_service
= IOServiceGetMatchingService(kIOMasterPortDefault
,
282 IOServiceMatching(kAppleKeyStoreServiceName
));
284 if (apple_key_bag_service
== IO_OBJECT_NULL
) {
285 fprintf(stderr
, "Failed to get service.\n");
289 result
= IOServiceOpen(apple_key_bag_service
, mach_task_self(), 0, &keystore
);
290 if (KERN_SUCCESS
!= result
)
291 fprintf(stderr
, "Failed to open keystore\n");
293 if (keystore
!= MACH_PORT_NULL
) {
294 IOReturn kernResult
= IOConnectCallMethod(keystore
,
295 kAppleKeyStoreUserClientOpen
, NULL
, 0, NULL
, 0, NULL
, NULL
,
298 fprintf(stderr
, "Failed to open AppleKeyStore: %x\n", kernResult
);
304 static CFDataRef
create_keybag(keybag_handle_t bag_type
, CFDataRef password
)
306 uint64_t inputs
[] = { bag_type
};
307 uint64_t outputs
[] = {0};
308 uint32_t num_inputs
= array_size(inputs
);
309 uint32_t num_outputs
= array_size(outputs
);
312 io_connect_t keystore
;
314 unsigned char keybagdata
[4096]; //Is that big enough?
315 size_t keybagsize
=sizeof(keybagdata
);
317 keystore
=connect_to_keystore();
319 kernResult
= IOConnectCallMethod(keystore
,
320 kAppleKeyStoreKeyBagCreate
,
321 inputs
, num_inputs
, NULL
, 0,
322 outputs
, &num_outputs
, NULL
, 0);
325 fprintf(stderr
, "kAppleKeyStoreKeyBagCreate: 0x%x\n", kernResult
);
329 /* Copy out keybag */
330 inputs
[0]=outputs
[0];
333 kernResult
= IOConnectCallMethod(keystore
,
334 kAppleKeyStoreKeyBagCopy
,
335 inputs
, num_inputs
, NULL
, 0,
336 NULL
, 0, keybagdata
, &keybagsize
);
339 fprintf(stderr
, "kAppleKeyStoreKeyBagCopy: 0x%x\n", kernResult
);
343 return CFDataCreate(kCFAllocatorDefault
, keybagdata
, keybagsize
);
347 /* Test low level keychain migration from device to device interface. */
348 static void tests(void)
351 CFNumberRef eighty
= CFNumberCreate(NULL
, kCFNumberSInt32Type
, &v_eighty
);
352 const char *v_data
= "test";
353 CFDataRef pwdata
= CFDataCreate(NULL
, (UInt8
*)v_data
, strlen(v_data
));
354 CFMutableDictionaryRef query
= CFDictionaryCreateMutable(NULL
, 0, NULL
, NULL
);
355 CFDictionaryAddValue(query
, kSecClass
, kSecClassInternetPassword
);
356 CFDictionaryAddValue(query
, kSecAttrServer
, CFSTR("members.spamcop.net"));
357 CFDictionaryAddValue(query
, kSecAttrAccount
, CFSTR("smith"));
358 CFDictionaryAddValue(query
, kSecAttrPort
, eighty
);
359 CFDictionaryAddValue(query
, kSecAttrProtocol
, kSecAttrProtocolHTTP
);
360 CFDictionaryAddValue(query
, kSecAttrAuthenticationType
, kSecAttrAuthenticationTypeDefault
);
361 CFDictionaryAddValue(query
, kSecValueData
, pwdata
);
362 ok_status(SecItemAdd(query
, NULL
), "add internet password");
363 is_status(SecItemAdd(query
, NULL
), errSecDuplicateItem
,
364 "add internet password again");
366 ok_status(SecItemCopyMatching(query
, NULL
), "Found the item we added");
368 struct test_persistent_s p
= {};
371 CFDataRef backup
= NULL
, keybag
= NULL
, password
= NULL
;
373 test_add_lockdown_identity_items();
376 keybag
= create_keybag(kAppleKeyStoreBackupBag
, password
);
378 keybag
= CFDataCreate(kCFAllocatorDefault
, NULL
, 0);
381 ok(backup
= _SecKeychainCopyBackup(keybag
, password
),
382 "_SecKeychainCopyBackup");
384 test_add_managedconfiguration_item();
385 test_remove_lockdown_identity_items();
387 ok_status(_SecKeychainRestoreBackup(backup
, keybag
, password
),
388 "_SecKeychainRestoreBackup");
389 CFReleaseSafe(backup
);
391 test_no_find_lockdown_identity_item();
392 test_find_managedconfiguration_item();
394 ok_status(SecItemCopyMatching(query
, NULL
),
395 "Found the item we added after restore");
397 test_persistent2(&p
);
400 CFReleaseNull(keybag
);
401 keybag
= create_keybag(kAppleKeyStoreOTABackupBag
, password
);
404 ok(backup
= _SecKeychainCopyBackup(keybag
, password
),
405 "_SecKeychainCopyBackup");
406 ok_status(_SecKeychainRestoreBackup(backup
, keybag
, password
),
407 "_SecKeychainRestoreBackup");
408 ok_status(SecItemCopyMatching(query
, NULL
),
409 "Found the item we added after restore");
410 CFReleaseNull(backup
);
412 CFDictionaryAddValue(query
, kSecUseTombstones
, kCFBooleanTrue
);
414 ok_status(SecItemDelete(query
), "Deleted item we added");
417 CFReleaseNull(keybag
);
418 keybag
= create_keybag(kAppleKeyStoreOTABackupBag
/* use truthiness bag once it's there */, password
);
422 CFDictionaryAddValue(query
, kSecAttrSynchronizable
, kCFBooleanTrue
);
423 ok_status(SecItemAdd(query
, NULL
), "add internet password");
425 // and non-syncable item
426 test_add_managedconfiguration_item();
428 CFDictionaryRef syncableBackup
= NULL
;
430 ok_status(_SecKeychainBackupSyncable(keybag
, password
, NULL
, &syncableBackup
), "export items");
432 // TODO: add item, call SecServerCopyTruthInTheCloud again
434 // CFShow(syncableBackup);
437 ok_status(SecItemCopyMatching(query
, NULL
), "find item we are about to destroy");
439 ok_status(SecItemDelete(query
), "delete item we backed up");
441 // ensure we added a tombstone
442 CFDictionaryAddValue(query
, kSecAttrTombstone
, kCFBooleanTrue
);
443 ok_status(SecItemCopyMatching(query
, NULL
), "find tombstone for item we deleted");
444 CFDictionaryRemoveValue(query
, kSecAttrTombstone
);
446 test_find_managedconfiguration_item();
448 // TODO: add a different new item - delete what's not in the syncableBackup?
450 // Do another backup after some changes
451 CFDictionaryRef scratch
= NULL
;
452 ok_status(_SecKeychainBackupSyncable(keybag
, password
, syncableBackup
, &scratch
), "export items after changes");
453 CFReleaseNull(scratch
);
455 ok_status(_SecKeychainRestoreSyncable(keybag
, password
, syncableBackup
), "import items");
457 // non-syncable item should (still) be gone -> add should work
458 test_add_managedconfiguration_item();
459 test_find_managedconfiguration_item();
461 // syncable item should have not been restored, because the tombstone was newer than the item in the backup -> copy matching should fail
462 is_status(errSecItemNotFound
, SecItemCopyMatching(query
, NULL
),
463 "find restored item");
464 is_status(errSecItemNotFound
, SecItemDelete(query
), "delete restored item");
466 CFReleaseSafe(syncableBackup
);
467 CFReleaseSafe(keybag
);
468 CFReleaseSafe(eighty
);
469 CFReleaseSafe(pwdata
);
470 CFReleaseSafe(query
);
473 int si_33_keychain_backup(int argc
, char *const *argv
)