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