1/*
2 * Copyright (c) 2006-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 * SecItemServer.c - CoreFoundation-based constants and functions for
26    access to Security items (certificates, keys, identities, and
27    passwords.)
28 */
29
30#include <securityd/SecItemServer.h>
31
32#include <notify.h>
33#include <securityd/SecItemDataSource.h>
34#include <securityd/SecItemDb.h>
35#include <securityd/SecItemSchema.h>
36#include <securityd/SOSCloudCircleServer.h>
37#include <Security/SecBasePriv.h>
38#include <Security/SecItemPriv.h>
39#include <Security/SecItemInternal.h>
40#include <SecureObjectSync/SOSDigestVector.h>
41
42// TODO: Make this include work on both platforms. rdar://problem/16526848
43#if TARGET_OS_EMBEDDED
44#include <Security/SecEntitlements.h>
45#else
46/* defines from <Security/SecEntitlements.h> */
47#define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
48#define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
49#endif
50
51#include <utilities/array_size.h>
52#include <utilities/SecFileLocations.h>
53#include <Security/SecuritydXPC.h>
54#include "swcagent_client.h"
55
56#if TARGET_OS_IPHONE
57#include <SharedWebCredentials/SharedWebCredentials.h>
58#else
59typedef uint32_t SWCFlags;
60#define kSWCFlags_None				0
61#define kSWCFlag_Pending			( 1U << 0 )
62#define kSWCFlag_SiteApproved		( 1U << 1 )
63#define kSWCFlag_SiteDenied			( 1U << 2 )
64#define kSWCFlag_UserApproved		( 1U << 3 )
65#define kSWCFlag_UserDenied			( 1U << 4 )
66#define kSWCFlag_ExternalMask		( kSWCFlag_UserApproved | kSWCFlag_UserDenied )
67#endif
68
69/* Changed the name of the keychain changed notification, for testing */
70static const char *g_keychain_changed_notification = kSecServerKeychainChangedNotification;
71
72void SecItemServerSetKeychainChangedNotification(const char *notification_name)
73{
74    g_keychain_changed_notification = notification_name;
75}
76
77void SecKeychainChanged(bool syncWithPeers) {
78    uint32_t result = notify_post(g_keychain_changed_notification);
79    if (syncWithPeers)
80        SOSCCSyncWithAllPeers();
81    if (result == NOTIFY_STATUS_OK)
82        secnotice("item", "Sent %s%s", syncWithPeers ? "SyncWithAllPeers and " : "", g_keychain_changed_notification);
83    else
84        secerror("%snotify_post %s returned: %" PRIu32, syncWithPeers ? "Sent SyncWithAllPeers, " : "", g_keychain_changed_notification, result);
85    }
86
87static const char * const s3dl_upgrade_sql[] = {
88    /* 0 */
89    "",
90
91    /* 1 */
92    /* Create indices. */
93    "CREATE INDEX igsha ON genp(sha1);"
94    "CREATE INDEX iisha ON inet(sha1);"
95    "CREATE INDEX icsha ON cert(sha1);"
96    "CREATE INDEX iksha ON keys(sha1);"
97    "CREATE INDEX ialis ON cert(alis);"
98    "CREATE INDEX isubj ON cert(subj);"
99    "CREATE INDEX iskid ON cert(skid);"
100    "CREATE INDEX ipkhh ON cert(pkhh);"
101    "CREATE INDEX ikcls ON keys(kcls);"
102    "CREATE INDEX iklbl ON keys(klbl);"
103    "CREATE INDEX iencr ON keys(encr);"
104    "CREATE INDEX idecr ON keys(decr);"
105    "CREATE INDEX idrve ON keys(drve);"
106    "CREATE INDEX isign ON keys(sign);"
107    "CREATE INDEX ivrfy ON keys(vrfy);"
108    "CREATE INDEX iwrap ON keys(wrap);"
109    "CREATE INDEX iunwp ON keys(unwp);",
110
111    /* 2 */
112    "",
113
114    /* 3 */
115    /* Rename version 2 or version 3 tables and drop version table since
116       step 0 creates it. */
117    "ALTER TABLE genp RENAME TO ogenp;"
118    "ALTER TABLE inet RENAME TO oinet;"
119    "ALTER TABLE cert RENAME TO ocert;"
120    "ALTER TABLE keys RENAME TO okeys;"
121    "DROP TABLE tversion;",
122
123    /* 4 */
124    "",
125
126    /* 5 */
127    "",
128
129    /* 6 */
130    "",
131
132    /* 7 */
133    /* Move data from version 5 tables to new ones and drop old ones. */
134    "INSERT INTO genp (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp,pdmn) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,svce,gena,data,agrp,pdmn from ogenp;"
135    "INSERT INTO inet (rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp,pdmn) SELECT rowid,cdat,mdat,desc,icmt,crtr,type,scrp,labl,alis,invi,nega,cusi,prot,acct,sdmn,srvr,ptcl,atyp,port,path,data,agrp,pdmn from oinet;"
136    "INSERT INTO cert (rowid,cdat,mdat,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn) SELECT rowid,cdat,mdat,ctyp,cenc,labl,alis,subj,issr,slnr,skid,pkhh,data,agrp,pdmn from ocert;"
137    "INSERT INTO keys (rowid,cdat,mdat,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp,pdmn) SELECT rowid,cdat,mdat,kcls,labl,alis,perm,priv,modi,klbl,atag,crtr,type,bsiz,esiz,sdat,edat,sens,asen,extr,next,encr,decr,drve,sign,vrfy,snrc,vyrc,wrap,unwp,data,agrp,pdmn from okeys;"
138    "DROP TABLE ogenp;"
139    "DROP TABLE oinet;"
140    "DROP TABLE ocert;"
141    "DROP TABLE okeys;"
142    "CREATE INDEX igsha ON genp(sha1);"
143    "CREATE INDEX iisha ON inet(sha1);"
144    "CREATE INDEX icsha ON cert(sha1);"
145    "CREATE INDEX iksha ON keys(sha1);",
146};
147
148struct sql_stages {
149    int pre;
150    int main;
151    int post;
152    bool init_pdmn; // If true do a full export followed by an import of the entire database so all items are re-encoded.
153};
154
155/* On disk database format version upgrade scripts.
156   If pre is 0, version is unsupported and db is considered corrupt for having that version.
157   First entry creates the current db, each susequent entry upgrade to current from the version
158   represented by the index of the slot.  Each script is either -1 (disabled) of the number of
159   the script in the main table.
160    {pre,main,post, reencode} */
161
162
163static struct sql_stages s3dl_upgrade_script[] = {
164    { -1, 0, 1, false },/* 0->current: Create version 6*/
165    {},                 /* 1->current: Upgrade to version 6 from version 1 -- Unsupported. */
166    {},                 /* 2->current: Upgrade to version 6 from version 2 -- Unsupported */
167    {},                 /* 3->current: Upgrade to version 6 from version 3 -- Unsupported */
168    {},                 /* 4->current: Upgrade to version 6 from version 4 -- Unsupported */
169    { 3, 0, 7, true },  /* 5->current: Upgrade to version 6 from version 5 */
170};
171
172static bool sql_run_script(SecDbConnectionRef dbt, int number, CFErrorRef *error)
173{
174    /* Script -1 == skip this step. */
175    if (number < 0)
176        return true;
177
178    /* If we are attempting to run a script we don't have, fail. */
179    if ((size_t)number >= array_size(s3dl_upgrade_sql))
180        return SecDbError(SQLITE_CORRUPT, error, CFSTR("script %d exceeds maximum %d"),
181                                number, (int)(array_size(s3dl_upgrade_sql)));
182    __block bool ok = true;
183    if (number == 0) {
184        CFMutableStringRef sql = CFStringCreateMutable(0, 0);
185        SecDbAppendCreateTableWithClass(sql, &genp_class);
186        SecDbAppendCreateTableWithClass(sql, &inet_class);
187        SecDbAppendCreateTableWithClass(sql, &cert_class);
188        SecDbAppendCreateTableWithClass(sql, &keys_class);
189        CFStringAppend(sql, CFSTR("CREATE TABLE tversion(version INTEGER);INSERT INTO tversion(version) VALUES(6);"));
190        CFStringPerformWithCString(sql, ^(const char *sql_string) {
191            ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), sql_string, NULL, NULL, NULL),
192                                     SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), sql_string);
193        });
194        CFReleaseSafe(sql);
195    } else {
196        ok = SecDbErrorWithDb(sqlite3_exec(SecDbHandle(dbt), s3dl_upgrade_sql[number], NULL, NULL, NULL),
197                                 SecDbHandle(dbt), error, CFSTR("sqlite3_exec: %s"), s3dl_upgrade_sql[number]);
198    }
199    return ok;
200}
201
202/* Return the current database version in *version.  Returns a
203 SQLITE error. */
204static bool s3dl_dbt_get_version(SecDbConnectionRef dbt, int *version, CFErrorRef *error)
205{
206    CFStringRef sql = CFSTR("SELECT version FROM tversion LIMIT 1");
207    return SecDbWithSQL(dbt, sql, error, ^(sqlite3_stmt *stmt) {
208        __block bool found_version = false;
209        bool step_ok = SecDbForEach(stmt, error, ^(int row_index __unused) {
210            if (!found_version) {
211                *version = sqlite3_column_int(stmt, 0);
212                found_version = true;
213            }
214            return found_version;
215        });
216        if (!found_version) {
217            /* We have a tversion table but we didn't find a single version
218             value, now what? I suppose we pretend the db is corrupted
219             since this isn't supposed to ever happen. */
220            step_ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("Failed to read version: database corrupt"));
221            secwarning("SELECT version step: %@", error ? *error : NULL);
222        }
223        return step_ok;
224    });
225}
226
227
228static bool s3dl_dbt_upgrade_from_version(SecDbConnectionRef dbt, int version, CFErrorRef *error)
229{
230    /* We need to go from db version to CURRENT_DB_VERSION, let's do so. */
231    __block bool ok = true;
232    /* O, guess we're done already. */
233    if (version == CURRENT_DB_VERSION)
234        return ok;
235
236    if (ok && version < 6) {
237        // Pre v6 keychains need to have WAL enabled, since SecDb only
238        // does this at db creation time.
239        // NOTE: This has to be run outside of a transaction.
240        ok = (SecDbExec(dbt, CFSTR("PRAGMA auto_vacuum = FULL"), error) &&
241              SecDbExec(dbt, CFSTR("PRAGMA journal_mode = WAL"), error));
242    }
243
244    // Start a transaction to do the upgrade within
245    if (ok) { ok = SecDbTransaction(dbt, kSecDbExclusiveTransactionType, error, ^(bool *commit) {
246        // Be conservative and get the version again once we start a transaction.
247        int cur_version = version;
248        s3dl_dbt_get_version(dbt, &cur_version, NULL);
249
250        /* If we are attempting to upgrade to a version greater than what we have
251         an upgrade script for, fail. */
252        if (ok && (cur_version < 0 ||
253            (size_t)cur_version >= array_size(s3dl_upgrade_script))) {
254            ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("no upgrade script for version: %d"), cur_version);
255            secerror("no upgrade script for version %d", cur_version);
256        }
257
258        struct sql_stages *script;
259        if (ok) {
260            script = &s3dl_upgrade_script[cur_version];
261            if (script->pre == 0)
262                ok = SecDbError(SQLITE_CORRUPT, error, CFSTR("unsupported db version %d"), cur_version);
263        }
264        if (ok)
265            ok = sql_run_script(dbt, script->pre, error);
266        if (ok)
267            ok = sql_run_script(dbt, script->main, error);
268        if (ok)
269            ok = sql_run_script(dbt, script->post, error);
270        if (ok && script->init_pdmn) {
271            CFErrorRef localError = NULL;
272            CFDictionaryRef backup = SecServerExportKeychainPlist(dbt,
273                                                                  KEYBAG_DEVICE, KEYBAG_NONE, kSecNoItemFilter, &localError);
274            if (backup) {
275                if (localError) {
276                    secerror("Ignoring export error: %@ during upgrade export", localError);
277                    CFReleaseNull(localError);
278                }
279                ok = SecServerImportKeychainInPlist(dbt, KEYBAG_NONE,
280                                                    KEYBAG_DEVICE, backup, kSecNoItemFilter, &localError);
281                CFRelease(backup);
282            } else {
283                ok = false;
284
285                if (localError && SecErrorGetOSStatus(localError) == errSecInteractionNotAllowed) {
286                    SecError(errSecUpgradePending, error,
287                         CFSTR("unable to complete upgrade due to device lock state"));
288                    secerror("unable to complete upgrade due to device lock state");
289                } else {
290                    secerror("unable to complete upgrade for unknown reason, marking DB as corrupt: %@", localError);
291                    SecDbCorrupt(dbt);
292                }
293            }
294
295            if (localError) {
296                if (error && !*error)
297                    *error = localError;
298                else
299                    CFRelease(localError);
300            }
301        } else if (!ok) {
302            secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error ? *error : NULL);
303            SecDbCorrupt(dbt);
304        }
305        *commit = ok;
306    }); } else {
307        secerror("unable to complete upgrade scripts, marking DB as corrupt: %@", error ? *error : NULL);
308        SecDbCorrupt(dbt);
309    }
310
311    return ok;
312}
313
314
315/* This function is called if the db doesn't have the proper version.  We
316   start an exclusive transaction and recheck the version, and then perform
317   the upgrade within that transaction. */
318static bool s3dl_dbt_upgrade(SecDbConnectionRef dbt, CFErrorRef *error)
319{
320    // Already in a transaction
321    //return kc_transaction(dbt, error, ^{
322        int version = 0; // Upgrade from version 0 == create new db
323        s3dl_dbt_get_version(dbt, &version, NULL);
324        return s3dl_dbt_upgrade_from_version(dbt, version, error);
325    //});
326}
327
328/* AUDIT[securityd](done):
329   accessGroup (ok) is a caller provided, non NULL CFTypeRef.
330
331   Return true iff accessGroup is allowable according to accessGroups.
332 */
333static bool accessGroupsAllows(CFArrayRef accessGroups,
334    CFStringRef accessGroup) {
335    /* NULL accessGroups is wildcard. */
336    if (!accessGroups)
337        return true;
338    /* Make sure we have a string. */
339    if (!isString(accessGroup))
340        return false;
341
342    /* Having the special accessGroup "*" allows access to all accessGroups. */
343    CFRange range = { 0, CFArrayGetCount(accessGroups) };
344    if (range.length &&
345        (CFArrayContainsValue(accessGroups, range, accessGroup) ||
346         CFArrayContainsValue(accessGroups, range, CFSTR("*"))))
347        return true;
348
349    return false;
350}
351
352bool itemInAccessGroup(CFDictionaryRef item, CFArrayRef accessGroups) {
353    return accessGroupsAllows(accessGroups,
354                              CFDictionaryGetValue(item, kSecAttrAccessGroup));
355}
356
357
358static CF_RETURNS_RETAINED CFDataRef SecServerExportKeychain(SecDbConnectionRef dbt,
359    keybag_handle_t src_keybag, keybag_handle_t dest_keybag, CFErrorRef *error) {
360    CFDataRef data_out = NULL;
361    /* Export everything except the items for which SecItemIsSystemBound()
362       returns true. */
363    CFDictionaryRef keychain = SecServerExportKeychainPlist(dbt,
364        src_keybag, dest_keybag, kSecBackupableItemFilter,
365        error);
366    if (keychain) {
367        data_out = CFPropertyListCreateData(kCFAllocatorDefault, keychain,
368                                             kCFPropertyListBinaryFormat_v1_0,
369                                             0, error);
370        CFRelease(keychain);
371    }
372
373    return data_out;
374}
375
376static bool SecServerImportKeychain(SecDbConnectionRef dbt,
377    keybag_handle_t src_keybag,
378    keybag_handle_t dest_keybag, CFDataRef data, CFErrorRef *error) {
379    return kc_transaction(dbt, error, ^{
380        bool ok = false;
381        CFDictionaryRef keychain;
382        keychain = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
383                                                kCFPropertyListImmutable, NULL,
384                                                error);
385        if (keychain) {
386            if (isDictionary(keychain)) {
387                ok = SecServerImportKeychainInPlist(dbt, src_keybag,
388                                                    dest_keybag, keychain,
389                                                    kSecBackupableItemFilter,
390                                                    error);
391            } else {
392                ok = SecError(errSecParam, error, CFSTR("import: keychain is not a dictionary"));
393            }
394            CFRelease(keychain);
395        }
396        return ok;
397    });
398}
399
400static CF_RETURNS_RETAINED CFDataRef SecServerKeychainBackup(SecDbConnectionRef dbt, CFDataRef keybag,
401    CFDataRef password, CFErrorRef *error) {
402    CFDataRef backup = NULL;
403    keybag_handle_t backup_keybag;
404    if (ks_open_keybag(keybag, password, &backup_keybag, error)) {
405        /* Export from system keybag to backup keybag. */
406        backup = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag, error);
407        if (!ks_close_keybag(backup_keybag, error)) {
408            CFReleaseNull(backup);
409        }
410    }
411    return backup;
412}
413
414static bool SecServerKeychainRestore(SecDbConnectionRef dbt, CFDataRef backup,
415    CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
416    keybag_handle_t backup_keybag;
417    if (!ks_open_keybag(keybag, password, &backup_keybag, error))
418        return false;
419
420    /* Import from backup keybag to system keybag. */
421    bool ok = SecServerImportKeychain(dbt, backup_keybag, KEYBAG_DEVICE,
422                                      backup, error);
423    ok &= ks_close_keybag(backup_keybag, error);
424
425    return ok;
426}
427
428
429// MARK - External SPI support code.
430
431CFStringRef __SecKeychainCopyPath(void) {
432    CFStringRef kcRelPath = NULL;
433    if (use_hwaes()) {
434        kcRelPath = CFSTR("keychain-2.db");
435    } else {
436        kcRelPath = CFSTR("keychain-2-debug.db");
437    }
438
439    CFStringRef kcPath = NULL;
440    CFURLRef kcURL = SecCopyURLForFileInKeychainDirectory(kcRelPath);
441    if (kcURL) {
442        kcPath = CFURLCopyFileSystemPath(kcURL, kCFURLPOSIXPathStyle);
443        CFRelease(kcURL);
444    }
445    return kcPath;
446
447}
448
449// MARK; -
450// MARK: kc_dbhandle init and reset
451
452SecDbRef SecKeychainDbCreate(CFStringRef path) {
453    return SecDbCreate(path, ^bool (SecDbConnectionRef dbconn, bool didCreate, CFErrorRef *localError) {
454        bool ok;
455        if (didCreate)
456            ok = s3dl_dbt_upgrade_from_version(dbconn, 0, localError);
457        else
458            ok = s3dl_dbt_upgrade(dbconn, localError);
459
460        if (!ok)
461            secerror("Upgrade %sfailed: %@", didCreate ? "from v0 " : "", localError ? *localError : NULL);
462
463        return ok;
464    });
465}
466
467static SecDbRef _kc_dbhandle = NULL;
468
469static void kc_dbhandle_init(void) {
470    SecDbRef oldHandle = _kc_dbhandle;
471    _kc_dbhandle = NULL;
472    CFStringRef dbPath = __SecKeychainCopyPath();
473    if (dbPath) {
474        _kc_dbhandle = SecKeychainDbCreate(dbPath);
475        CFRelease(dbPath);
476    } else {
477        secerror("no keychain path available");
478    }
479    if (oldHandle) {
480        secerror("replaced %@ with %@", oldHandle, _kc_dbhandle);
481        CFRelease(oldHandle);
482    }
483}
484
485static dispatch_once_t _kc_dbhandle_once;
486
487static SecDbRef kc_dbhandle(void) {
488    dispatch_once(&_kc_dbhandle_once, ^{
489        kc_dbhandle_init();
490    });
491    return _kc_dbhandle;
492}
493
494/* For whitebox testing only */
495void kc_dbhandle_reset(void);
496void kc_dbhandle_reset(void)
497{
498    __block bool done = false;
499    dispatch_once(&_kc_dbhandle_once, ^{
500        kc_dbhandle_init();
501        done = true;
502    });
503    // TODO: Not thread safe at all! - FOR DEBUGGING ONLY
504    if (!done)
505        kc_dbhandle_init();
506}
507
508static SecDbConnectionRef kc_aquire_dbt(bool writeAndRead, CFErrorRef *error) {
509    SecDbRef db = kc_dbhandle();
510    if (db == NULL) {
511        SecError(errSecDataNotAvailable, error, CFSTR("failed to get a db handle"));
512        return NULL;
513    }
514    return SecDbConnectionAquire(db, !writeAndRead, error);
515}
516
517/* Return a per thread dbt handle for the keychain.  If create is true create
518 the database if it does not yet exist.  If it is false, just return an
519 error if it fails to auto-create. */
520static bool kc_with_dbt(bool writeAndRead, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt))
521{
522    // Make sure we initialize our engines before writing to the keychain
523    if (writeAndRead)
524        SecItemDataSourceFactoryGetDefault();
525
526    bool ok = false;
527    SecDbConnectionRef dbt = kc_aquire_dbt(writeAndRead, error);
528    if (dbt) {
529        ok = perform(dbt);
530        SecDbConnectionRelease(dbt);
531    }
532    return ok;
533}
534
535static bool
536items_matching_issuer_parent(SecDbConnectionRef dbt, CFArrayRef accessGroups,
537                             CFDataRef issuer, CFArrayRef issuers, int recurse)
538{
539    Query *q;
540    CFArrayRef results = NULL;
541    CFIndex i, count;
542    bool found = false;
543
544    if (CFArrayContainsValue(issuers, CFRangeMake(0, CFArrayGetCount(issuers)), issuer))
545        return true;
546
547    const void *keys[] = { kSecClass, kSecReturnRef, kSecAttrSubject };
548    const void *vals[] = { kSecClassCertificate, kCFBooleanTrue, issuer };
549    CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, array_size(keys), NULL, NULL);
550
551    if (!query)
552        return false;
553
554    CFErrorRef localError = NULL;
555    q = query_create_with_limit(query, kSecMatchUnlimited, &localError);
556    CFRelease(query);
557    if (q) {
558        s3dl_copy_matching(dbt, q, (CFTypeRef*)&results, accessGroups, &localError);
559        query_destroy(q, &localError);
560    }
561    if (localError) {
562        secerror("items matching issuer parent: %@", localError);
563        CFReleaseNull(localError);
564        return false;
565    }
566
567    count = CFArrayGetCount(results);
568    for (i = 0; (i < count) && !found; i++) {
569        CFDictionaryRef cert_dict = (CFDictionaryRef)CFArrayGetValueAtIndex(results, i);
570        CFDataRef cert_issuer = CFDictionaryGetValue(cert_dict, kSecAttrIssuer);
571        if (CFEqual(cert_issuer, issuer))
572            continue;
573        if (recurse-- > 0)
574            found = items_matching_issuer_parent(dbt, accessGroups, cert_issuer, issuers, recurse);
575    }
576    CFReleaseSafe(results);
577
578    return found;
579}
580
581bool match_item(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFDictionaryRef item)
582{
583    if (q->q_match_issuer) {
584        CFDataRef issuer = CFDictionaryGetValue(item, kSecAttrIssuer);
585        if (!items_matching_issuer_parent(dbt, accessGroups, issuer, q->q_match_issuer, 10 /*max depth*/))
586            return false;
587    }
588
589    /* Add future match checks here. */
590
591    return true;
592}
593
594/****************************************************************************
595 **************** Beginning of Externally Callable Interface ****************
596 ****************************************************************************/
597
598#if 0
599// TODO Use as a safety wrapper
600static bool SecErrorWith(CFErrorRef *in_error, bool (^perform)(CFErrorRef *error)) {
601    CFErrorRef error = in_error ? *in_error : NULL;
602    bool ok;
603    if ((ok = perform(&error))) {
604        assert(error == NULL);
605        if (error)
606            secerror("error + success: %@", error);
607    } else {
608        assert(error);
609        OSStatus status = SecErrorGetOSStatus(error);
610        if (status != errSecItemNotFound)           // Occurs in normal operation, so exclude
611            secerror("error:[%" PRIdOSStatus "] %@", status, error);
612        if (in_error) {
613            *in_error = error;
614        } else {
615            CFReleaseNull(error);
616        }
617    }
618    return ok;
619}
620#endif
621
622/* AUDIT[securityd](done):
623   query (ok) is a caller provided dictionary, only its cf type has been checked.
624 */
625static bool
626SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
627    CFArrayRef accessGroups, CFErrorRef *error)
628{
629    CFIndex ag_count;
630    if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
631        return SecError(errSecMissingEntitlement, error,
632                         CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
633    }
634
635    if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
636        /* Having the special accessGroup "*" allows access to all accessGroups. */
637        accessGroups = NULL;
638    }
639
640    bool ok = false;
641    Query *q = query_create_with_limit(query, 1, error);
642    if (q) {
643        CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
644        if (agrp && accessGroupsAllows(accessGroups, agrp)) {
645            // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
646            const void *val = agrp;
647            accessGroups = CFArrayCreate(0, &val, 1, &kCFTypeArrayCallBacks);
648        } else {
649            CFRetainSafe(accessGroups);
650        }
651
652        query_enable_interactive(q);
653        query_set_caller_access_groups(q, accessGroups);
654
655        /* Sanity check the query. */
656        if (q->q_use_item_list) {
657            ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list unsupported"));
658#if defined(MULTIPLE_KEYCHAINS)
659        } else if (q->q_use_keychain) {
660            ok = SecError(errSecUseKeychainUnsupported, error, CFSTR("use keychain list unsupported"));
661#endif
662        } else if (q->q_match_issuer && ((q->q_class != &cert_class) &&
663                    (q->q_class != &identity_class))) {
664            ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported match attribute"));
665        } else if (q->q_return_type != 0 && result == NULL) {
666            ok = SecError(errSecReturnMissingPointer, error, CFSTR("missing pointer"));
667        } else if (!q->q_error) {
668            do {
669                ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
670                    return s3dl_copy_matching(dbt, q, result, accessGroups, error);
671                });
672            } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
673        }
674
675        CFReleaseSafe(accessGroups);
676        if (!query_destroy(q, error))
677            ok = false;
678    }
679
680	return ok;
681}
682
683bool
684_SecItemCopyMatching(CFDictionaryRef query, CFArrayRef accessGroups, CFTypeRef *result, CFErrorRef *error) {
685    return SecItemServerCopyMatching(query, result, accessGroups, error);
686}
687
688/* AUDIT[securityd](done):
689   attributes (ok) is a caller provided dictionary, only its cf type has
690       been checked.
691 */
692bool
693_SecItemAdd(CFDictionaryRef attributes, CFArrayRef accessGroups,
694            CFTypeRef *result, CFErrorRef *error)
695{
696    bool ok = true;
697    CFIndex ag_count;
698    if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups)))
699        return SecError(errSecMissingEntitlement, error,
700                           CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
701
702    Query *q = query_create_with_limit(attributes, 0, error);
703    if (q) {
704        /* Access group sanity checking. */
705        CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
706            kSecAttrAccessGroup);
707
708        CFArrayRef ag = accessGroups;
709        /* Having the special accessGroup "*" allows access to all accessGroups. */
710        if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
711            accessGroups = NULL;
712
713        if (agrp) {
714            /* The user specified an explicit access group, validate it. */
715            if (!accessGroupsAllows(accessGroups, agrp))
716                return SecError(errSecNoAccessForItem, error, CFSTR("NoAccessForItem"));
717        } else {
718            agrp = (CFStringRef)CFArrayGetValueAtIndex(ag, 0);
719
720            /* We are using an implicit access group, add it as if the user
721               specified it as an attribute. */
722            query_add_attribute(kSecAttrAccessGroup, agrp, q);
723        }
724
725        query_ensure_access_control(q, agrp);
726        query_enable_interactive(q);
727
728        if (q->q_row_id)
729            ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id"));  // TODO: better error string
730    #if defined(MULTIPLE_KEYCHAINS)
731        else if (q->q_use_keychain_list)
732            ok = SecError(errSecUseKeychainListUnsupported, error, CFSTR("q_use_keychain_list"));  // TODO: better error string;
733    #endif
734        else if (!q->q_error) {
735            do {
736                ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt){
737                    return kc_transaction(dbt, error, ^{
738                        query_pre_add(q, true);
739                        return s3dl_query_add(dbt, q, result, error);
740                    });
741                });
742            } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
743        }
744        ok = query_notify_and_destroy(q, ok, error);
745    } else {
746        ok = false;
747    }
748    return ok;
749}
750
751/* AUDIT[securityd](done):
752   query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
753       only their cf types have been checked.
754 */
755bool
756_SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
757               CFArrayRef accessGroups, CFErrorRef *error)
758{
759    CFIndex ag_count;
760    if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
761        return SecError(errSecMissingEntitlement, error,
762                         CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
763    }
764
765    if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
766        /* Having the special accessGroup "*" allows access to all accessGroups. */
767        accessGroups = NULL;
768    }
769
770    bool ok = true;
771    Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
772    if (!q) {
773        ok = false;
774    }
775    if (ok) {
776        /* Sanity check the query. */
777        query_set_caller_access_groups(q, accessGroups);
778        if (q->q_use_item_list) {
779            ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list not supported"));
780        } else if (q->q_return_type & kSecReturnDataMask) {
781            /* Update doesn't return anything so don't ask for it. */
782            ok = SecError(errSecReturnDataUnsupported, error, CFSTR("return data not supported by update"));
783        } else if (q->q_return_type & kSecReturnAttributesMask) {
784            ok = SecError(errSecReturnAttributesUnsupported, error, CFSTR("return attributes not supported by update"));
785        } else if (q->q_return_type & kSecReturnRefMask) {
786            ok = SecError(errSecReturnRefUnsupported, error, CFSTR("return ref not supported by update"));
787        } else if (q->q_return_type & kSecReturnPersistentRefMask) {
788            ok = SecError(errSecReturnPersistentRefUnsupported, error, CFSTR("return persistent ref not supported by update"));
789        } else {
790            /* Access group sanity checking. */
791            CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
792                kSecAttrAccessGroup);
793            if (agrp) {
794                /* The user is attempting to modify the access group column,
795                   validate it to make sure the new value is allowable. */
796                if (!accessGroupsAllows(accessGroups, agrp)) {
797                    ok = SecError(errSecNoAccessForItem, error, CFSTR("accessGroup %@ not in %@"), agrp, accessGroups);
798                }
799            }
800        }
801    }
802    if (ok) {
803        if (!q->q_use_tomb && SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
804            q->q_use_tomb = kCFBooleanFalse;
805        }
806        query_enable_interactive(q);
807        do {
808            ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
809                return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
810            });
811        } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
812    }
813    if (q) {
814        ok = query_notify_and_destroy(q, ok, error);
815    }
816    return ok;
817}
818
819
820/* AUDIT[securityd](done):
821   query (ok) is a caller provided dictionary, only its cf type has been checked.
822 */
823bool
824_SecItemDelete(CFDictionaryRef query, CFArrayRef accessGroups, CFErrorRef *error)
825{
826    CFIndex ag_count;
827    if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
828        return SecError(errSecMissingEntitlement, error,
829                           CFSTR("client has neither application-identifier nor keychain-access-groups entitlements"));
830    }
831
832    if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
833        /* Having the special accessGroup "*" allows access to all accessGroups. */
834        accessGroups = NULL;
835    }
836
837    Query *q = query_create_with_limit(query, kSecMatchUnlimited, error);
838    bool ok;
839    if (q) {
840        q->q_crypto_op = kSecKsDelete;
841        query_set_caller_access_groups(q, accessGroups);
842        /* Sanity check the query. */
843        if (q->q_limit != kSecMatchUnlimited)
844            ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
845        else if (query_match_count(q) != 0)
846            ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
847        else if (q->q_ref)
848            ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
849        else if (q->q_row_id && query_attr_count(q))
850            ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
851        else {
852            if (!q->q_use_tomb && SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
853                q->q_use_tomb = kCFBooleanFalse;
854            }
855            query_enable_interactive(q);
856            do {
857                ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
858                    return s3dl_query_delete(dbt, q, accessGroups, error);
859                });
860            } while (query_needs_authentication(q) && (ok = query_authenticate(q, &error)));
861        }
862        ok = query_notify_and_destroy(q, ok, error);
863    } else {
864        ok = false;
865    }
866    return ok;
867}
868
869
870/* AUDIT[securityd](done):
871   No caller provided inputs.
872 */
873static bool
874SecItemServerDeleteAll(CFErrorRef *error) {
875    return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
876        return (kc_transaction(dbt, error, ^bool {
877            return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
878                    SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
879                    SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
880                    SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
881        }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
882    });
883}
884
885bool
886_SecItemDeleteAll(CFErrorRef *error) {
887    return SecItemServerDeleteAll(error);
888}
889
890
891// MARK: -
892// MARK: Shared web credentials
893
894/* constants */
895#define SEC_CONST_DECL(k,v) CFTypeRef k = (CFTypeRef)(CFSTR(v));
896
897SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
898SEC_CONST_DECL (kSecSafariDefaultComment, "default");
899SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
900SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
901SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
902
903#if !TARGET_IPHONE_SIMULATOR
904static SWCFlags
905_SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
906{
907    __block SWCFlags flags = kSWCFlags_None;
908
909#if TARGET_OS_IPHONE
910    CFRetainSafe(appID);
911    CFRetainSafe(fqdn);
912    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
913    dispatch_retain(semaphore);
914    if (0 == SWCCheckService(kSecSharedWebCredentialsService, appID, fqdn,
915        ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails) {
916            if (!inStatus) { flags = inFlags; }
917            CFReleaseSafe(appID);
918            CFReleaseSafe(fqdn);
919            dispatch_semaphore_signal(semaphore);
920            dispatch_release(semaphore);
921            //secerror("SWCCheckService: inStatus=%d, flags=%0X", inStatus, flags);
922        }))
923    {
924        // wait for the block to complete, as we need its answer
925        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
926    }
927    else // didn't queue the block
928    {
929        CFReleaseSafe(appID);
930        CFReleaseSafe(fqdn);
931        dispatch_release(semaphore);
932    }
933    dispatch_release(semaphore);
934#else
935    flags |= (kSWCFlag_SiteApproved);
936#endif
937
938    if (!error) { return flags; }
939    *error = NULL;
940
941    // check website approval status
942    if (!(flags & kSWCFlag_SiteApproved)) {
943        if (flags & kSWCFlag_Pending) {
944            SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
945        } else {
946            SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
947        }
948        return flags;
949    }
950
951    // check user approval status
952    if (flags & kSWCFlag_UserDenied) {
953        SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
954    }
955    return flags;
956}
957#endif
958
959#if !TARGET_IPHONE_SIMULATOR
960static bool
961_SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
962{
963    bool result = false;
964    CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
965    if (!count || !domain || !service) {
966        return result;
967    }
968    for (idx=0; idx < count; idx++) {
969        CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
970        if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
971            CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
972            CFIndex substr_len = CFStringGetLength(str) - prefix_len;
973            CFRange range = { prefix_len, substr_len };
974            CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
975            if (substr && CFEqual(substr, domain)) {
976                result = true;
977            }
978            CFReleaseSafe(substr);
979            if (result) {
980                break;
981            }
982        }
983    }
984    return result;
985}
986#endif
987
988static bool
989_SecAddNegativeWebCredential(CFStringRef fqdn, CFStringRef appID, bool forSafari)
990{
991    bool result = false;
992    if (!fqdn) { return result; }
993
994#if TARGET_OS_IPHONE
995    // update our database
996    CFRetainSafe(appID);
997    CFRetainSafe(fqdn);
998    if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService,
999        appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
1000        ^void(OSStatus inStatus, SWCFlags inNewFlags){
1001            CFReleaseSafe(appID);
1002            CFReleaseSafe(fqdn);
1003        }))
1004    {
1005        result = true;
1006    }
1007    else // didn't queue the block
1008    {
1009        CFReleaseSafe(appID);
1010        CFReleaseSafe(fqdn);
1011    }
1012#endif
1013    if (!forSafari) { return result; }
1014
1015    // below this point: create a negative Safari web credential item
1016
1017    CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1018    if (!attrs) { return result; }
1019
1020    CFErrorRef error = NULL;
1021    CFStringRef accessGroup = CFSTR("*");
1022    CFArrayRef accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1023
1024    CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1025    CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1026    CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1027    CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1028    CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1029    CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1030
1031    (void)_SecItemDelete(attrs, accessGroups, &error);
1032    CFReleaseNull(error);
1033
1034    CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1035    CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1036
1037    CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
1038                                                 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
1039    if (label) {
1040        CFDictionaryAddValue(attrs, kSecAttrLabel, label);
1041        CFReleaseSafe(label);
1042    }
1043
1044    UInt8 space = ' ';
1045    CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
1046    if (data) {
1047        CFDictionarySetValue(attrs, kSecValueData, data);
1048        CFReleaseSafe(data);
1049    }
1050
1051    CFTypeRef addResult = NULL;
1052    result = _SecItemAdd(attrs, accessGroups, &addResult, &error);
1053
1054    CFReleaseSafe(addResult);
1055    CFReleaseSafe(error);
1056    CFReleaseSafe(attrs);
1057    CFReleaseSafe(accessGroups);
1058
1059    return result;
1060}
1061
1062/* Specialized version of SecItemAdd for shared web credentials */
1063bool
1064_SecAddSharedWebCredential(CFDictionaryRef attributes,
1065    const audit_token_t *clientAuditToken,
1066    CFStringRef appID,
1067    CFArrayRef domains,
1068    CFTypeRef *result,
1069    CFErrorRef *error) {
1070
1071    CFStringRef fqdn = CFDictionaryGetValue(attributes, kSecAttrServer);
1072    CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
1073#if TARGET_OS_IPHONE
1074    CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
1075#else
1076    CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
1077#endif
1078    CFStringRef accessGroup = CFSTR("*");
1079    CFArrayRef accessGroups = NULL;
1080    CFMutableDictionaryRef query = NULL, attrs = NULL;
1081    SInt32 port = -1;
1082    bool ok = false, update = false;
1083    //bool approved = false;
1084
1085    // check autofill enabled status
1086    if (!swca_autofill_enabled(clientAuditToken)) {
1087        SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1088        goto cleanup;
1089    }
1090
1091    // parse fqdn with CFURL here, since it could be specified as domain:port
1092    if (fqdn) {
1093        CFRetainSafe(fqdn);
1094        CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1095        if (urlStr) {
1096            CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1097            if (url) {
1098                CFStringRef hostname = CFURLCopyHostName(url);
1099                if (hostname) {
1100                    CFReleaseSafe(fqdn);
1101                    fqdn = hostname;
1102                    port = CFURLGetPortNumber(url);
1103                }
1104                CFReleaseSafe(url);
1105            }
1106            CFReleaseSafe(urlStr);
1107        }
1108    }
1109
1110    if (!account) {
1111        SecError(errSecParam, error, CFSTR("No account provided"));
1112        goto cleanup;
1113    }
1114    if (!fqdn) {
1115        SecError(errSecParam, error, CFSTR("No domain provided"));
1116        goto cleanup;
1117    }
1118
1119#if TARGET_IPHONE_SIMULATOR
1120    secerror("app/site association entitlements not checked in Simulator");
1121#else
1122    OSStatus status = errSecMissingEntitlement;
1123    // validate that fqdn is part of caller's shared credential domains entitlement
1124    if (!appID) {
1125        SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1126        goto cleanup;
1127    }
1128    if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1129        status = errSecSuccess;
1130    }
1131    if (errSecSuccess != status) {
1132        CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1133                                                   CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1134        if (!msg) {
1135            msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1136        }
1137        SecError(status, error, CFSTR("%@"), msg);
1138        CFReleaseSafe(msg);
1139        goto cleanup;
1140    }
1141#endif
1142
1143#if TARGET_IPHONE_SIMULATOR
1144    secerror("Ignoring app/site approval state in the Simulator.");
1145#else
1146    // get approval status for this app/domain pair
1147    SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1148    //approved = ((flags & kSWCFlag_SiteApproved) && (flags & kSWCFlag_UserApproved));
1149    if (!(flags & kSWCFlag_SiteApproved)) {
1150        goto cleanup;
1151    }
1152#endif
1153
1154    // give ourselves access to see matching items for kSecSafariAccessGroup
1155    accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1156
1157    // create lookup query
1158    query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1159    if (!query) {
1160        SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1161        goto cleanup;
1162    }
1163    CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
1164    CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
1165    CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1166    CFDictionaryAddValue(query, kSecAttrServer, fqdn);
1167    CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
1168
1169    // check for presence of Safari's negative entry ('passwords not saved')
1170    CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
1171    ok = _SecItemCopyMatching(query, accessGroups, result, error);
1172    CFReleaseNull(*result);
1173    CFReleaseNull(*error);
1174    if (ok) {
1175        SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
1176        goto cleanup;
1177    }
1178
1179    // now use the provided account (and optional port number, if one was present)
1180    CFDictionarySetValue(query, kSecAttrAccount, account);
1181    if (port < -1 || port > 0) {
1182        SInt16 portValueShort = (port & 0xFFFF);
1183        CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1184        CFDictionaryAddValue(query, kSecAttrPort, portNumber);
1185        CFReleaseSafe(portNumber);
1186    }
1187
1188    // look up existing password
1189    if (_SecItemCopyMatching(query, accessGroups, result, error)) {
1190        // found it, so this becomes either an "update password" or "delete password" operation
1191        CFReleaseNull(*result);
1192        CFReleaseNull(*error);
1193        update = (password != NULL);
1194        if (update) {
1195            attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1196            CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1197            CFDictionaryAddValue(attrs, kSecValueData, credential);
1198            CFReleaseSafe(credential);
1199            CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
1200
1201            // confirm the update
1202            // (per rdar://16676310 we always prompt, even if there was prior user approval)
1203            ok = /*approved ||*/ swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
1204                ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1205            if (ok) {
1206                ok = _SecItemUpdate(query, attrs, accessGroups, error);
1207            }
1208        }
1209        else {
1210            // confirm the delete
1211            // (per rdar://16676288 we always prompt, even if there was prior user approval)
1212            ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
1213                ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1214            if (ok) {
1215                ok = _SecItemDelete(query, accessGroups, error);
1216            }
1217        }
1218        if (ok) {
1219            CFReleaseNull(*error);
1220        }
1221        goto cleanup;
1222    }
1223    CFReleaseNull(*result);
1224    CFReleaseNull(*error);
1225
1226    // password does not exist, so prepare to add it
1227    if (true) {
1228        CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
1229        if (label) {
1230            CFDictionaryAddValue(query, kSecAttrLabel, label);
1231            CFReleaseSafe(label);
1232        }
1233        // NOTE: we always expect to use HTTPS for web forms.
1234        CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
1235
1236        CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
1237        CFDictionarySetValue(query, kSecValueData, credential);
1238        CFReleaseSafe(credential);
1239        CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
1240
1241        CFReleaseSafe(accessGroups);
1242        accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
1243
1244        // mark the item as created by this function
1245        const int32_t creator_value = 'swca';
1246        CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
1247        if (creator) {
1248            CFDictionarySetValue(query, kSecAttrCreator, creator);
1249            CFReleaseSafe(creator);
1250            ok = true;
1251        }
1252        else {
1253            // confirm the add
1254            // (per rdar://16680019, we won't prompt here in the normal case)
1255            ok = /*approved ||*/ swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error,
1256                ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1257        }
1258    }
1259    if (ok) {
1260        ok = _SecItemAdd(query, accessGroups, result, error);
1261    }
1262
1263cleanup:
1264#if 0 /* debugging */
1265{
1266    const char *op_str = (password) ? ((update) ? "updated" : "added") : "deleted";
1267    const char *result_str = (ok) ? "true" : "false";
1268    secerror("result=%s, %s item %@, error=%@", result_str, op_str, *result, *error);
1269}
1270#else
1271    (void)update;
1272#endif
1273    CFReleaseSafe(attrs);
1274    CFReleaseSafe(query);
1275    CFReleaseSafe(accessGroups);
1276    CFReleaseSafe(fqdn);
1277    return ok;
1278}
1279
1280/* Specialized version of SecItemCopyMatching for shared web credentials */
1281bool
1282_SecCopySharedWebCredential(CFDictionaryRef query,
1283    const audit_token_t *clientAuditToken,
1284    CFStringRef appID,
1285    CFArrayRef domains,
1286    CFTypeRef *result,
1287    CFErrorRef *error) {
1288
1289    CFMutableArrayRef credentials = NULL;
1290    CFMutableArrayRef foundItems = NULL;
1291    CFMutableArrayRef fqdns = NULL;
1292    CFArrayRef accessGroups = NULL;
1293    CFStringRef fqdn = NULL;
1294    CFStringRef account = NULL;
1295    CFIndex idx, count;
1296    SInt32 port = -1;
1297    bool ok = false;
1298
1299    require_quiet(result, cleanup);
1300    credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1301    foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1302    fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1303
1304    // give ourselves access to see matching items for kSecSafariAccessGroup
1305    CFStringRef accessGroup = CFSTR("*");
1306    accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
1307
1308    // On input, the query dictionary contains optional fqdn and account entries.
1309    fqdn = CFDictionaryGetValue(query, kSecAttrServer);
1310    account = CFDictionaryGetValue(query, kSecAttrAccount);
1311
1312    // Check autofill enabled status
1313    if (!swca_autofill_enabled(clientAuditToken)) {
1314        SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
1315        goto cleanup;
1316    }
1317
1318    // Check fqdn; if NULL, add domains from caller's entitlement.
1319    if (fqdn) {
1320        CFArrayAppendValue(fqdns, fqdn);
1321    }
1322    else if (domains) {
1323        CFIndex idx, count = CFArrayGetCount(domains);
1324        for (idx=0; idx < count; idx++) {
1325            CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
1326            // Parse the entry for our service label prefix
1327            if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
1328                CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
1329                CFIndex substr_len = CFStringGetLength(str) - prefix_len;
1330                CFRange range = { prefix_len, substr_len };
1331                fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
1332                if (fqdn) {
1333                    CFArrayAppendValue(fqdns, fqdn);
1334                    CFRelease(fqdn);
1335                }
1336            }
1337        }
1338    }
1339    count = CFArrayGetCount(fqdns);
1340    if (count < 1) {
1341        SecError(errSecParam, error, CFSTR("No domain provided"));
1342        goto cleanup;
1343    }
1344
1345    // Aggregate search results for each domain
1346    for (idx = 0; idx < count; idx++) {
1347        CFMutableArrayRef items = NULL;
1348        CFMutableDictionaryRef attrs = NULL;
1349        fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
1350        CFRetainSafe(fqdn);
1351        port = -1;
1352
1353        // Parse the fqdn for a possible port specifier.
1354        if (fqdn) {
1355            CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
1356            if (urlStr) {
1357                CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
1358                if (url) {
1359                    CFStringRef hostname = CFURLCopyHostName(url);
1360                    if (hostname) {
1361                        CFReleaseSafe(fqdn);
1362                        fqdn = hostname;
1363                        port = CFURLGetPortNumber(url);
1364                    }
1365                    CFReleaseSafe(url);
1366                }
1367                CFReleaseSafe(urlStr);
1368            }
1369        }
1370
1371    #if TARGET_IPHONE_SIMULATOR
1372        secerror("app/site association entitlements not checked in Simulator");
1373    #else
1374	    OSStatus status = errSecMissingEntitlement;
1375        if (!appID) {
1376            SecError(status, error, CFSTR("Missing application-identifier entitlement"));
1377            CFReleaseSafe(fqdn);
1378            goto cleanup;
1379        }
1380        // validate that fqdn is part of caller's entitlement
1381        if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
1382            status = errSecSuccess;
1383        }
1384        if (errSecSuccess != status) {
1385            CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
1386                CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
1387            if (!msg) {
1388                msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
1389            }
1390            SecError(status, error, CFSTR("%@"), msg);
1391            CFReleaseSafe(msg);
1392            CFReleaseSafe(fqdn);
1393            goto cleanup;
1394        }
1395    #endif
1396
1397        attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1398        if (!attrs) {
1399            SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
1400            CFReleaseSafe(fqdn);
1401            goto cleanup;
1402        }
1403        CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
1404        CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
1405        CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
1406        CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
1407        if (account) {
1408            CFDictionaryAddValue(attrs, kSecAttrAccount, account);
1409        }
1410        if (port < -1 || port > 0) {
1411            SInt16 portValueShort = (port & 0xFFFF);
1412            CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
1413            CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
1414            CFReleaseSafe(portNumber);
1415        }
1416        CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
1417        CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
1418        CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
1419        CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
1420
1421        ok = _SecItemCopyMatching(attrs, accessGroups, (CFTypeRef*)&items, error);
1422        if (count > 1) {
1423            // ignore interim error since we have multiple domains to search
1424            CFReleaseNull(*error);
1425        }
1426        if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
1427    #if TARGET_IPHONE_SIMULATOR
1428            secerror("Ignoring app/site approval state in the Simulator.");
1429            bool approved = true;
1430    #else
1431            // get approval status for this app/domain pair
1432            SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
1433            if (count > 1) {
1434                // ignore interim error since we have multiple domains to check
1435                CFReleaseNull(*error);
1436            }
1437            bool approved = (flags & kSWCFlag_SiteApproved);
1438    #endif
1439            if (approved) {
1440                CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
1441            }
1442        }
1443        CFReleaseSafe(items);
1444        CFReleaseSafe(attrs);
1445        CFReleaseSafe(fqdn);
1446    }
1447
1448//  If matching credentials are found, the credentials provided to the completionHandler
1449//  will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
1450//  contain the following pairs (see Security/SecItem.h):
1451//  key: kSecAttrServer     value: CFStringRef (the website)
1452//  key: kSecAttrAccount    value: CFStringRef (the account)
1453//  key: kSecSharedPassword value: CFStringRef (the password)
1454//  Optional keys:
1455//  key: kSecAttrPort       value: CFNumberRef (the port number, if non-standard for https)
1456
1457    count = CFArrayGetCount(foundItems);
1458    for (idx = 0; idx < count; idx++) {
1459        CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
1460        CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1461        if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
1462            CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
1463            CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
1464            CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
1465            CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
1466            CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
1467            if (srvr) {
1468                CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
1469            }
1470            if (acct) {
1471                CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
1472            }
1473            if (pnum) {
1474                SInt16 pval = -1;
1475                if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
1476                    (pval < -1 || pval > 0)) {
1477                    CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
1478                }
1479            }
1480            if (data) {
1481                CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
1482                if (password) {
1483                #if TARGET_OS_IPHONE
1484                    CFDictionaryAddValue(newdict, kSecSharedPassword, password);
1485                #else
1486                    CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
1487                #endif
1488                    CFReleaseSafe(password);
1489                }
1490            }
1491            if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
1492                CFArrayInsertValueAtIndex(credentials, 0, newdict);
1493            } else {
1494                CFArrayAppendValue(credentials, newdict);
1495            }
1496        }
1497        CFReleaseSafe(newdict);
1498    }
1499
1500    if (count) {
1501
1502        ok = false;
1503
1504        // create a new array of dictionaries (without the actual password) for picker UI
1505        count = CFArrayGetCount(credentials);
1506        CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
1507        for (idx = 0; idx < count; idx++) {
1508            CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1509            CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
1510        #if TARGET_OS_IPHONE
1511            CFDictionaryRemoveValue(newdict, kSecSharedPassword);
1512        #else
1513            CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
1514        #endif
1515            CFArrayAppendValue(items, newdict);
1516            CFReleaseSafe(newdict);
1517        }
1518
1519        // prompt user to select one of the dictionary items
1520        CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
1521                                                                 clientAuditToken, items, error);
1522        if (selected) {
1523            // find the matching item in our credentials array
1524            CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
1525            CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
1526            CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
1527            for (idx = 0; idx < count; idx++) {
1528                CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
1529                CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
1530                CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
1531                CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
1532
1533                if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
1534                if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
1535                if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
1536
1537                // we have a match!
1538                CFReleaseSafe(selected);
1539                CFRetainSafe(dict);
1540                selected = dict;
1541                ok = true;
1542                break;
1543            }
1544        }
1545        CFReleaseSafe(items);
1546        CFArrayRemoveAllValues(credentials);
1547        if (selected && ok) {
1548#if TARGET_OS_IPHONE
1549            fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
1550#endif
1551            CFArrayAppendValue(credentials, selected);
1552        }
1553
1554#if 0
1555        // confirm the access
1556        ok = swca_confirm_operation(swca_copy_request_id, clientAuditToken, query, error,
1557                    ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(fqdn, appID, false); });
1558#endif
1559        if (ok) {
1560            #if TARGET_OS_IPHONE
1561            // register confirmation with database
1562            CFRetainSafe(appID);
1563            CFRetainSafe(fqdn);
1564            if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService,
1565                appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
1566                ^void(OSStatus inStatus, SWCFlags inNewFlags){
1567                    CFReleaseSafe(appID);
1568                    CFReleaseSafe(fqdn);
1569                }))
1570            {
1571                 // we didn't queue the block
1572                CFReleaseSafe(appID);
1573                CFReleaseSafe(fqdn);
1574            }
1575            #endif
1576        }
1577        CFReleaseSafe(selected);
1578    }
1579    else if (NULL == *error) {
1580        // found no items, and we haven't already filled in the error
1581        SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
1582    }
1583
1584cleanup:
1585    if (!ok) {
1586        CFArrayRemoveAllValues(credentials);
1587    }
1588    CFReleaseSafe(foundItems);
1589    *result = credentials;
1590    CFReleaseSafe(accessGroups);
1591    CFReleaseSafe(fqdns);
1592#if 0 /* debugging */
1593    secerror("result=%s, copied items %@, error=%@", (ok) ? "true" : "false", *result, *error);
1594#endif
1595    return ok;
1596}
1597
1598// MARK: -
1599// MARK: Keychain backup
1600
1601CF_RETURNS_RETAINED CFDataRef
1602_SecServerKeychainBackup(CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1603    CFDataRef backup;
1604	SecDbConnectionRef dbt = SecDbConnectionAquire(kc_dbhandle(), false, error);
1605
1606	if (!dbt)
1607		return NULL;
1608
1609    if (keybag == NULL && passcode == NULL) {
1610#if USE_KEYSTORE
1611        backup = SecServerExportKeychain(dbt, KEYBAG_DEVICE, backup_keybag_handle, error);
1612#else /* !USE_KEYSTORE */
1613        SecError(errSecParam, error, CFSTR("Why are you doing this?"));
1614        backup = NULL;
1615#endif /* USE_KEYSTORE */
1616    } else {
1617        backup = SecServerKeychainBackup(dbt, keybag, passcode, error);
1618    }
1619
1620    SecDbConnectionRelease(dbt);
1621
1622    return backup;
1623}
1624
1625bool
1626_SecServerKeychainRestore(CFDataRef backup, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
1627    if (backup == NULL || keybag == NULL)
1628        return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
1629
1630    __block bool ok = true;
1631    ok &= SecDbPerformWrite(kc_dbhandle(), error, ^(SecDbConnectionRef dbconn) {
1632        ok = SecServerKeychainRestore(dbconn, backup, keybag, passcode, error);
1633    });
1634
1635    if (ok) {
1636        SecKeychainChanged(true);
1637    }
1638
1639    return ok;
1640}
1641
1642// MARK: -
1643// MARK: SecItemDataSource
1644
1645// Make sure to call this before any writes to the keychain, so that we fire
1646// up the engines to monitor manifest changes.
1647SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
1648    return SecItemDataSourceFactoryGetShared(kc_dbhandle());
1649        }
1650
1651/* AUDIT[securityd]:
1652   args_in (ok) is a caller provided, CFDictionaryRef.
1653 */
1654
1655CF_RETURNS_RETAINED CFArrayRef
1656_SecServerKeychainSyncUpdateKeyParameter(CFDictionaryRef updates, CFErrorRef *error) {
1657    // This never fails, trust us!
1658    return SOSCCHandleUpdateKeyParameter(updates);
1659}
1660
1661
1662CF_RETURNS_RETAINED CFArrayRef
1663_SecServerKeychainSyncUpdateCircle(CFDictionaryRef updates, CFErrorRef *error) {
1664    // This never fails, trust us!
1665    return SOSCCHandleUpdateCircle(updates);
1666}
1667
1668CF_RETURNS_RETAINED CFArrayRef
1669_SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
1670    // This never fails, trust us!
1671    return SOSCCHandleUpdateMessage(updates);
1672}
1673
1674
1675//
1676// Truthiness in the cloud backup/restore support.
1677//
1678
1679static CFDictionaryRef
1680_SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
1681    CFDictionaryRef backup, CFErrorRef *error)
1682{
1683    SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
1684    __block CFMutableDictionaryRef backup_new = NULL;
1685    keybag_handle_t bag_handle;
1686    if (!ks_open_keybag(keybag, password, &bag_handle, error))
1687        return backup_new;
1688
1689    // We need to have a datasource singleton for protection domain
1690    // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
1691    // instance around which we create in the datasource constructor as well.
1692    SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1693    SOSDataSourceRef ds = dsf->create_datasource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1694    if (ds) {
1695        backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1696        mold = SOSCreateManifestWithBackup(backup, error);
1697        SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
1698        mnow = SOSEngineCopyManifest(engine, error);
1699        if (!mnow) {
1700            mnow = SOSDataSourceCopyManifest(ds, error);
1701        }
1702        if (!mnow) {
1703            CFReleaseNull(backup_new);
1704            secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
1705        } else {
1706            SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
1707        }
1708
1709        // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
1710        SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
1711            CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
1712            CFDictionaryRemoveValue(backup_new, deleted_item_key);
1713            CFRelease(deleted_item_key);
1714        });
1715
1716        __block struct SOSDigestVector dvdel = SOSDigestVectorInit;
1717        SOSDataSourceForEachObject(ds, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
1718            CFErrorRef localError = NULL;
1719            CFDataRef digest_data = NULL;
1720            CFTypeRef value = NULL;
1721            if (!object) {
1722                // Key in our manifest can't be found in db, remove it from our manifest
1723                SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(digest));
1724            } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
1725                || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
1726                if (SecErrorGetOSStatus(localError) == errSecDecode) {
1727                    // Ignore decode errors, pretend the objects aren't there
1728                    CFRelease(localError);
1729                    // Object undecodable, remove it from our manifest
1730                    SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(digest));
1731                } else {
1732                    // Stop iterating and propagate out all other errors.
1733                    *stop = true;
1734                    *error = localError;
1735                    CFReleaseNull(backup_new);
1736                }
1737            } else {
1738                // TODO: Should we skip tombstones here?
1739                CFStringRef key = CFDataCopyHexString(digest_data);
1740                CFDictionarySetValue(backup_new, key, value);
1741                CFReleaseSafe(key);
1742            }
1743            CFReleaseSafe(digest_data);
1744            CFReleaseSafe(value);
1745        }) || CFReleaseNull(backup_new);
1746
1747        if (dvdel.count) {
1748            struct SOSDigestVector dvadd = SOSDigestVectorInit;
1749            if (!SOSEngineUpdateLocalManifest(engine, kSOSDataSourceSOSTransaction, &dvdel, &dvadd, error)) {
1750                CFReleaseNull(backup_new);
1751            }
1752            SOSDigestVectorFree(&dvdel);
1753        }
1754
1755        SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
1756    }
1757
1758    CFReleaseSafe(mold);
1759    CFReleaseSafe(mnow);
1760    CFReleaseSafe(madd);
1761    CFReleaseSafe(mdelete);
1762    ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
1763
1764    return backup_new;
1765}
1766
1767static bool
1768_SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
1769    __block bool ok = true;
1770    keybag_handle_t bag_handle;
1771    if (!ks_open_keybag(keybag, password, &bag_handle, error))
1772        return false;
1773
1774    SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
1775    if (mbackup) {
1776        SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
1777        SOSDataSourceRef ds = dsf->create_datasource(dsf, kSecAttrAccessibleWhenUnlocked, error);
1778        if (ds) {
1779            ok = SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
1780                SOSManifestRef mnow = SOSDataSourceCopyManifest(ds, error);
1781            SOSManifestRef mdelete = NULL, madd = NULL;
1782            SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
1783
1784            // Don't delete everything in datasource not in backup.
1785
1786            // Add items from the backup
1787                SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
1788                CFDictionaryRef item = NULL;
1789                CFStringRef sha1 = CFDataCopyHexString(e);
1790                if (sha1) {
1791                    item = CFDictionaryGetValue(backup_in, sha1);
1792                    CFRelease(sha1);
1793                }
1794                if (item) {
1795                    CFErrorRef localError = NULL;
1796
1797                        if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
1798                            OSStatus status = SecErrorGetOSStatus(localError);
1799                            if (status == errSecDuplicateItem) {
1800                            // Log and ignore duplicate item errors during restore
1801                            secnotice("titc", "restore %@ not replacing existing item", item);
1802                        } else {
1803                                if (status == errSecInteractionNotAllowed)
1804                                    *stop = true;
1805                            // Propagate the first other error upwards (causing the restore to fail).
1806                            secerror("restore %@ failed %@", item, localError);
1807                            ok = false;
1808                            if (error && !*error) {
1809                                *error = localError;
1810                                localError = NULL;
1811                            }
1812                        }
1813                        CFReleaseSafe(localError);
1814                    }
1815                }
1816            });
1817                ok &= SOSDataSourceRelease(ds, error);
1818            CFReleaseNull(mdelete);
1819            CFReleaseNull(madd);
1820            CFReleaseNull(mnow);
1821            });
1822        } else {
1823            ok = false;
1824        }
1825        CFRelease(mbackup);
1826    }
1827
1828    ok &= ks_close_keybag(bag_handle, error);
1829
1830    return ok;
1831}
1832
1833
1834CF_RETURNS_RETAINED CFDictionaryRef
1835_SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1836    require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
1837    require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
1838    require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
1839
1840    return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
1841
1842errOut:
1843    return NULL;
1844}
1845
1846bool
1847_SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
1848    bool ok;
1849    require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
1850    require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
1851    if (password) {
1852        require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
1853    }
1854
1855    ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
1856
1857errOut:
1858    return ok;
1859}
1860
1861bool _SecServerRollKeys(bool force, CFErrorRef *error) {
1862#if USE_KEYSTORE
1863    uint32_t keystore_generation_status = 0;
1864    if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
1865        return false;
1866    uint32_t current_generation = keystore_generation_status & generation_current;
1867
1868    return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1869        bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1870
1871        if (force && !up_to_date) {
1872            up_to_date = s3dl_dbt_update_keys(dbt, error);
1873            if (up_to_date) {
1874                secerror("Completed roll keys.");
1875                up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
1876            }
1877            if (!up_to_date)
1878                secerror("Failed to roll keys.");
1879        }
1880        return up_to_date;
1881    });
1882#else
1883    return true;
1884#endif
1885}
1886