1/* 2 * Copyright (c) 2010,2012-2014 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 25#include <TargetConditionals.h> 26 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 31#endif 32 33 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 41#if USE_KEYSTORE 42#include <IOKit/IOKitLib.h> 43#include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h> 44#endif 45 46#include <stdlib.h> 47#include <fcntl.h> 48#include <unistd.h> 49#include <sys/stat.h> 50#include <sqlite3.h> 51 52#include "../Security_regressions.h" 53 54struct test_persistent_s { 55 CFTypeRef persist[2]; 56 CFDictionaryRef query; 57 CFDictionaryRef query1; 58 CFDictionaryRef query2; 59 CFMutableDictionaryRef query3; 60 CFMutableDictionaryRef query4; 61}; 62 63static void test_persistent(struct test_persistent_s *p) 64{ 65 int v_eighty = 80; 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[] = { 70 kSecClass, 71 kSecAttrServer, 72 kSecAttrAccount, 73 kSecAttrPort, 74 kSecAttrProtocol, 75 kSecAttrAuthenticationType, 76 kSecReturnPersistentRef, 77 kSecValueData 78 }; 79 const void *values[] = { 80 kSecClassInternetPassword, 81 CFSTR("zuigt.nl"), 82 CFSTR("frtnbf"), 83 eighty, 84 CFSTR("http"), 85 CFSTR("dflt"), 86 kCFBooleanTrue, 87 pwdata 88 }; 89 CFDictionaryRef item = CFDictionaryCreate(NULL, keys, values, 90 array_size(keys), &kCFTypeDictionaryKeyCallBacks, 91 &kCFTypeDictionaryValueCallBacks); 92 93 p->persist[0] = NULL; 94 // NUKE anything we might have left around from a previous test run so we don't crash. 95 SecItemDelete(item); 96 ok_status(SecItemAdd(item, &p->persist[0]), "add internet password"); 97 CFTypeRef results = NULL; 98 CFTypeRef results2 = NULL; 99 SKIP: { 100 skip("no persistent ref", 6, ok(p->persist[0], "got back persistent ref")); 101 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"); 108 109 const void *keys_persist[] = { 110 kSecReturnAttributes, 111 kSecValuePersistentRef 112 }; 113 const void *values_persist[] = { 114 kCFBooleanTrue, 115 p->persist[0] 116 }; 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)"); 122 123 CFReleaseNull(results); 124 CFReleaseNull(results2); 125 126 ok_status(SecItemDelete(p->query), "delete internet password"); 127 128 ok_status(!SecItemCopyMatching(p->query, &results), 129 "don't find internet password by attributes"); 130 ok(!results, "no results"); 131 } 132 133 /* clean up left over from aborted run */ 134 if (results) { 135 CFDictionaryRef cleanup = CFDictionaryCreate(NULL, &kSecValuePersistentRef, 136 &results, 1, &kCFTypeDictionaryKeyCallBacks, 137 &kCFTypeDictionaryValueCallBacks); 138 SecItemDelete(cleanup); 139 CFRelease(results); 140 CFRelease(cleanup); 141 } 142 143 ok_status(!SecItemCopyMatching(p->query2, &results2), 144 "don't find internet password by persistent ref anymore"); 145 ok(!results2, "no results"); 146 147 CFReleaseNull(p->persist[0]); 148 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; 159SKIP: { 160 skip("no persistent ref", 3, ok(p->persist[0], "got back persistent ref from first internet password")); 161 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"); 169 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); 177} 178 179SKIP: { 180 skip("no persistent ref", 2, ok(p->persist[1], "got back persistent ref")); 181 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); 191} 192 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); 201 202 CFReleaseNull(update); 203 CFReleaseNull(item); 204 CFReleaseNull(item2); 205 CFReleaseNull(eighty); 206 CFReleaseNull(pwdata); 207} 208 209static void test_persistent2(struct test_persistent_s *p) 210{ 211 CFTypeRef results = NULL; 212 CFTypeRef results2 = NULL; 213 214 ok_status(!SecItemCopyMatching(p->query, &results), 215 "don't find internet password by attributes"); 216 ok(!results, "no results"); 217 218 ok_status(!SecItemCopyMatching(p->query2, &results2), 219 "don't find internet password by persistent ref anymore"); 220 ok(!results2, "no results"); 221 222 SKIP:{ 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); 229 } 230 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); 237 238 ok_status(SecItemDelete(p->query4),"Deleted internet password by persistent ref"); 239 240 CFRelease(p->query); 241 CFRelease(p->query2); 242 CFRelease(p->query3); 243 CFRelease(p->query4); 244 CFReleaseNull(p->persist[0]); 245 CFReleaseNull(p->persist[1]); 246} 247 248static CFMutableDictionaryRef test_create_lockdown_identity_query(void) { 249 CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); 250 CFDictionaryAddValue(query, kSecClass, kSecClassGenericPassword); 251 CFDictionaryAddValue(query, kSecAttrAccessGroup, CFSTR("lockdown-identities")); 252 return query; 253} 254 255static CFMutableDictionaryRef test_create_managedconfiguration_query(void) { 256 CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, NULL, 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")); 261 return query; 262} 263 264static 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); 272} 273 274static 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); 278} 279 280static 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); 285} 286 287static void test_add_managedconfiguration_item(void) { 288 CFMutableDictionaryRef query = test_create_managedconfiguration_query(); 289 const char *v_data = "public managedconfiguration password history data"; 290 CFDataRef pwdata = CFDataCreate(NULL, (UInt8 *)v_data, strlen(v_data)); 291 CFDictionaryAddValue(query, kSecValueData, pwdata); 292 ok_status(SecItemAdd(query, NULL), "test_add_managedconfiguration_item"); 293 CFReleaseSafe(pwdata); 294 CFReleaseSafe(query); 295} 296 297static void test_find_managedconfiguration_item(void) { 298 CFMutableDictionaryRef query = test_create_managedconfiguration_query(); 299 ok_status(SecItemCopyMatching(query, NULL), "test_find_managedconfiguration_item"); 300 ok_status(SecItemDelete(query), "test_find_managedconfiguration_item (deleted)"); 301 CFReleaseSafe(query); 302} 303 304#if USE_KEYSTORE 305static io_connect_t connect_to_keystore(void) 306{ 307 io_registry_entry_t apple_key_bag_service; 308 kern_return_t result; 309 io_connect_t keystore = MACH_PORT_NULL; 310 311 apple_key_bag_service = IOServiceGetMatchingService(kIOMasterPortDefault, 312 IOServiceMatching(kAppleKeyStoreServiceName)); 313 314 if (apple_key_bag_service == IO_OBJECT_NULL) { 315 fprintf(stderr, "Failed to get service.\n"); 316 return keystore; 317 } 318 319 result = IOServiceOpen(apple_key_bag_service, mach_task_self(), 0, &keystore); 320 if (KERN_SUCCESS != result) 321 fprintf(stderr, "Failed to open keystore\n"); 322 323 if (keystore != MACH_PORT_NULL) { 324 IOReturn kernResult = IOConnectCallMethod(keystore, 325 kAppleKeyStoreUserClientOpen, NULL, 0, NULL, 0, NULL, NULL, 326 NULL, NULL); 327 if (kernResult) { 328 fprintf(stderr, "Failed to open AppleKeyStore: %x\n", kernResult); 329 } 330 } 331 return keystore; 332} 333 334static CFDataRef create_keybag(keybag_handle_t bag_type, CFDataRef password) 335{ 336 uint64_t inputs[] = { bag_type }; 337 uint64_t outputs[] = {0}; 338 uint32_t num_inputs = array_size(inputs); 339 uint32_t num_outputs = array_size(outputs); 340 IOReturn kernResult; 341 342 io_connect_t keystore; 343 344 unsigned char keybagdata[4096]; //Is that big enough? 345 size_t keybagsize=sizeof(keybagdata); 346 347 keystore=connect_to_keystore(); 348 349 kernResult = IOConnectCallMethod(keystore, 350 kAppleKeyStoreKeyBagCreate, 351 inputs, num_inputs, NULL, 0, 352 outputs, &num_outputs, NULL, 0); 353 354 if (kernResult) { 355 fprintf(stderr, "kAppleKeyStoreKeyBagCreate: 0x%x\n", kernResult); 356 return NULL; 357 } 358 359 /* Copy out keybag */ 360 inputs[0]=outputs[0]; 361 num_inputs=1; 362 363 kernResult = IOConnectCallMethod(keystore, 364 kAppleKeyStoreKeyBagCopy, 365 inputs, num_inputs, NULL, 0, 366 NULL, 0, keybagdata, &keybagsize); 367 368 if (kernResult) { 369 fprintf(stderr, "kAppleKeyStoreKeyBagCopy: 0x%x\n", kernResult); 370 return NULL; 371 } 372 373 return CFDataCreate(kCFAllocatorDefault, keybagdata, keybagsize); 374} 375#endif 376 377/* Test low level keychain migration from device to device interface. */ 378static void tests(void) 379{ 380 int v_eighty = 80; 381 CFNumberRef eighty = CFNumberCreate(NULL, kCFNumberSInt32Type, &v_eighty); 382 const char *v_data = "test"; 383 CFDataRef pwdata = CFDataCreate(NULL, (UInt8 *)v_data, strlen(v_data)); 384 CFMutableDictionaryRef query = CFDictionaryCreateMutable(NULL, 0, NULL, NULL); 385 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword); 386 CFDictionaryAddValue(query, kSecAttrServer, CFSTR("members.spamcop.net")); 387 CFDictionaryAddValue(query, kSecAttrAccount, CFSTR("smith")); 388 CFDictionaryAddValue(query, kSecAttrPort, eighty); 389 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTP); 390 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeDefault); 391 CFDictionaryAddValue(query, kSecValueData, pwdata); 392 // NUKE anything we might have left around from a previous test run so we don't crash. 393 SecItemDelete(query); 394 ok_status(SecItemAdd(query, NULL), "add internet password"); 395 is_status(SecItemAdd(query, NULL), errSecDuplicateItem, 396 "add internet password again"); 397 398 ok_status(SecItemCopyMatching(query, NULL), "Found the item we added"); 399 400 struct test_persistent_s p = {}; 401 test_persistent(&p); 402 403 CFDataRef backup = NULL, keybag = NULL, password = NULL; 404 405 test_add_lockdown_identity_items(); 406 407#if USE_KEYSTORE 408 keybag = create_keybag(kAppleKeyStoreBackupBag, password); 409#else 410 keybag = CFDataCreate(kCFAllocatorDefault, NULL, 0); 411#endif 412 413 ok(backup = _SecKeychainCopyBackup(keybag, password), 414 "_SecKeychainCopyBackup"); 415 416 test_add_managedconfiguration_item(); 417 test_remove_lockdown_identity_items(); 418 419 ok_status(_SecKeychainRestoreBackup(backup, keybag, password), 420 "_SecKeychainRestoreBackup"); 421 CFReleaseSafe(backup); 422 423 test_no_find_lockdown_identity_item(); 424 test_find_managedconfiguration_item(); 425 426 ok_status(SecItemCopyMatching(query, NULL), 427 "Found the item we added after restore"); 428 429 test_persistent2(&p); 430 431#if USE_KEYSTORE 432 CFReleaseNull(keybag); 433 keybag = create_keybag(kAppleKeyStoreOTABackupBag, password); 434#endif 435 436 ok(backup = _SecKeychainCopyBackup(keybag, password), 437 "_SecKeychainCopyBackup"); 438 ok_status(_SecKeychainRestoreBackup(backup, keybag, password), 439 "_SecKeychainRestoreBackup"); 440 ok_status(SecItemCopyMatching(query, NULL), 441 "Found the item we added after restore"); 442 CFReleaseNull(backup); 443 444 // force tombstone to be added, since it's not the default behavior per rdar://14680869 445 CFDictionaryAddValue(query, kSecUseTombstones, kCFBooleanTrue); 446 447 ok_status(SecItemDelete(query), "Deleted item we added"); 448 449#if USE_KEYSTORE 450 CFReleaseNull(keybag); 451 keybag = create_keybag(kAppleKeyStoreOTABackupBag /* use truthiness bag once it's there */, password); 452#endif 453 454 // add syncable item 455 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue); 456 ok_status(SecItemAdd(query, NULL), "add internet password"); 457 458 // and non-syncable item 459 test_add_managedconfiguration_item(); 460 461 CFDictionaryRef syncableBackup = NULL; 462 463 CFErrorRef error = NULL; 464 CFDictionaryRef scratch = NULL; 465 SKIP: { 466 skip("skipping syncable backup tests", 7, 467 ok_status(_SecKeychainBackupSyncable(keybag, password, NULL, &syncableBackup), "export items")); 468 469 // TODO: add item, call SecServerCopyTruthInTheCloud again 470 471 // CFShow(syncableBackup); 472 473 // find and delete 474 skip("skipping syncable backup tests", 6, 475 ok_status(SecItemCopyMatching(query, NULL), "find item we are about to destroy")); 476 477 skip("skipping syncable backup tests", 5, 478 ok_status(SecItemDelete(query), "delete item we backed up")); 479 480 // ensure we added a tombstone 481 CFDictionaryAddValue(query, kSecAttrTombstone, kCFBooleanTrue); 482 skip("skipping syncable backup tests", 4, 483 ok_status(SecItemCopyMatching(query, NULL), "find tombstone for item we deleted")); 484 CFDictionaryRemoveValue(query, kSecAttrTombstone); 485 486 test_find_managedconfiguration_item(); // <- 2 tests here 487 488 // TODO: add a different new item - delete what's not in the syncableBackup? 489 490 // Do another backup after some changes 491 skip("skipping syncable backup tests", 1, 492 ok_status(_SecKeychainBackupSyncable(keybag, password, syncableBackup, &scratch), "export items after changes")); 493 494 skip("skipping syncable backup tests", 0, 495 ok_status(_SecKeychainRestoreSyncable(keybag, password, syncableBackup), "import items")); 496 } 497 CFReleaseNull(scratch); 498 CFReleaseNull(error); 499 500 // non-syncable item should (still) be gone -> add should work 501 test_add_managedconfiguration_item(); 502 test_find_managedconfiguration_item(); 503 504 // syncable item should have not been restored, because the tombstone was newer than the item in the backup -> copy matching should fail 505 is_status(errSecItemNotFound, SecItemCopyMatching(query, NULL), 506 "find restored item"); 507 is_status(errSecItemNotFound, SecItemDelete(query), "delete restored item"); 508 509 CFReleaseSafe(syncableBackup); 510 CFReleaseSafe(keybag); 511 CFReleaseSafe(eighty); 512 CFReleaseSafe(pwdata); 513 CFReleaseSafe(query); 514} 515 516int si_33_keychain_backup(int argc, char *const *argv) 517{ 518 plan_tests(64); 519 520 tests(); 521 522 return 0; 523} 524