1/* 2 * si-33-keychain-backup.c 3 * Security 4 * 5 * Created by Michael Brouwer on 1/30/10. 6 * Copyright 2010 Apple Inc. All rights reserved. 7 * 8 */ 9 10#include <TargetConditionals.h> 11 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 16#endif 17 18 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> 25 26#if USE_KEYSTORE 27#include <IOKit/IOKitLib.h> 28#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h> 29#endif 30 31#include <stdlib.h> 32#include <fcntl.h> 33#include <unistd.h> 34#include <sys/stat.h> 35#include <sqlite3.h> 36 37#include "Security_regressions.h" 38 39struct test_persistent_s { 40 CFTypeRef persist[2]; 41 CFDictionaryRef query; 42 CFDictionaryRef query1; 43 CFDictionaryRef query2; 44 CFMutableDictionaryRef query3; 45 CFMutableDictionaryRef query4; 46}; 47 48static void test_persistent(struct test_persistent_s *p) 49{ 50 int v_eighty = 80; 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[] = { 55 kSecClass, 56 kSecAttrServer, 57 kSecAttrAccount, 58 kSecAttrPort, 59 kSecAttrProtocol, 60 kSecAttrAuthenticationType, 61 kSecReturnPersistentRef, 62 kSecValueData 63 }; 64 const void *values[] = { 65 kSecClassInternetPassword, 66 CFSTR("zuigt.nl"), 67 CFSTR("frtnbf"), 68 eighty, 69 CFSTR("http"), 70 CFSTR("dflt"), 71 kCFBooleanTrue, 72 pwdata 73 }; 74 CFDictionaryRef item = CFDictionaryCreate(NULL, keys, values, 75 array_size(keys), &kCFTypeDictionaryKeyCallBacks, 76 &kCFTypeDictionaryValueCallBacks); 77 78 p->persist[0] = NULL; 79 ok_status(SecItemAdd(item, &p->persist[0]), "add internet password"); 80 CFTypeRef results = NULL; 81 CFTypeRef results2 = NULL; 82 SKIP: { 83 skip("no persistent ref", 3, ok(p->persist[0], "got back persistent ref")); 84 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"); 91 92 const void *keys_persist[] = { 93 kSecReturnAttributes, 94 kSecValuePersistentRef 95 }; 96 const void *values_persist[] = { 97 kCFBooleanTrue, 98 p->persist[0] 99 }; 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)"); 105 106 CFReleaseNull(results); 107 CFReleaseNull(results2); 108 } 109 ok_status(SecItemDelete(p->query), "delete internet password"); 110 111 ok_status(!SecItemCopyMatching(p->query, &results), 112 "don't find internet password by attributes"); 113 ok(!results, "no results"); 114 115 /* clean up left over from aborted run */ 116 if (results) { 117 CFDictionaryRef cleanup = CFDictionaryCreate(NULL, &kSecValuePersistentRef, 118 &results, 1, &kCFTypeDictionaryKeyCallBacks, 119 &kCFTypeDictionaryValueCallBacks); 120 SecItemDelete(cleanup); 121 CFRelease(results); 122 CFRelease(cleanup); 123 } 124 125 ok_status(!SecItemCopyMatching(p->query2, &results2), 126 "don't find internet password by persistent ref anymore"); 127 ok(!results2, "no results"); 128 129 CFReleaseNull(p->persist[0]); 130 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"); 144 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); 152 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); 162 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); 171 172 CFRelease(update); 173 CFReleaseNull(item); 174 CFReleaseNull(item2); 175 CFReleaseNull(eighty); 176 CFReleaseNull(pwdata); 177} 178 179static void test_persistent2(struct test_persistent_s *p) 180{ 181 CFTypeRef results = NULL; 182 CFTypeRef results2 = NULL; 183 184 ok_status(!SecItemCopyMatching(p->query, &results), 185 "don't find internet password by attributes"); 186 ok(!results, "no results"); 187 188 ok_status(!SecItemCopyMatching(p->query2, &results2), 189 "don't find internet password by persistent ref anymore"); 190 ok(!results2, "no results"); 191 192 SKIP:{ 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); 199 } 200 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); 207 208 ok_status(SecItemDelete(p->query4),"Deleted internet password by persistent ref"); 209 210 CFRelease(p->query); 211 CFRelease(p->query2); 212 CFRelease(p->query3); 213 CFRelease(p->query4); 214 CFReleaseNull(p->persist[0]); 215 CFReleaseNull(p->persist[1]); 216} 217 218static 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")); 222 return query; 223} 224 225static 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")); 231 return query; 232} 233 234static 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); 242} 243 244static 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); 248} 249 250static 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); 255} 256 257static 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); 265} 266 267static 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); 272} 273 274#if USE_KEYSTORE 275static io_connect_t connect_to_keystore(void) 276{ 277 io_registry_entry_t apple_key_bag_service; 278 kern_return_t result; 279 io_connect_t keystore = MACH_PORT_NULL; 280 281 apple_key_bag_service = IOServiceGetMatchingService(kIOMasterPortDefault, 282 IOServiceMatching(kAppleKeyStoreServiceName)); 283 284 if (apple_key_bag_service == IO_OBJECT_NULL) { 285 fprintf(stderr, "Failed to get service.\n"); 286 return keystore; 287 } 288 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"); 292 293 if (keystore != MACH_PORT_NULL) { 294 IOReturn kernResult = IOConnectCallMethod(keystore, 295 kAppleKeyStoreUserClientOpen, NULL, 0, NULL, 0, NULL, NULL, 296 NULL, NULL); 297 if (kernResult) { 298 fprintf(stderr, "Failed to open AppleKeyStore: %x\n", kernResult); 299 } 300 } 301 return keystore; 302} 303 304static CFDataRef create_keybag(keybag_handle_t bag_type, CFDataRef password) 305{ 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); 310 IOReturn kernResult; 311 312 io_connect_t keystore; 313 314 unsigned char keybagdata[4096]; //Is that big enough? 315 size_t keybagsize=sizeof(keybagdata); 316 317 keystore=connect_to_keystore(); 318 319 kernResult = IOConnectCallMethod(keystore, 320 kAppleKeyStoreKeyBagCreate, 321 inputs, num_inputs, NULL, 0, 322 outputs, &num_outputs, NULL, 0); 323 324 if (kernResult) { 325 fprintf(stderr, "kAppleKeyStoreKeyBagCreate: 0x%x\n", kernResult); 326 return NULL; 327 } 328 329 /* Copy out keybag */ 330 inputs[0]=outputs[0]; 331 num_inputs=1; 332 333 kernResult = IOConnectCallMethod(keystore, 334 kAppleKeyStoreKeyBagCopy, 335 inputs, num_inputs, NULL, 0, 336 NULL, 0, keybagdata, &keybagsize); 337 338 if (kernResult) { 339 fprintf(stderr, "kAppleKeyStoreKeyBagCopy: 0x%x\n", kernResult); 340 return NULL; 341 } 342 343 return CFDataCreate(kCFAllocatorDefault, keybagdata, keybagsize); 344} 345#endif 346 347/* Test low level keychain migration from device to device interface. */ 348static void tests(void) 349{ 350 int v_eighty = 80; 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"); 365 366 ok_status(SecItemCopyMatching(query, NULL), "Found the item we added"); 367 368 struct test_persistent_s p = {}; 369 test_persistent(&p); 370 371 CFDataRef backup = NULL, keybag = NULL, password = NULL; 372 373 test_add_lockdown_identity_items(); 374 375#if USE_KEYSTORE 376 keybag = create_keybag(kAppleKeyStoreBackupBag, password); 377#else 378 keybag = CFDataCreate(kCFAllocatorDefault, NULL, 0); 379#endif 380 381 ok(backup = _SecKeychainCopyBackup(keybag, password), 382 "_SecKeychainCopyBackup"); 383 384 test_add_managedconfiguration_item(); 385 test_remove_lockdown_identity_items(); 386 387 ok_status(_SecKeychainRestoreBackup(backup, keybag, password), 388 "_SecKeychainRestoreBackup"); 389 CFReleaseSafe(backup); 390 391 test_no_find_lockdown_identity_item(); 392 test_find_managedconfiguration_item(); 393 394 ok_status(SecItemCopyMatching(query, NULL), 395 "Found the item we added after restore"); 396 397 test_persistent2(&p); 398 399#if USE_KEYSTORE 400 CFReleaseNull(keybag); 401 keybag = create_keybag(kAppleKeyStoreOTABackupBag, password); 402#endif 403 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); 411 412 CFDictionaryAddValue(query, kSecUseTombstones, kCFBooleanTrue); 413 414 ok_status(SecItemDelete(query), "Deleted item we added"); 415 416#if USE_KEYSTORE 417 CFReleaseNull(keybag); 418 keybag = create_keybag(kAppleKeyStoreOTABackupBag /* use truthiness bag once it's there */, password); 419#endif 420 421 // add syncable item 422 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue); 423 ok_status(SecItemAdd(query, NULL), "add internet password"); 424 425 // and non-syncable item 426 test_add_managedconfiguration_item(); 427 428 CFDictionaryRef syncableBackup = NULL; 429 430 ok_status(_SecKeychainBackupSyncable(keybag, password, NULL, &syncableBackup), "export items"); 431 432 // TODO: add item, call SecServerCopyTruthInTheCloud again 433 434 // CFShow(syncableBackup); 435 436 // find and delete 437 ok_status(SecItemCopyMatching(query, NULL), "find item we are about to destroy"); 438 439 ok_status(SecItemDelete(query), "delete item we backed up"); 440 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); 445 446 test_find_managedconfiguration_item(); 447 448 // TODO: add a different new item - delete what's not in the syncableBackup? 449 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); 454 455 ok_status(_SecKeychainRestoreSyncable(keybag, password, syncableBackup), "import items"); 456 457 // non-syncable item should (still) be gone -> add should work 458 test_add_managedconfiguration_item(); 459 test_find_managedconfiguration_item(); 460 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"); 465 466 CFReleaseSafe(syncableBackup); 467 CFReleaseSafe(keybag); 468 CFReleaseSafe(eighty); 469 CFReleaseSafe(pwdata); 470 CFReleaseSafe(query); 471} 472 473int si_33_keychain_backup(int argc, char *const *argv) 474{ 475 plan_tests(62); 476 477 478 tests(); 479 480 return 0; 481} 482