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