]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemServer.c
Security-58286.220.15.tar.gz
[apple/security.git] / OSX / sec / securityd / SecItemServer.c
1 /*
2 * Copyright (c) 2006-2017 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 <utilities/SecDb.h>
37 #include <securityd/SecDbKeychainItem.h>
38 #include <securityd/SOSCloudCircleServer.h>
39 #include <Security/SecBasePriv.h>
40 #include <Security/SecItemPriv.h>
41 #include <Security/SecItemInternal.h>
42 #include <Security/SecureObjectSync/SOSChangeTracker.h>
43 #include <Security/SecureObjectSync/SOSDigestVector.h>
44 #include <Security/SecureObjectSync/SOSEngine.h>
45 #include <Security/SecureObjectSync/SOSViews.h>
46 #include <Security/SecTrustPriv.h>
47 #include <Security/SecTrustInternal.h>
48 #include <Security/SecCertificatePriv.h>
49 #include <Security/SecEntitlements.h>
50 #include <Security/SecSignpost.h>
51
52 #include <keychain/ckks/CKKS.h>
53
54 #if USE_KEYSTORE
55 #include <MobileKeyBag/MobileKeyBag.h>
56 #include <Kernel/IOKit/crypto/AppleKeyStoreDefs.h>
57 #endif
58 // TODO: Make this include work on both platforms. rdar://problem/16526848
59 #if TARGET_OS_EMBEDDED
60 #include <Security/SecEntitlements.h>
61 #else
62 /* defines from <Security/SecEntitlements.h> */
63 #define kSecEntitlementAssociatedDomains CFSTR("com.apple.developer.associated-domains")
64 #define kSecEntitlementPrivateAssociatedDomains CFSTR("com.apple.private.associated-domains")
65 #endif
66
67 #if TARGET_OS_EMBEDDED
68 #include <utilities/SecADWrapper.h>
69 #include <sys/time.h>
70 #include <unistd.h>
71 #endif
72
73 #include <utilities/array_size.h>
74 #include <utilities/SecFileLocations.h>
75 #include <utilities/SecTrace.h>
76 #include <utilities/SecXPCError.h>
77 #include <utilities/sec_action.h>
78 #include <Security/SecuritydXPC.h>
79 #include "swcagent_client.h"
80 #include "SecPLWrappers.h"
81
82 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
83 #include <SharedWebCredentials/SharedWebCredentials.h>
84 #endif
85
86 #include "Analytics/Clients/LocalKeychainAnalytics.h"
87
88 /* Changed the name of the keychain changed notification, for testing */
89 static const char *g_keychain_changed_notification = kSecServerKeychainChangedNotification;
90
91 void SecItemServerSetKeychainChangedNotification(const char *notification_name)
92 {
93 g_keychain_changed_notification = notification_name;
94 }
95
96 void SecKeychainChanged() {
97 static dispatch_once_t once;
98 static sec_action_t action;
99
100 dispatch_once(&once, ^{
101 action = sec_action_create("SecKeychainChanged", 1);
102 sec_action_set_handler(action, ^{
103 uint32_t result = notify_post(g_keychain_changed_notification);
104 if (result == NOTIFY_STATUS_OK)
105 secnotice("item", "Sent %s", g_keychain_changed_notification);
106 else
107 secerror("notify_post %s returned: %" PRIu32, g_keychain_changed_notification, result);
108 });
109 });
110
111 sec_action_perform(action);
112 }
113
114 /* Return the current database version in *version. */
115 bool SecKeychainDbGetVersion(SecDbConnectionRef dbt, int *version, CFErrorRef *error)
116 {
117 __block bool ok = true;
118 __block CFErrorRef localError = NULL;
119 __block bool found = false;
120
121 /*
122 * First check for the version table itself
123 */
124
125 ok &= SecDbPrepare(dbt, CFSTR("SELECT name FROM sqlite_master WHERE type='table' AND name='tversion'"), &localError, ^(sqlite3_stmt *stmt) {
126 ok = SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
127 found = true;
128 *stop = true;
129 });
130 });
131 require_action(ok, out, SecDbError(SQLITE_CORRUPT, error, CFSTR("Failed to read sqlite_master table: %@"), localError));
132 if (!found) {
133 secnotice("upgr", "no tversion table, will setup a new database: %@", localError);
134 *version = 0;
135 goto out;
136 }
137
138 /*
139 * Now build up major.minor
140 */
141
142 ok &= SecDbPrepare(dbt, CFSTR("SELECT version FROM tversion"), &localError, ^(sqlite3_stmt *stmt) {
143 ok = SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
144 *version = sqlite3_column_int(stmt, 0);
145 if (*version)
146 *stop = true;
147 });
148 });
149 if (ok && (*version & 0xffff) >= 9) {
150 ok &= SecDbPrepare(dbt, CFSTR("SELECT minor FROM tversion WHERE version = ?"), &localError, ^(sqlite3_stmt *stmt) {
151 ok = SecDbBindInt(stmt, 1, *version, &localError) &&
152 SecDbStep(dbt, stmt, NULL, ^(bool *stop) {
153 int64_t minor = sqlite3_column_int64(stmt, 0);
154 *version |= ((minor & 0xff) << 8) | ((minor & 0xff0000) << 8);
155 *stop = true;
156 });
157 });
158 ok = true;
159 }
160 out:
161 secnotice("upgr", "database version is: 0x%08x : %d : %@", *version, ok, localError);
162 CFReleaseSafe(localError);
163
164
165 return ok;
166 }
167
168 static bool
169 isClassD(SecDbItemRef item)
170 {
171 CFTypeRef accessible = SecDbItemGetCachedValueWithName(item, kSecAttrAccessible);
172
173 if (CFEqualSafe(accessible, kSecAttrAccessibleAlways) || CFEqualSafe(accessible, kSecAttrAccessibleAlwaysThisDeviceOnly))
174 return true;
175 return false;
176 }
177
178 #if TARGET_OS_EMBEDDED
179
180 static int64_t
181 measureDuration(struct timeval *start)
182 {
183 struct timeval stop;
184 int64_t duration;
185
186 gettimeofday(&stop, NULL);
187
188 duration = (stop.tv_sec-start->tv_sec) * 1000;
189 duration += (stop.tv_usec / 1000) - (start->tv_usec / 1000);
190
191 return SecBucket2Significant(duration);
192 }
193
194 static void
195 measureUpgradePhase1(struct timeval *start, bool success, int64_t itemsMigrated)
196 {
197 int64_t duration = measureDuration(start);
198
199 if (success) {
200 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-success"), itemsMigrated);
201 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-success"), duration);
202 } else {
203 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-items-fail"), itemsMigrated);
204 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase1.migrated-time-fail"), duration);
205 }
206 }
207
208 static void
209 measureUpgradePhase2(struct timeval *start, int64_t itemsMigrated)
210 {
211 int64_t duration = measureDuration(start);
212
213 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-items"), itemsMigrated);
214 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.phase2.migrated-time"), duration);
215 }
216 #endif /* TARGET_OS_EMBEDDED */
217
218 static bool DBClassesAreEqual(const SecDbClass* class1, const SecDbClass* class2)
219 {
220 if (CFEqual(class1->name, class2->name) && class1->itemclass == class2->itemclass) {
221 int attrIndex = 0;
222 const SecDbAttr* class1Attr = class1->attrs[attrIndex];
223 const SecDbAttr* class2Attr = class2->attrs[attrIndex];
224
225 while (class1Attr && class2Attr) {
226 if (CFEqual(class1Attr->name, class2Attr->name) && class1Attr->kind == class2Attr->kind && class1Attr->flags == class2Attr->flags && class1Attr->copyValue == class2Attr->copyValue && class1Attr->setValue == class2Attr->setValue) {
227 attrIndex++;
228 class1Attr = class1->attrs[attrIndex];
229 class2Attr = class2->attrs[attrIndex];
230 }
231 else {
232 return false;
233 }
234 }
235
236 // if everything has checked out to this point, and we've hit the end of both class's attr list, then they're equal
237 if (class1Attr == NULL && class2Attr == NULL) {
238 return true;
239 }
240 }
241
242 return false;
243 }
244
245 static bool ShouldRenameTable(const SecDbClass* class1, const SecDbClass* class2, int oldTableVersion)
246 {
247 return oldTableVersion < 10 || !DBClassesAreEqual(class1, class2);
248 }
249
250 #define SCHEMA_VERSION(schema) ((((schema)->minorVersion) << 8) | ((schema)->majorVersion))
251 #define VERSION_MAJOR(version) ((version) & 0xff)
252 #define VERSION_MINOR(version) (((version) >> 8) & 0xff)
253 #define VERSION_NEW(version) ((version) & 0xffff)
254 #define VERSION_OLD(version) (((version) >> 16) & 0xffff)
255
256 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
257 static bool UpgradeSchemaPhase1(SecDbConnectionRef dbt, const SecDbSchema *oldSchema, CFErrorRef *error)
258 {
259 int oldVersion = SCHEMA_VERSION(oldSchema);
260 const SecDbSchema *newSchema = current_schema();
261 int newVersion = SCHEMA_VERSION(newSchema);
262 __block bool ok = true;
263 SecDbClass const *const *oldClass;
264 SecDbClass const *const *newClass;
265 SecDbQueryRef query = NULL;
266 CFMutableStringRef sql = NULL;
267 SecDbClass* renamedOldClass = NULL;
268 #if TARGET_OS_EMBEDDED
269 __block int64_t itemsMigrated = 0;
270 struct timeval start;
271
272 gettimeofday(&start, NULL);
273 #endif
274
275 // Rename existing tables to names derived from old schema names
276 sql = CFStringCreateMutable(NULL, 0);
277 bool oldClassDone = false;
278 CFMutableArrayRef classIndexesForNewTables = CFArrayCreateMutable(NULL, 0, NULL);
279 int classIndex = 0;
280 for (oldClass = oldSchema->classes, newClass = newSchema->classes;
281 *newClass != NULL; classIndex++, oldClass++, newClass++) {
282 oldClassDone |= (*oldClass) == NULL; // Check if the new schema has more tables than the old
283
284 if (!oldClassDone && !CFEqual((*oldClass)->name, (*newClass)->name) && ShouldRenameTable(*oldClass, *newClass, oldSchema->majorVersion)) {
285 CFStringAppendFormat(sql, NULL, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass)->name, (*oldClass)->name);
286 CFArrayAppendValue(classIndexesForNewTables, (void*)(long)classIndex);
287
288 } else if (!oldClassDone && !DBClassesAreEqual(*oldClass, *newClass)) {
289 CFStringAppendFormat(sql, NULL, CFSTR("ALTER TABLE %@ RENAME TO %@_old;"), (*newClass)->name, (*oldClass)->name);
290 CFArrayAppendValue(classIndexesForNewTables, (void*)(long)classIndex);
291 }
292
293 if(oldClassDone && *newClass) {
294 // These should be no-ops, unless you're upgrading a previously-upgraded database with an invalid version number
295 CFStringAppendFormat(sql, NULL, CFSTR("DROP TABLE IF EXISTS %@;"), (*newClass)->name);
296
297 if (classIndexesForNewTables) {
298 CFArrayAppendValue(classIndexesForNewTables, (void*)(long)classIndex);
299 }
300 }
301 }
302
303 if(CFStringGetLength(sql) > 0) {
304 require_action_quiet(ok &= SecDbExec(dbt, sql, error), out,
305 LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase1AlterTables, error ? *error : NULL));
306 }
307 CFReleaseNull(sql);
308
309 // Drop indices that that new schemas will use
310 sql = CFStringCreateMutable(NULL, 0);
311 for (newClass = newSchema->classes; *newClass != NULL; newClass++) {
312 SecDbForEachAttrWithMask((*newClass), desc, kSecDbIndexFlag | kSecDbInFlag) {
313 CFStringAppendFormat(sql, 0, CFSTR("DROP INDEX IF EXISTS %@%@;"), (*newClass)->name, desc->name);
314 if (desc->kind == kSecDbSyncAttr) {
315 CFStringAppendFormat(sql, 0, CFSTR("DROP INDEX IF EXISTS %@%@0;"), (*newClass)->name, desc->name);
316 }
317 }
318 }
319 require_action_quiet(ok &= SecDbExec(dbt, sql, error), out,
320 LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase1DropIndices, error ? *error : NULL));
321 CFReleaseNull(sql);
322
323 // Create tables for new schema.
324 require_action_quiet(ok &= SecItemDbCreateSchema(dbt, newSchema, classIndexesForNewTables, false, error), out,
325 LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase1CreateSchema, error ? *error : NULL));
326 // Go through all classes of current schema to transfer all items to new tables.
327 for (oldClass = oldSchema->classes, newClass = newSchema->classes;
328 *oldClass != NULL && *newClass != NULL; oldClass++, newClass++) {
329
330 if (CFEqual((*oldClass)->name, (*newClass)->name) && DBClassesAreEqual(*oldClass, *newClass)) {
331 continue;
332 }
333
334 secnotice("upgr", "Upgrading table %@", (*oldClass)->name);
335
336 // Create a new 'old' class with a new 'old' name.
337 int count = 0;
338 SecDbForEachAttr(*oldClass, attr) {
339 count++;
340 }
341 if(renamedOldClass) {
342 CFReleaseNull(renamedOldClass->name);
343 free(renamedOldClass);
344 }
345 renamedOldClass = (SecDbClass*) malloc(sizeof(SecDbClass) + sizeof(SecDbAttr*)*(count+1));
346 renamedOldClass->name = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@_old"), (*oldClass)->name);
347 renamedOldClass->itemclass = (*oldClass)->itemclass;
348 for(; count >= 0; count--) {
349 renamedOldClass->attrs[count] = (*oldClass)->attrs[count];
350 }
351
352 // SecDbItemSelect only works for item classes.
353 if((*oldClass)->itemclass) {
354 // Prepare query to iterate through all items in cur_class.
355 if (query != NULL)
356 query_destroy(query, NULL);
357 require_quiet(query = query_create(renamedOldClass, SecMUSRGetAllViews(), NULL, error), out);
358
359 ok &= SecDbItemSelect(query, dbt, error, ^bool(const SecDbAttr *attr) {
360 // We are interested in all attributes which are physically present in the DB.
361 return (attr->flags & kSecDbInFlag) != 0;
362 }, ^bool(const SecDbAttr *attr) {
363 // No filtering please.
364 return false;
365 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
366 CFErrorRef localError = NULL;
367
368 #if TARGET_OS_EMBEDDED
369 itemsMigrated++;
370 #endif
371 // Switch item to the new class.
372 item->class = *newClass;
373
374 if (isClassD(item)) {
375 // Decrypt the item.
376 ok &= SecDbItemEnsureDecrypted(item, true, &localError);
377 require_quiet(ok, out);
378
379 // Delete SHA1 field from the item, so that it is newly recalculated before storing
380 // the item into the new table.
381 require_quiet(ok &= SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, error),
382 kCFNull, error), out);
383 } else {
384 // Leave item encrypted, do not ever try to decrypt it since it will fail.
385 item->_edataState = kSecDbItemAlwaysEncrypted;
386 }
387 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
388 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
389 // <rdar://problem/33401870>
390 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup), kSecAttrAccessGroupToken) &&
391 SecDbItemGetCachedValueWithName(item, kSecAttrTokenID) == NULL) {
392 secnotice("upgr", "dropping item during schema upgrade due to agrp=com.apple.token: %@", item);
393 } else {
394 // Insert new item into the new table.
395 if (!SecDbItemInsert(item, dbt, &localError)) {
396 secerror("item: %@ insert during upgrade: %@", item, localError);
397 ok = false;
398 }
399 }
400
401 out:
402 if (localError) {
403 OSStatus status = SecErrorGetOSStatus(localError);
404
405 switch (status) {
406 // continue to upgrade and don't propagate errors for insert failures
407 // that are typical of a single item failure
408 case errSecDecode:
409 case errSecDuplicateItem:
410 ok = true;
411 break;
412 case errSecInteractionNotAllowed:
413 case errSecAuthNeeded:
414 ok = true;
415 break;
416 // This does not mean the keychain is hosed, we just can't use it right now
417 #if USE_KEYSTORE
418 case kAKSReturnNotReady:
419 case kAKSReturnTimeout:
420 #endif
421 case errSecNotAvailable:
422 secnotice("upgr", "Bailing in phase 1 because AKS is unavailable: %@", localError);
423 // FALLTHROUGH
424 default:
425 ok &= CFErrorPropagate(CFRetainSafe(localError), error);
426 break;
427 }
428 CFReleaseSafe(localError);
429 }
430
431 *stop = !ok;
432
433 });
434
435 require_action_quiet(ok, out,
436 LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase1Items, error ? *error : NULL));
437 } else {
438 // This table does not contain secdb items, and must be transferred without using SecDbItemSelect.
439 // For now, this code does not support removing or renaming any columns, or adding any new non-null columns.
440 CFReleaseNull(sql);
441 sql = CFStringCreateMutable(NULL, 0);
442 bool comma = false;
443
444 CFMutableStringRef columns = CFStringCreateMutable(NULL, 0);
445
446 SecDbForEachAttr(renamedOldClass, attr) {
447 if(comma) {
448 CFStringAppendFormat(columns, NULL, CFSTR(","));
449 }
450 CFStringAppendFormat(columns, NULL, CFSTR("%@"), attr->name);
451 comma = true;
452 }
453
454 CFStringAppendFormat(sql, NULL, CFSTR("INSERT OR REPLACE INTO %@ (%@) SELECT %@ FROM %@;"), (*newClass)->name, columns, columns, renamedOldClass->name);
455
456 CFReleaseNull(columns);
457 require_action_quiet(ok &= SecDbExec(dbt, sql, error), out,
458 LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase1NonItems, error ? *error : NULL));
459 }
460 }
461
462 // Remove old tables from the DB.
463 CFReleaseNull(sql);
464 sql = CFStringCreateMutable(NULL, 0);
465 for (oldClass = oldSchema->classes, newClass = newSchema->classes;
466 *oldClass != NULL && *newClass != NULL; oldClass++, newClass++) {
467 if (CFEqual((*oldClass)->name, (*newClass)->name) && DBClassesAreEqual(*oldClass, *newClass)) {
468 continue;
469 }
470
471 CFStringAppendFormat(sql, NULL, CFSTR("DROP TABLE %@_old;"), (*oldClass)->name);
472 }
473
474 if(CFStringGetLength(sql) > 0) {
475 require_action_quiet(ok &= SecDbExec(dbt, sql, error), out,
476 LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase1DropOld, error ? *error : NULL));
477 }
478 out:
479 #if TARGET_OS_EMBEDDED
480 measureUpgradePhase1(&start, ok, SecBucket2Significant(itemsMigrated));
481 #endif
482
483 if (query != NULL) {
484 query_destroy(query, NULL);
485 }
486 CFReleaseSafe(sql);
487 CFReleaseNull(classIndexesForNewTables);
488 if(renamedOldClass) {
489 CFReleaseNull(renamedOldClass->name);
490 free(renamedOldClass);
491 }
492 return ok;
493 }
494
495 __thread SecDbConnectionRef dbt = NULL;
496
497 // Goes through all tables represented by old_schema and tries to migrate all items from them into new (current version) tables.
498 static bool UpgradeItemPhase2(SecDbConnectionRef inDbt, bool *inProgress, int oldVersion, CFErrorRef *error) {
499 SecDbConnectionRef oldDbt = dbt;
500 dbt = inDbt;
501 __block bool ok = true;
502 SecDbQueryRef query = NULL;
503 #if TARGET_OS_EMBEDDED
504 __block int64_t itemsMigrated = 0;
505 struct timeval start;
506
507 gettimeofday(&start, NULL);
508 #endif
509
510 // Go through all classes in new schema
511 const SecDbSchema *newSchema = current_schema();
512 int newVersion = SCHEMA_VERSION(newSchema);
513 for (const SecDbClass *const *class = newSchema->classes; *class != NULL && !*inProgress; class++) {
514 if(!((*class)->itemclass)) {
515 //Don't try to decrypt non-item 'classes'
516 continue;
517 }
518
519 const SecDbAttr *pdmn = SecDbClassAttrWithKind(*class, kSecDbAccessAttr, error);
520 if (pdmn == nil) {
521 continue;
522 }
523
524 // Prepare query to go through all non-DK|DKU items
525 if (query != NULL) {
526 query_destroy(query, NULL);
527 }
528 require_action_quiet(query = query_create(*class, SecMUSRGetAllViews(), NULL, error), out, ok = false);
529 ok &= SecDbItemSelect(query, dbt, error, NULL, ^bool(const SecDbAttr *attr) {
530 // No simple per-attribute filtering.
531 return false;
532 }, ^bool(CFMutableStringRef sql, bool *needWhere) {
533 // Select only non-D-class items
534 SecDbAppendWhereOrAnd(sql, needWhere);
535 CFStringAppendFormat(sql, NULL, CFSTR("NOT %@ IN (?,?)"), pdmn->name);
536 return true;
537 }, ^bool(sqlite3_stmt *stmt, int col) {
538 return SecDbBindObject(stmt, col++, kSecAttrAccessibleAlwaysPrivate, error) &&
539 SecDbBindObject(stmt, col++, kSecAttrAccessibleAlwaysThisDeviceOnlyPrivate, error);
540 }, ^(SecDbItemRef item, bool *stop) {
541 CFErrorRef localError = NULL;
542
543 #if TARGET_OS_EMBEDDED
544 itemsMigrated++;
545 #endif
546
547 // Decrypt the item.
548 if (SecDbItemEnsureDecrypted(item, true, &localError)) {
549
550 // Delete SHA1 field from the item, so that it is newly recalculated before storing
551 // the item into the new table.
552 require_quiet(ok = SecDbItemSetValue(item, SecDbClassAttrWithKind(item->class, kSecDbSHA1Attr, error),
553 kCFNull, &localError), out);
554 // Drop items with kSecAttrAccessGroupToken, as these items should not be there at all. Since agrp attribute
555 // is always stored as cleartext in the DB column, we can always rely on this attribute being present in item->attributes.
556 // <rdar://problem/33401870>
557 if (CFEqualSafe(SecDbItemGetCachedValueWithName(item, kSecAttrAccessGroup), kSecAttrAccessGroupToken) &&
558 SecDbItemGetCachedValueWithName(item, kSecAttrTokenID) == NULL) {
559 secnotice("upgr", "dropping item during item upgrade due to agrp=com.apple.token: %@", item);
560 ok = SecDbItemDelete(item, dbt, kCFBooleanFalse, &localError);
561 } else {
562 // Replace item with the new value in the table; this will cause the item to be decoded and recoded back,
563 // incl. recalculation of item's hash.
564 ok = SecDbItemUpdate(item, item, dbt, false, query->q_uuid_from_primary_key, &localError);
565 }
566 }
567
568 if (localError) {
569 CFIndex status = CFErrorGetCode(localError);
570
571 switch (status) {
572 case errSecDecode: {
573 // Items producing errSecDecode are silently dropped - they are not decodable and lost forever.
574 // make sure we use a local error so that this error is not proppaged upward and cause a
575 // migration failure.
576 CFErrorRef deleteError = NULL;
577 (void)SecDbItemDelete(item, dbt, false, &deleteError);
578 CFReleaseNull(deleteError);
579 ok = true;
580 break;
581 }
582 case errSecInteractionNotAllowed:
583 // If we are still not able to decrypt the item because the class key is not released yet,
584 // remember that DB still needs phase2 migration to be run next time a connection is made. Also
585 // stop iterating next items, it would be just waste of time because the whole iteration will be run
586 // next time when this phase2 will be rerun.
587 LKAReportKeychainUpgradeOutcome(oldVersion, newVersion, LKAKeychainUpgradeOutcomeLocked);
588 *inProgress = true;
589 *stop = true;
590 ok = true;
591 break;
592 case errSecAuthNeeded:
593 // errSecAuthNeeded means that it is an ACL-based item which requires authentication (or at least
594 // ACM context, which we do not have).
595 ok = true;
596 break;
597 case SQLITE_CONSTRAINT: // yeah...
598 if (!CFEqual(kSecDbErrorDomain, CFErrorGetDomain(localError))) {
599 secerror("Received SQLITE_CONSTRAINT with wrong error domain. Huh? Item: %@, error: %@", item, localError);
600 break;
601 }
602 case errSecDuplicateItem:
603 // continue to upgrade and don't propagate errors for insert failures
604 // that are typical of a single item failure
605 secnotice("upgr", "Ignoring duplicate item: %@", item);
606 secdebug("upgr", "Duplicate item error: %@", localError);
607 ok = true;
608 break;
609 #if USE_KEYSTORE
610 case kAKSReturnNotReady:
611 case kAKSReturnTimeout:
612 #endif
613 case errSecNotAvailable:
614 *inProgress = true; // We're not done, call me again later!
615 secnotice("upgr", "Bailing in phase 2 because AKS is unavailable: %@", localError);
616 // FALLTHROUGH
617 default:
618 // Other errors should abort the migration completely.
619 ok = CFErrorPropagate(CFRetainSafe(localError), error);
620 break;
621 }
622 }
623
624 out:
625 CFReleaseSafe(localError);
626 *stop = *stop || !ok;
627
628 });
629 require_action(ok, out, LKAReportKeychainUpgradeOutcomeWithError(oldVersion, newVersion, LKAKeychainUpgradeOutcomePhase2, error ? *error : NULL));
630 }
631
632 #if TARGET_OS_EMBEDDED
633 measureUpgradePhase2(&start, SecBucket2Significant(itemsMigrated));
634 #endif
635
636 out:
637 if (query != NULL)
638 query_destroy(query, NULL);
639
640 dbt = oldDbt;
641 return ok;
642 }
643
644 static bool SecKeychainDbUpgradeFromVersion(SecDbConnectionRef dbt, int version, bool *inProgress, CFErrorRef *error) {
645 __block bool didPhase2 = false;
646 __block bool ok = true;
647 __block CFErrorRef localError = NULL;
648
649 if (error)
650 *error = NULL;
651
652 const SecDbSchema *newSchema = current_schema();
653 int newVersion = SCHEMA_VERSION(newSchema);
654 bool skipped_upgrade = false;
655
656 // If DB schema is the one we want, we are done.
657 require_action_quiet(SCHEMA_VERSION(newSchema) != version, out, skipped_upgrade = true);
658
659 // Check if the schema of the database on disk is the same major, but newer version then what we have
660 // in code, lets just skip this since a newer version of the OS have upgrade it. Since its the same
661 // major, its a promise that it will be compatible.
662 if (newSchema->majorVersion == VERSION_MAJOR(version) && newSchema->minorVersion < VERSION_MINOR(version)) {
663 secnotice("upgr", "skipping upgrade since minor is newer");
664 goto out;
665 }
666
667 ok &= SecDbTransaction(dbt, kSecDbExclusiveTransactionType, &localError, ^(bool *commit) {
668 CFStringRef sql = NULL;
669 bool didPhase1 = false;
670
671 // Get version again once we start a transaction, someone else might change the migration state.
672 int version2 = 0;
673 require_quiet(ok = SecKeychainDbGetVersion(dbt, &version2, &localError), out);
674 // Check if someone has raced us to the migration of the database
675 require_action(version == version2, out, CFReleaseNull(localError); ok = true);
676
677 require_quiet(SCHEMA_VERSION(newSchema) != version2, out);
678
679 // If this is empty database, just create table according to schema and be done with it.
680 require_action_quiet(version2 != 0, out, ok = SecItemDbCreateSchema(dbt, newSchema, NULL, true, &localError);
681 LKAReportKeychainUpgradeOutcomeWithError(version2, newVersion, LKAKeychainUpgradeOutcomeNewDb, localError));
682
683 int oldVersion = VERSION_OLD(version2);
684 version2 = VERSION_NEW(version2);
685
686 require_action_quiet(version2 == SCHEMA_VERSION(newSchema) || oldVersion == 0, out,
687 ok = SecDbError(SQLITE_CORRUPT, &localError,
688 CFSTR("Half migrated but obsolete DB found: found 0x%x(0x%x) but 0x%x is needed"),
689 version2, oldVersion, SCHEMA_VERSION(newSchema));
690 LKAReportKeychainUpgradeOutcome(version2, newVersion, LKAKeychainUpgradeOutcomeObsoleteDb));
691
692 // Check whether we have both old and new tables in the DB.
693 if (oldVersion == 0) {
694 // Pure old-schema migration attempt, with full blown table renames etc (a.k.a. phase1)
695 oldVersion = version2;
696 version2 = SCHEMA_VERSION(newSchema);
697
698 // Find schema for old database.
699 const SecDbSchema *oldSchema = NULL;
700 for (const SecDbSchema * const *pschema = all_schemas(); *pschema; ++pschema) {
701 if (SCHEMA_VERSION((*pschema)) == oldVersion) {
702 oldSchema = *pschema;
703 break;
704 }
705 }
706
707 // If we are attempting to upgrade from a version for which we have no schema, fail.
708 require_action_quiet(oldSchema != NULL, out,
709 ok = SecDbError(SQLITE_CORRUPT, &localError, CFSTR("no schema for version: 0x%x"), oldVersion);
710 secerror("no schema for version 0x%x", oldVersion);
711 LKAReportKeychainUpgradeOutcome(version2, newVersion, LKAKeychainUpgradeOutcomeNoSchema));
712
713 secnotice("upgr", "Upgrading from version 0x%x to 0x%x", oldVersion, SCHEMA_VERSION(newSchema));
714 SecSignpostStart(SecSignpostUpgradePhase1);
715 require_action(ok = UpgradeSchemaPhase1(dbt, oldSchema, &localError), out, secerror("upgrade: Upgrade phase1 failed: %@", localError));
716 SecSignpostStop(SecSignpostUpgradePhase1);
717
718 didPhase1 = true;
719 }
720
721 {
722 CFErrorRef phase2Error = NULL;
723
724 SecSignpostStart(SecSignpostUpgradePhase2);
725
726 // Lets try to go through non-D-class items in new tables and apply decode/encode on them
727 // If this fails the error will be ignored after doing a phase1 since but not in the second
728 // time when we are doing phase2.
729 ok = UpgradeItemPhase2(dbt, inProgress, version2, &phase2Error);
730 if (!ok) {
731 if (didPhase1) {
732 *inProgress = true;
733 ok = true;
734 CFReleaseNull(phase2Error);
735 } else {
736 SecErrorPropagate(phase2Error, &localError);
737 }
738 }
739 require_action(ok, out, secerror("upgrade: Upgrade phase2 (%d) failed: %@", didPhase1, localError));
740
741 if (!*inProgress) {
742 // If either migration path we did reported that the migration was complete, signalize that
743 // in the version database by cleaning oldVersion (which is stored in upper halfword of the version)
744 secnotice("upgr", "Done upgrading from version 0x%x to 0x%x", oldVersion, SCHEMA_VERSION(newSchema));
745 oldVersion = 0;
746
747 didPhase2 = true;
748 SecSignpostStop(SecSignpostUpgradePhase2);
749 }
750 }
751
752 // Update database version table.
753 uint32_t major = (VERSION_MAJOR(version2)) | (VERSION_MAJOR(oldVersion) << 16);
754 uint32_t minor = (VERSION_MINOR(version2)) | (VERSION_MINOR(oldVersion) << 16);
755 secnotice("upgr", "Upgrading saving version major 0x%x minor 0x%x", major, minor);
756 sql = CFStringCreateWithFormat(NULL, NULL, CFSTR("UPDATE tversion SET version='%d', minor='%d'"),
757 major, minor);
758 require_action_quiet(ok = SecDbExec(dbt, sql, &localError), out, secerror("upgrade: Setting version failed: %@", localError));
759
760 out:
761 if (!ok) {
762 secerror("upgrade: SecDB upgrade failed: %@", localError);
763 }
764 CFReleaseSafe(sql);
765 *commit = ok;
766 });
767
768 if (ok && didPhase2) {
769 #if TARGET_OS_EMBEDDED
770 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-success"), 1);
771 #endif
772 }
773
774 out:
775 if (!ok || localError) {
776 // TODO: This logic should be inverted to a do-not-corrupt-unless default, <rdar://problem/29771874>
777 /*
778 * We assume that database is corrupt at this point, but we need to
779 * check if the error we got isn't severe enough to mark the database as corrupt.
780 * In those cases we opt out of corrupting the database.
781 */
782 bool markedCorrupt = true;
783
784 if (ok) {
785 secwarning("upgrade: error has been set but status is true");
786 ok = false;
787 }
788 secerror("upgrade: error occurred, considering marking database as corrupt: %@", localError);
789 if (localError) {
790 CFStringRef domain = CFErrorGetDomain(localError);
791 CFIndex code = CFErrorGetCode(localError);
792
793 if ((CFEqualSafe(domain, kSecDbErrorDomain) &&
794 ((code & 0xff) == SQLITE_LOCKED || (code & 0xff) == SQLITE_BUSY || (code & 0xff) == SQLITE_FULL)) ||
795 #if USE_KEYSTORE
796 code == kAKSReturnNotReady || code == kAKSReturnTimeout ||
797 #endif
798 code == errSecNotAvailable)
799 {
800 secerror("upgrade: not marking keychain database corrupt for error: %@", localError);
801 markedCorrupt = false;
802 CFReleaseNull(localError);
803 } else {
804 secerror("upgrade: unable to complete upgrade, marking DB as corrupt: %@", localError);
805 }
806 } else {
807 secerror("upgrade: unable to complete upgrade and no error object returned, marking DB as corrupt");
808 }
809 if (markedCorrupt) {
810 secerror("upgrade: marking database as corrupt");
811 SecDbCorrupt(dbt, localError);
812 #if TARGET_OS_EMBEDDED
813 SecADSetValueForScalarKey(CFSTR("com.apple.keychain.migration-failure"), 1);
814 #endif
815 }
816 } else {
817 // Things seemed to go okay!
818 if (didPhase2) {
819 LKAReportKeychainUpgradeOutcome(version, newVersion, LKAKeychainUpgradeOutcomeSuccess);
820 }
821
822 //If we're done here, we should opportunistically re-add all indices (just in case)
823 if(skipped_upgrade || didPhase2) {
824 // Create indices, ignoring all errors
825 for (SecDbClass const* const* newClass = newSchema->classes; *newClass; ++newClass) {
826 SecDbForEachAttrWithMask((*newClass), desc, kSecDbIndexFlag | kSecDbInFlag) {
827 CFStringRef sql = NULL;
828 CFErrorRef localError = NULL;
829 bool localOk = true;
830
831 if (desc->kind == kSecDbSyncAttr) {
832 // Replace the complete sync index with a partial index for sync=0. Most items are sync=1, so the complete index isn't helpful for sync=1 queries.
833 sql = CFStringCreateWithFormat(NULL, NULL, CFSTR("DROP INDEX IF EXISTS %@%@; CREATE INDEX IF NOT EXISTS %@%@0 on %@(%@) WHERE %@=0;"),
834 (*newClass)->name, desc->name, (*newClass)->name, desc->name, (*newClass)->name, desc->name, desc->name);
835 } else {
836 sql = CFStringCreateWithFormat(NULL, NULL, CFSTR("CREATE INDEX IF NOT EXISTS %@%@ ON %@(%@);"), (*newClass)->name, desc->name, (*newClass)->name, desc->name);
837 }
838 localOk &= SecDbExec(dbt, sql, &localError);
839 CFReleaseNull(sql);
840
841 if(!localOk) {
842 secerror("upgrade: unable to opportunistically create index (%@,%@): %@", (*newClass)->name, desc->name, localError);
843 }
844 CFReleaseNull(localError);
845 }
846 }
847 }
848 }
849 if (localError) {
850 if (error) {
851 *error = (CFErrorRef)CFRetain(localError);
852 }
853 CFReleaseNull(localError);
854 }
855
856 return ok;
857 }
858
859 static bool accessGroupIsNetworkExtensionAndClientIsEntitled(CFStringRef accessGroup, SecurityClient* client)
860 {
861 return client && client->canAccessNetworkExtensionAccessGroups && accessGroup && CFStringHasSuffix(accessGroup, kSecNetworkExtensionAccessGroupSuffix);
862 }
863
864 /* AUDIT[securityd](done):
865 accessGroup (ok) is a caller provided, non NULL CFTypeRef.
866
867 Return true iff accessGroup is allowable according to accessGroups.
868 */
869 bool accessGroupsAllows(CFArrayRef accessGroups, CFStringRef accessGroup, SecurityClient* client) {
870 /* NULL accessGroups is wildcard. */
871 if (!accessGroups)
872 return true;
873 /* Make sure we have a string. */
874 if (!isString(accessGroup))
875 return false;
876
877 /* Having the special accessGroup "*" allows access to all accessGroups. */
878 CFRange range = { 0, CFArrayGetCount(accessGroups) };
879 if (range.length &&
880 (CFArrayContainsValue(accessGroups, range, accessGroup) ||
881 CFArrayContainsValue(accessGroups, range, CFSTR("*")) ||
882 accessGroupIsNetworkExtensionAndClientIsEntitled(accessGroup, client)))
883 return true;
884
885 return false;
886 }
887
888 bool itemInAccessGroup(CFDictionaryRef item, CFArrayRef accessGroups) {
889 return accessGroupsAllows(accessGroups,
890 CFDictionaryGetValue(item, kSecAttrAccessGroup), NULL);
891 }
892
893
894 static CF_RETURNS_RETAINED CFDataRef SecServerExportBackupableKeychain(SecDbConnectionRef dbt,
895 SecurityClient *client,
896 keybag_handle_t src_keybag, keybag_handle_t dest_keybag, CFErrorRef *error) {
897 CFDataRef data_out = NULL;
898
899 SecSignpostStart(SecSignpostBackupKeychainBackupable);
900
901 /* Export everything except the items for which SecItemIsSystemBound()
902 returns true. */
903 CFDictionaryRef keychain = SecServerCopyKeychainPlist(dbt, client,
904 src_keybag, dest_keybag, kSecBackupableItemFilter,
905 error);
906 if (keychain) {
907 data_out = CFPropertyListCreateData(kCFAllocatorDefault, keychain,
908 kCFPropertyListBinaryFormat_v1_0,
909 0, error);
910 CFRelease(keychain);
911 }
912 SecSignpostStop(SecSignpostBackupKeychainBackupable);
913
914 return data_out;
915 }
916
917 static bool SecServerImportBackupableKeychain(SecDbConnectionRef dbt,
918 SecurityClient *client,
919 keybag_handle_t src_keybag,
920 keybag_handle_t dest_keybag,
921 CFDataRef data,
922 CFErrorRef *error)
923 {
924 return kc_transaction(dbt, error, ^{
925 bool ok = false;
926 CFDictionaryRef keychain;
927
928 SecSignpostStart(SecSignpostRestoreKeychainBackupable);
929
930 keychain = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
931 kCFPropertyListImmutable, NULL,
932 error);
933 if (keychain) {
934 if (isDictionary(keychain)) {
935 ok = SecServerImportKeychainInPlist(dbt,
936 client,
937 src_keybag,
938 dest_keybag,
939 keychain,
940 kSecBackupableItemFilter,
941 false, // Restoring backup should not remove stuff that got into the keychain before us
942 error);
943 } else {
944 ok = SecError(errSecParam, error, CFSTR("import: keychain is not a dictionary"));
945 }
946 CFRelease(keychain);
947 }
948
949 SecSignpostStop(SecSignpostRestoreKeychainBackupable);
950
951 return ok;
952 });
953 }
954
955 #if USE_KEYSTORE
956 /*
957 * Similar to ks_open_keybag, but goes through MKB interface
958 */
959 static bool mkb_open_keybag(CFDataRef keybag, CFDataRef password, MKBKeyBagHandleRef *handle, bool emcs, CFErrorRef *error) {
960 kern_return_t rc;
961 MKBKeyBagHandleRef mkbhandle = NULL;
962
963 rc = MKBKeyBagCreateWithData(keybag, &mkbhandle);
964 if (rc != kMobileKeyBagSuccess) {
965 return SecKernError(rc, error, CFSTR("MKBKeyBagCreateWithData failed: %d"), rc);
966 }
967
968 if (!emcs) {
969 rc = MKBKeyBagUnlock(mkbhandle, password);
970 if (rc != kMobileKeyBagSuccess) {
971 CFRelease(mkbhandle);
972 return SecKernError(rc, error, CFSTR("failed to unlock bag: %d"), rc);
973 }
974 } else {
975 secnotice("keychainbackup", "skipping keybag unlock for EMCS");
976 }
977
978 *handle = mkbhandle;
979
980 return true;
981 }
982 #endif
983
984
985 static CFDataRef SecServerKeychainCreateBackup(SecDbConnectionRef dbt, SecurityClient *client, CFDataRef keybag,
986 CFDataRef password, bool emcs, CFErrorRef *error) {
987 CFDataRef backup = NULL;
988 keybag_handle_t backup_keybag;
989
990 SecSignpostStart(SecSignpostBackupOpenKeybag);
991
992 #if USE_KEYSTORE
993 MKBKeyBagHandleRef mkbhandle = NULL;
994 require(mkb_open_keybag(keybag, password, &mkbhandle, emcs, error), out);
995
996 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle, &backup_keybag), out);
997
998 #else
999 backup_keybag = KEYBAG_NONE;
1000 #endif
1001 SecSignpostStop(SecSignpostBackupOpenKeybag);
1002 SecSignpostStart(SecSignpostBackupKeychain);
1003
1004 /* Export from system keybag to backup keybag. */
1005 backup = SecServerExportBackupableKeychain(dbt, client, KEYBAG_DEVICE, backup_keybag, error);
1006
1007 #if USE_KEYSTORE
1008 out:
1009 SecSignpostStop(SecSignpostBackupOpenKeybag);
1010
1011 if (mkbhandle)
1012 CFRelease(mkbhandle);
1013 #endif
1014 return backup;
1015 }
1016
1017 static bool SecServerKeychainRestore(SecDbConnectionRef dbt,
1018 SecurityClient *client,
1019 CFDataRef backup,
1020 CFDataRef keybag,
1021 CFDataRef password,
1022 CFErrorRef *error)
1023 {
1024 bool ok = false;
1025 keybag_handle_t backup_keybag;
1026
1027
1028 SecSignpostStart(SecSignpostRestoreOpenKeybag);
1029 #if USE_KEYSTORE
1030 MKBKeyBagHandleRef mkbhandle = NULL;
1031 require(mkb_open_keybag(keybag, password, &mkbhandle, false, error), out);
1032
1033 require_noerr(MKBKeyBagGetAKSHandle(mkbhandle, &backup_keybag), out);
1034 #else
1035 backup_keybag = KEYBAG_NONE;
1036 #endif
1037 SecSignpostStop(SecSignpostRestoreOpenKeybag);
1038 SecSignpostStart(SecSignpostRestoreKeychain);
1039
1040 /* Import from backup keybag to system keybag. */
1041 require(SecServerImportBackupableKeychain(dbt, client, backup_keybag, KEYBAG_DEVICE, backup, error), out);
1042
1043 ok = true;
1044 out:
1045 SecSignpostStop(SecSignpostRestoreKeychain);
1046 #if USE_KEYSTORE
1047 if (mkbhandle)
1048 CFRelease(mkbhandle);
1049 #endif
1050
1051 if (ok) {
1052 secwarning("Restore completed sucessfully");
1053 } else {
1054 secwarning("Restore failed with: %@", error ? *error : NULL);
1055 }
1056
1057 return ok;
1058 }
1059
1060
1061 // MARK - External SPI support code.
1062
1063 CFStringRef __SecKeychainCopyPath(void) {
1064 CFStringRef kcRelPath = NULL;
1065 if (use_hwaes()) {
1066 kcRelPath = CFSTR("keychain-2.db");
1067 } else {
1068 kcRelPath = CFSTR("keychain-2-debug.db");
1069 }
1070
1071 CFStringRef kcPath = NULL;
1072 CFURLRef kcURL = SecCopyURLForFileInKeychainDirectory(kcRelPath);
1073 if (kcURL) {
1074 kcPath = CFURLCopyFileSystemPath(kcURL, kCFURLPOSIXPathStyle);
1075 CFRelease(kcURL);
1076 }
1077 return kcPath;
1078 }
1079
1080 // MARK; -
1081 // MARK: kc_dbhandle init and reset
1082
1083 SecDbRef SecKeychainDbCreate(CFStringRef path, CFErrorRef* error) {
1084 __block CFErrorRef localerror = NULL;
1085
1086 SecDbRef kc = SecDbCreate(path, 0600, true, true, true, true, kSecDbMaxIdleHandles,
1087 ^bool (SecDbRef db, SecDbConnectionRef dbconn, bool didCreate, bool *callMeAgainForNextConnection, CFErrorRef *error)
1088 {
1089 // Upgrade from version 0 means create the schema in empty db.
1090 int version = 0;
1091 bool ok = true;
1092 if (!didCreate)
1093 ok = SecKeychainDbGetVersion(dbconn, &version, error);
1094
1095 ok = ok && SecKeychainDbUpgradeFromVersion(dbconn, version, callMeAgainForNextConnection, error);
1096 if (!ok)
1097 secerror("Upgrade %sfailed: %@", didCreate ? "from v0 " : "", error ? *error : NULL);
1098
1099 localerror = error ? *error : NULL;
1100
1101 if(ok) {
1102 // This block might get called many, many times due to callMeAgainForNextConnection.
1103 // When we no longer want to be called, we believe we're done. Begin the rest of initialization.
1104 if( !callMeAgainForNextConnection || !(*callMeAgainForNextConnection)) {
1105 SecKeychainDbInitialize(db);
1106 }
1107 }
1108
1109 return ok;
1110 });
1111
1112 if (kc) {
1113 SecDbSetCorruptionReset(kc, ^{
1114 SecDbResetMetadataKeys();
1115 });
1116 }
1117
1118 if(error) {
1119 *error = localerror;
1120 }
1121
1122 return kc;
1123 }
1124
1125 SecDbRef SecKeychainDbInitialize(SecDbRef db) {
1126
1127 #if OCTAGON
1128 if(SecCKKSIsEnabled()) {
1129 // This needs to be async, otherwise we get hangs between securityd, cloudd, and apsd
1130 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
1131 SecCKKSInitialize(db);
1132
1133 });
1134 }
1135 #endif
1136
1137 return db;
1138 }
1139
1140 static SecDbRef _kc_dbhandle = NULL;
1141 static dispatch_queue_t _kc_dbhandle_dispatch = NULL;
1142 static dispatch_once_t _kc_dbhandle_dispatch_onceToken = 0;
1143 static dispatch_queue_t get_kc_dbhandle_dispatch() {
1144 dispatch_once(&_kc_dbhandle_dispatch_onceToken, ^{
1145 _kc_dbhandle_dispatch = dispatch_queue_create("sec_kc_dbhandle", DISPATCH_QUEUE_SERIAL);
1146 });
1147
1148 return _kc_dbhandle_dispatch;
1149 }
1150
1151 static bool kc_dbhandle_init(CFErrorRef* error) {
1152 SecDbRef oldHandle = _kc_dbhandle;
1153 _kc_dbhandle = NULL;
1154 CFStringRef dbPath = __SecKeychainCopyPath();
1155 if (dbPath) {
1156 _kc_dbhandle = SecKeychainDbCreate(dbPath, error);
1157 CFRelease(dbPath);
1158 } else {
1159 secerror("no keychain path available");
1160 }
1161 if (oldHandle) {
1162 secerror("replaced %@ with %@", oldHandle, _kc_dbhandle);
1163 CFRelease(oldHandle);
1164 }
1165 // Having a dbhandle means we succeeded.
1166 return !!_kc_dbhandle;
1167 }
1168
1169 static SecDbRef kc_dbhandle(CFErrorRef* error)
1170 {
1171 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1172 if(_kc_dbhandle == NULL) {
1173 _SecDbServerSetup();
1174 kc_dbhandle_init(error);
1175 }
1176 });
1177 return _kc_dbhandle;
1178 }
1179
1180 /* For whitebox testing only */
1181 void SecKeychainDbReset(dispatch_block_t inbetween)
1182 {
1183 dispatch_sync(get_kc_dbhandle_dispatch(), ^{
1184 CFReleaseNull(_kc_dbhandle);
1185 SecDbResetMetadataKeys();
1186
1187 if (inbetween)
1188 inbetween();
1189 });
1190 }
1191
1192 static bool kc_acquire_dbt(bool writeAndRead, SecDbConnectionRef* dbconn, CFErrorRef *error) {
1193 SecDbRef db = kc_dbhandle(error);
1194 if (db == NULL) {
1195 if(error && !(*error)) {
1196 SecError(errSecDataNotAvailable, error, CFSTR("failed to get a db handle"));
1197 }
1198 return NULL;
1199 }
1200
1201 return SecDbConnectionAcquireRefMigrationSafe(db, !writeAndRead, dbconn, error);
1202 }
1203
1204 /* Return a per thread dbt handle for the keychain. If create is true create
1205 the database if it does not yet exist. If it is false, just return an
1206 error if it fails to auto-create. */
1207 bool kc_with_dbt(bool writeAndRead, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt))
1208 {
1209 return kc_with_custom_db(writeAndRead, true, NULL, error, perform);
1210 }
1211
1212 bool kc_with_dbt_non_item_tables(bool writeAndRead, CFErrorRef* error, bool (^perform)(SecDbConnectionRef dbt))
1213 {
1214 return kc_with_custom_db(writeAndRead, false, NULL, error, perform);
1215 }
1216
1217 bool kc_with_custom_db(bool writeAndRead, bool usesItemTables, SecDbRef db, CFErrorRef *error, bool (^perform)(SecDbConnectionRef dbt))
1218 {
1219 if (db && db != kc_dbhandle(error)) {
1220 __block bool result = false;
1221 if (writeAndRead) {
1222 return SecDbPerformWrite(db, error, ^(SecDbConnectionRef dbconn) {
1223 result = perform(dbconn);
1224 });
1225 }
1226 else {
1227 return SecDbPerformRead(db, error, ^(SecDbConnectionRef dbconn) {
1228 result = perform(dbconn);
1229 });
1230 }
1231 return result;
1232 }
1233
1234 if(dbt) {
1235 // The kc_with_dbt upthread will clean this up when it's done.
1236 return perform(dbt);
1237 }
1238
1239 if (writeAndRead && usesItemTables) {
1240 SecItemDataSourceFactoryGetDefault();
1241 }
1242
1243 bool ok = false;
1244 if (kc_acquire_dbt(writeAndRead, &dbt, error)) {
1245 ok = perform(dbt);
1246 SecDbConnectionRelease(dbt);
1247 dbt = NULL;
1248 }
1249 return ok;
1250 }
1251
1252 static bool
1253 items_matching_issuer_parent(SecDbConnectionRef dbt, CFArrayRef accessGroups, CFDataRef musrView,
1254 CFDataRef issuer, CFArrayRef issuers, int recurse)
1255 {
1256 Query *q;
1257 CFArrayRef results = NULL;
1258 CFIndex i, count;
1259 bool found = false;
1260
1261 if (CFArrayContainsValue(issuers, CFRangeMake(0, CFArrayGetCount(issuers)), issuer))
1262 return true;
1263
1264 /* XXX make musr supported */
1265 const void *keys[] = { kSecClass, kSecReturnRef, kSecAttrSubject };
1266 const void *vals[] = { kSecClassCertificate, kCFBooleanTrue, issuer };
1267 CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, vals, array_size(keys), NULL, NULL);
1268
1269 if (!query)
1270 return false;
1271
1272 CFErrorRef localError = NULL;
1273 q = query_create_with_limit(query, musrView, kSecMatchUnlimited, &localError);
1274 CFRelease(query);
1275 if (q) {
1276 s3dl_copy_matching(dbt, q, (CFTypeRef*)&results, accessGroups, &localError);
1277 query_destroy(q, &localError);
1278 }
1279 if (localError) {
1280 secerror("items matching issuer parent: %@", localError);
1281 CFReleaseNull(localError);
1282 return false;
1283 }
1284
1285 count = CFArrayGetCount(results);
1286 for (i = 0; (i < count) && !found; i++) {
1287 CFDictionaryRef cert_dict = (CFDictionaryRef)CFArrayGetValueAtIndex(results, i);
1288 CFDataRef cert_issuer = CFDictionaryGetValue(cert_dict, kSecAttrIssuer);
1289 if (CFEqual(cert_issuer, issuer))
1290 continue;
1291 if (recurse-- > 0)
1292 found = items_matching_issuer_parent(dbt, accessGroups, musrView, cert_issuer, issuers, recurse);
1293 }
1294 CFReleaseSafe(results);
1295
1296 return found;
1297 }
1298
1299 static bool
1300 _FilterWithPolicy(SecPolicyRef policy, CFDateRef date, SecCertificateRef cert)
1301 {
1302 CFDictionaryRef props = NULL;
1303 CFArrayRef keychains = NULL;
1304 CFArrayRef anchors = NULL;
1305 CFArrayRef certs = NULL;
1306 CFArrayRef chain = NULL;
1307 SecTrustRef trust = NULL;
1308
1309 SecTrustResultType trustResult;
1310 Boolean needChain = false;
1311 __block bool ok = false;
1312
1313 if (!policy || !cert) return false;
1314
1315 certs = CFArrayCreate(NULL, (const void **)&cert, (CFIndex)1, &kCFTypeArrayCallBacks);
1316 require_noerr_quiet(SecTrustCreateWithCertificates(certs, policy, &trust), cleanup);
1317
1318 /* Set evaluation date, if specified (otherwise current date is implied) */
1319 if (date && (CFGetTypeID(date) == CFDateGetTypeID())) {
1320 require_noerr_quiet(SecTrustSetVerifyDate(trust, date), cleanup);
1321 }
1322
1323 /* Check whether this is the X509 Basic policy, which means chain building */
1324 props = SecPolicyCopyProperties(policy);
1325 if (props) {
1326 CFTypeRef oid = (CFTypeRef) CFDictionaryGetValue(props, kSecPolicyOid);
1327 if (oid && (CFEqual(oid, kSecPolicyAppleX509Basic) ||
1328 CFEqual(oid, kSecPolicyAppleRevocation))) {
1329 needChain = true;
1330 }
1331 }
1332
1333 if (!needChain) {
1334 require_noerr_quiet(SecTrustEvaluateLeafOnly(trust, &trustResult), cleanup);
1335 } else {
1336 require_noerr_quiet(SecTrustEvaluate(trust, &trustResult), cleanup);
1337 }
1338
1339 require_quiet((trustResult == kSecTrustResultProceed ||
1340 trustResult == kSecTrustResultUnspecified ||
1341 trustResult == kSecTrustResultRecoverableTrustFailure), cleanup);
1342
1343 ok = true;
1344 #if TARGET_OS_IPHONE
1345 CFArrayRef properties = SecTrustCopyProperties(trust);
1346 #else
1347 CFArrayRef properties = SecTrustCopyProperties_ios(trust);
1348 #endif
1349 if (properties) {
1350 CFArrayForEach(properties, ^(const void *property) {
1351 CFDictionaryForEach((CFDictionaryRef)property, ^(const void *key, const void *value) {
1352 if (CFEqual((CFTypeRef)key, kSecPropertyKeyType) && CFEqual((CFTypeRef)value, kSecPropertyTypeError))
1353 ok = false;
1354 });
1355 });
1356 CFRelease(properties);
1357 }
1358
1359 cleanup:
1360 if(props) CFRelease(props);
1361 if(chain) CFRelease(chain);
1362 if(anchors) CFRelease(anchors);
1363 if(keychains) CFRelease(keychains);
1364 if(certs) CFRelease(certs);
1365 if(trust) CFRelease(trust);
1366
1367 return ok;
1368 }
1369
1370 static bool
1371 _FilterWithDate(CFDateRef validOnDate, SecCertificateRef cert)
1372 {
1373 if (!validOnDate || !cert) return false;
1374
1375 CFAbsoluteTime at, nb, na;
1376 at = CFDateGetAbsoluteTime((CFDateRef)validOnDate);
1377
1378 bool ok = true;
1379 nb = SecCertificateNotValidBefore(cert);
1380 na = SecCertificateNotValidAfter(cert);
1381
1382 if (nb == 0 || na == 0 || nb == na) {
1383 ok = false;
1384 secnotice("FilterWithDate", "certificate cannot operate");
1385 }
1386 else if (at < nb) {
1387 ok = false;
1388 secnotice("FilterWithDate", "certificate is not valid yet");
1389 }
1390 else if (at > na) {
1391 ok = false;
1392 secnotice("FilterWithDate", "certificate expired");
1393 }
1394
1395 return ok;
1396 }
1397
1398 static bool
1399 _FilterWithTrust(Boolean trustedOnly, SecCertificateRef cert)
1400 {
1401 if (!cert) return false;
1402 if (!trustedOnly) return true;
1403
1404 bool ok = false;
1405 CFArrayRef certArray = CFArrayCreate(NULL, (const void**)&cert, 1, &kCFTypeArrayCallBacks);
1406 SecTrustRef trust = NULL;
1407 SecPolicyRef policy = SecPolicyCreateBasicX509();
1408 require_quiet(policy, out);
1409
1410 require_noerr_quiet(SecTrustCreateWithCertificates(certArray, policy, &trust), out);
1411 SecTrustResultType trustResult;
1412 require_noerr_quiet(SecTrustEvaluate(trust, &trustResult), out);
1413
1414 require_quiet((trustResult == kSecTrustResultProceed ||
1415 trustResult == kSecTrustResultUnspecified), out);
1416 ok = true;
1417 out:
1418 CFReleaseSafe(trust);
1419 CFReleaseSafe(policy);
1420 CFReleaseSafe(certArray);
1421 return ok;
1422 }
1423
1424 static SecCertificateRef
1425 CopyCertificateFromItem(Query *q, CFDictionaryRef item) {
1426 SecCertificateRef certRef = NULL;
1427 CFDictionaryRef itemValue = NULL;
1428
1429 CFTypeRef tokenID = NULL;
1430 CFDataRef certData = NULL;
1431 if (q->q_class == identity_class()) {
1432 certData = CFDictionaryGetValue(item, kSecAttrIdentityCertificateData);
1433 tokenID = CFDictionaryGetValue(item, kSecAttrIdentityCertificateTokenID);
1434 } else if (q->q_class == cert_class()) {
1435 certData = CFDictionaryGetValue(item, kSecValueData);
1436 tokenID = CFDictionaryGetValue(item, kSecAttrTokenID);
1437 }
1438
1439 require_quiet(certData, out);
1440 if (tokenID != NULL) {
1441 CFErrorRef error = NULL;
1442 itemValue = SecTokenItemValueCopy(certData, &error);
1443 require_action_quiet(itemValue, out, { secerror("function SecTokenItemValueCopy failed with: %@", error); CFReleaseSafe(error); });
1444 CFDataRef tokenCertData = CFDictionaryGetValue(itemValue, kSecTokenValueDataKey);
1445 require_action_quiet(tokenCertData, out, { secerror("token item doesn't contain token value data");});
1446 certRef = SecCertificateCreateWithData(kCFAllocatorDefault, tokenCertData);
1447 }
1448 else
1449 certRef = SecCertificateCreateWithData(kCFAllocatorDefault, certData);
1450
1451 out:
1452 CFReleaseNull(itemValue);
1453 return certRef;
1454 }
1455
1456 bool match_item(SecDbConnectionRef dbt, Query *q, CFArrayRef accessGroups, CFDictionaryRef item)
1457 {
1458 bool ok = false;
1459 SecCertificateRef certRef = NULL;
1460 if (q->q_match_issuer) {
1461 CFDataRef issuer = CFDictionaryGetValue(item, kSecAttrIssuer);
1462 if (!items_matching_issuer_parent(dbt, accessGroups, q->q_musrView, issuer, q->q_match_issuer, 10 /*max depth*/))
1463 return ok;
1464 }
1465
1466 if (q->q_match_policy && (q->q_class == identity_class() || q->q_class == cert_class())) {
1467 if (!certRef)
1468 certRef = CopyCertificateFromItem(q, item);
1469 require_quiet(certRef, out);
1470 require_quiet(_FilterWithPolicy(q->q_match_policy, q->q_match_valid_on_date, certRef), out);
1471 }
1472
1473 if (q->q_match_valid_on_date && (q->q_class == identity_class() || q->q_class == cert_class())) {
1474 if (!certRef)
1475 certRef = CopyCertificateFromItem(q, item);
1476 require_quiet(certRef, out);
1477 require_quiet(_FilterWithDate(q->q_match_valid_on_date, certRef), out);
1478 }
1479
1480 if (q->q_match_trusted_only && (q->q_class == identity_class() || q->q_class == cert_class())) {
1481 if (!certRef)
1482 certRef = CopyCertificateFromItem(q, item);
1483 require_quiet(certRef, out);
1484 require_quiet(_FilterWithTrust(CFBooleanGetValue(q->q_match_trusted_only), certRef), out);
1485 }
1486
1487 /* Add future match checks here. */
1488 ok = true;
1489 out:
1490 CFReleaseSafe(certRef);
1491 return ok;
1492 }
1493
1494 /****************************************************************************
1495 **************** Beginning of Externally Callable Interface ****************
1496 ****************************************************************************/
1497
1498 static bool SecEntitlementError(OSStatus status, CFErrorRef *error)
1499 {
1500 #if TARGET_OS_OSX
1501 #define SEC_ENTITLEMENT_WARNING CFSTR("com.apple.application-identifier, com.apple.security.application-groups nor keychain-access-groups")
1502 #else
1503 #define SEC_ENTITLEMENT_WARNING CFSTR("application-identifier nor keychain-access-groups")
1504 #endif
1505
1506 return SecError(errSecMissingEntitlement, error, CFSTR("Client has neither %@ entitlements"), SEC_ENTITLEMENT_WARNING);
1507 }
1508
1509 static CFStringRef CopyAccessGroupForRowID(sqlite_int64 rowID, CFStringRef itemClass)
1510 {
1511 __block CFStringRef accessGroup = NULL;
1512
1513 __block CFErrorRef error = NULL;
1514 bool ok = kc_with_dbt(false, &error, ^bool(SecDbConnectionRef dbt) {
1515 CFStringRef table = CFEqual(itemClass, kSecClassIdentity) ? kSecClassCertificate : itemClass;
1516 CFStringRef sql = CFStringCreateWithFormat(NULL, NULL, CFSTR("SELECT agrp FROM %@ WHERE rowid == %u"), table, (unsigned int)rowID);
1517 bool dbOk = SecDbWithSQL(dbt, sql, &error, ^bool(sqlite3_stmt *stmt) {
1518 bool rowOk = SecDbForEach(dbt, stmt, &error, ^bool(int row_index) {
1519 accessGroup = CFStringCreateWithBytes(NULL, sqlite3_column_blob(stmt, 0), sqlite3_column_bytes(stmt, 0), kCFStringEncodingUTF8, false);
1520 return accessGroup != NULL;
1521 });
1522
1523 return (bool)(rowOk && accessGroup != NULL);
1524 });
1525
1526 CFReleaseNull(sql);
1527 return (bool)(dbOk && accessGroup);
1528 });
1529
1530 if (ok) {
1531 return accessGroup;
1532 }
1533 else {
1534 CFReleaseNull(accessGroup);
1535 return NULL;
1536 }
1537 }
1538
1539 /* AUDIT[securityd](done):
1540 query (ok) is a caller provided dictionary, only its cf type has been checked.
1541 */
1542 static bool
1543 SecItemServerCopyMatching(CFDictionaryRef query, CFTypeRef *result,
1544 SecurityClient *client, CFErrorRef *error)
1545 {
1546 CFArrayRef accessGroups = client->accessGroups;
1547 CFMutableArrayRef mutableAccessGroups = NULL;
1548
1549 CFIndex ag_count;
1550 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1551 return SecEntitlementError(errSecMissingEntitlement, error);
1552 }
1553
1554 SecSignpostStart(SecSignpostSecItemCopyMatching);
1555
1556 if (client->canAccessNetworkExtensionAccessGroups) {
1557 CFDataRef persistentRef = CFDictionaryGetValue(query, kSecValuePersistentRef);
1558 CFStringRef itemClass = NULL;
1559 sqlite_int64 itemRowID = 0;
1560 if (persistentRef && _SecItemParsePersistentRef(persistentRef, &itemClass, &itemRowID, NULL)) {
1561 CFStringRef accessGroup = CopyAccessGroupForRowID(itemRowID, itemClass);
1562 if (accessGroup && CFStringHasSuffix(accessGroup, kSecNetworkExtensionAccessGroupSuffix) && !CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), accessGroup)) {
1563 mutableAccessGroups = CFArrayCreateMutableCopy(NULL, 0, accessGroups);
1564 CFArrayAppendValue(mutableAccessGroups, accessGroup);
1565 accessGroups = mutableAccessGroups;
1566 }
1567 CFReleaseNull(accessGroup);
1568 }
1569 }
1570
1571 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1572 /* Having the special accessGroup "*" allows access to all accessGroups. */
1573 accessGroups = NULL;
1574 }
1575
1576 bool ok = false;
1577 Query *q = query_create_with_limit(query, client->musr, 1, error);
1578 if (q) {
1579 CFStringRef agrp = CFDictionaryGetValue(q->q_item, kSecAttrAccessGroup);
1580 if (agrp && accessGroupsAllows(accessGroups, agrp, client)) {
1581 // TODO: Return an error if agrp is not NULL and accessGroupsAllows() fails above.
1582 const void *val = agrp;
1583 accessGroups = CFArrayCreate(0, &val, 1, &kCFTypeArrayCallBacks);
1584 } else {
1585 CFRetainSafe(accessGroups);
1586 }
1587
1588 #if TARGET_OS_IPHONE
1589 if (q->q_sync_bubble && client->inMultiUser) {
1590 CFReleaseNull(q->q_musrView);
1591 q->q_musrView = SecMUSRCreateSyncBubbleUserUUID(q->q_sync_bubble);
1592 } else if (client->inMultiUser && client->isNetworkExtension) {
1593 CFReleaseNull(q->q_musrView);
1594 q->q_musrView = SecMUSRCreateBothUserAndSystemUUID(client->uid);
1595 } else if (q->q_system_keychain && client->inMultiUser) {
1596 CFReleaseNull(q->q_musrView);
1597 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1598 } else {
1599 q->q_system_keychain = false;
1600 }
1601 #endif
1602
1603 query_set_caller_access_groups(q, accessGroups);
1604
1605 /* Sanity check the query. */
1606 if (q->q_system_keychain && !client->allowSystemKeychain) {
1607 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1608 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1609 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1610 } else if (q->q_system_keychain && q->q_sync_bubble) {
1611 ok = SecError(errSecMissingEntitlement, error, CFSTR("can't do both system and syncbubble keychain"));
1612 } else if (q->q_use_item_list) {
1613 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list unsupported"));
1614 } else if (q->q_match_issuer && ((q->q_class != cert_class()) &&
1615 (q->q_class != identity_class()))) {
1616 ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported match attribute"));
1617 } else if (q->q_match_policy && ((q->q_class != cert_class()) &&
1618 (q->q_class != identity_class()))) {
1619 ok = SecError(errSecUnsupportedOperation, error, CFSTR("unsupported kSecMatchPolicy attribute"));
1620 } else if (q->q_return_type != 0 && result == NULL) {
1621 ok = SecError(errSecReturnMissingPointer, error, CFSTR("missing pointer"));
1622 } else if (!q->q_error) {
1623 ok = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
1624 return s3dl_copy_matching(dbt, q, result, accessGroups, error);
1625 });
1626 }
1627
1628 CFReleaseSafe(accessGroups);
1629 if (!query_destroy(q, error))
1630 ok = false;
1631 }
1632 CFReleaseNull(mutableAccessGroups);
1633
1634 SecSignpostStop(SecSignpostSecItemCopyMatching);
1635
1636 return ok;
1637 }
1638
1639 bool
1640 _SecItemCopyMatching(CFDictionaryRef query, SecurityClient *client, CFTypeRef *result, CFErrorRef *error) {
1641 return SecItemServerCopyMatching(query, result, client, error);
1642 }
1643
1644 #if TARGET_OS_IPHONE
1645 static bool
1646 SecItemSynchronizable(CFDictionaryRef query)
1647 {
1648 bool result = false;
1649 CFTypeRef value = CFDictionaryGetValue(query, kSecAttrSynchronizable);
1650 if (isBoolean(value))
1651 return CFBooleanGetValue(value);
1652 else if (isNumber(value)) {
1653 SInt32 number = 0;
1654 (void)CFNumberGetValue(value, kCFNumberSInt32Type, &number);
1655 result = !!number;
1656 }
1657
1658 return result;
1659 }
1660 #endif
1661
1662 static CFArrayRef
1663 SecurityClientCopyWritableAccessGroups(SecurityClient *client) {
1664 if (client == NULL || client->accessGroups == NULL) {
1665 return NULL;
1666 }
1667 CFIndex count = CFArrayGetCount(client->accessGroups);
1668 if (CFArrayContainsValue(client->accessGroups, CFRangeMake(0, count), kSecAttrAccessGroupToken)) {
1669 CFMutableArrayRef writableGroups = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, client->accessGroups);
1670 CFArrayRemoveAllValue(writableGroups, kSecAttrAccessGroupToken);
1671 return writableGroups;
1672 } else {
1673 return CFRetainSafe(client->accessGroups);
1674 }
1675 }
1676
1677
1678 /* AUDIT[securityd](done):
1679 attributes (ok) is a caller provided dictionary, only its cf type has
1680 been checked.
1681 */
1682 bool
1683 _SecItemAdd(CFDictionaryRef attributes, SecurityClient *client, CFTypeRef *result, CFErrorRef *error)
1684 {
1685 CFArrayRef accessGroups = SecurityClientCopyWritableAccessGroups(client);
1686
1687 bool ok = true;
1688 CFIndex ag_count;
1689 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1690 CFReleaseNull(accessGroups);
1691 return SecEntitlementError(errSecMissingEntitlement, error);
1692 }
1693
1694 SecSignpostStart(SecSignpostSecItemAdd);
1695
1696 Query *q = query_create_with_limit(attributes, client->musr, 0, error);
1697 if (q) {
1698 /* Access group sanity checking. */
1699 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributes,
1700 kSecAttrAccessGroup);
1701
1702 /* Having the special accessGroup "*" allows access to all accessGroups. */
1703 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*")))
1704 CFReleaseNull(accessGroups);
1705
1706 if (agrp) {
1707 /* The user specified an explicit access group, validate it. */
1708 if (!accessGroupsAllows(accessGroups, agrp, client))
1709 ok = SecError(errSecMissingEntitlement, error,
1710 CFSTR("explicit accessGroup %@ not in client access %@"), agrp, accessGroups);
1711 } else {
1712 agrp = (CFStringRef)CFArrayGetValueAtIndex(client->accessGroups, 0);
1713
1714 /* We are using an implicit access group, add it as if the user
1715 specified it as an attribute. */
1716 query_add_attribute(kSecAttrAccessGroup, agrp, q);
1717 }
1718
1719 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1720 CFDictionaryRef dict = CFDictionaryCreateForCFTypes(NULL, CFSTR("operation"), CFSTR("add"), CFSTR("AccessGroup"), agrp, NULL);
1721 if (dict) {
1722 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict);
1723 CFRelease(dict);
1724 }
1725 }
1726 #if TARGET_OS_IPHONE
1727 if (q->q_system_keychain && client->inMultiUser) {
1728 CFReleaseNull(q->q_musrView);
1729 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1730 } else {
1731 q->q_system_keychain = false;
1732 }
1733 query_add_attribute_with_desc(&v8musr, q->q_musrView, q);
1734 #endif
1735
1736 if (ok) {
1737 query_ensure_access_control(q, agrp);
1738
1739 #if OCTAGON
1740 void (^add_sync_callback)(bool, CFErrorRef) = CFDictionaryGetValue(attributes, CFSTR("f_ckkscallback"));
1741 if(add_sync_callback) {
1742 // The existence of this callback indicates that we need a predictable UUID for this item.
1743 q->q_uuid_from_primary_key = true;
1744 q->q_add_sync_callback = add_sync_callback;
1745 }
1746 #endif
1747
1748 if (q->q_system_keychain && !client->allowSystemKeychain) {
1749 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1750 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1751 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1752 #if TARGET_OS_IPHONE
1753 } else if (q->q_system_keychain && SecItemSynchronizable(attributes) && !client->inMultiUser) {
1754 ok = SecError(errSecInvalidKey, error, CFSTR("Can't store system keychain and synchronizable"));
1755 #endif
1756 } else if (q->q_row_id || q->q_token_object_id) {
1757 ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); // TODO: better error string
1758 } else if (!q->q_error) {
1759 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt){
1760 return kc_transaction(dbt, error, ^{
1761 query_pre_add(q, true);
1762 return s3dl_query_add(dbt, q, result, error);
1763 });
1764 });
1765 }
1766 }
1767 ok = query_notify_and_destroy(q, ok, error);
1768 } else {
1769 ok = false;
1770 }
1771
1772 SecSignpostStop(SecSignpostSecItemAdd);
1773
1774 CFReleaseNull(accessGroups);
1775 return ok;
1776 }
1777
1778 /* AUDIT[securityd](done):
1779 query (ok) and attributesToUpdate (ok) are a caller provided dictionaries,
1780 only their cf types have been checked.
1781 */
1782 bool
1783 _SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate,
1784 SecurityClient *client, CFErrorRef *error)
1785 {
1786 CFArrayRef accessGroups = SecurityClientCopyWritableAccessGroups(client);
1787
1788 CFIndex ag_count;
1789 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1790 CFReleaseNull(accessGroups);
1791 return SecEntitlementError(errSecMissingEntitlement, error);
1792 }
1793
1794 SecSignpostStart(SecSignpostSecItemUpdate);
1795
1796 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1797 CFTypeRef agrp = CFArrayGetValueAtIndex(accessGroups, 0);
1798 CFDictionaryRef dict = CFDictionaryCreateForCFTypes(NULL, CFSTR("operation"), CFSTR("update"), CFSTR("AccessGroup"), agrp, NULL);
1799 if (dict) {
1800 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict);
1801 CFRelease(dict);
1802 }
1803 }
1804
1805 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1806 /* Having the special accessGroup "*" allows access to all accessGroups. */
1807 CFReleaseNull(accessGroups);
1808 }
1809
1810 bool ok = true;
1811 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1812 if (!q) {
1813 ok = false;
1814 }
1815 if (ok) {
1816 #if TARGET_OS_IPHONE
1817 if (q->q_system_keychain && client->inMultiUser) {
1818 CFReleaseNull(q->q_musrView);
1819 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1820 } else {
1821 q->q_system_keychain = false;
1822 }
1823 #endif
1824
1825 /* Sanity check the query. */
1826 query_set_caller_access_groups(q, accessGroups);
1827 if (q->q_system_keychain && !client->allowSystemKeychain) {
1828 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1829 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1830 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1831 #if TARGET_OS_IPHONE
1832 } else if (q->q_system_keychain && SecItemSynchronizable(attributesToUpdate) && !client->inMultiUser) {
1833 ok = SecError(errSecInvalidKey, error, CFSTR("Can't update an system keychain item with synchronizable"));
1834 #endif
1835 } else if (q->q_use_item_list) {
1836 ok = SecError(errSecUseItemListUnsupported, error, CFSTR("use item list not supported"));
1837 } else if (q->q_return_type & kSecReturnDataMask) {
1838 /* Update doesn't return anything so don't ask for it. */
1839 ok = SecError(errSecReturnDataUnsupported, error, CFSTR("return data not supported by update"));
1840 } else if (q->q_return_type & kSecReturnAttributesMask) {
1841 ok = SecError(errSecReturnAttributesUnsupported, error, CFSTR("return attributes not supported by update"));
1842 } else if (q->q_return_type & kSecReturnRefMask) {
1843 ok = SecError(errSecReturnRefUnsupported, error, CFSTR("return ref not supported by update"));
1844 } else if (q->q_return_type & kSecReturnPersistentRefMask) {
1845 ok = SecError(errSecReturnPersistentRefUnsupported, error, CFSTR("return persistent ref not supported by update"));
1846 } else {
1847 /* Access group sanity checking. */
1848 CFStringRef agrp = (CFStringRef)CFDictionaryGetValue(attributesToUpdate,
1849 kSecAttrAccessGroup);
1850 if (agrp) {
1851 /* The user is attempting to modify the access group column,
1852 validate it to make sure the new value is allowable. */
1853 if (!accessGroupsAllows(accessGroups, agrp, client)) {
1854 ok = SecError(errSecNoAccessForItem, error, CFSTR("accessGroup %@ not in %@"), agrp, accessGroups);
1855 }
1856 }
1857 }
1858 }
1859 if (ok) {
1860 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1861 return kc_transaction(dbt, error, ^{
1862 return s3dl_query_update(dbt, q, attributesToUpdate, accessGroups, error);
1863 });
1864 });
1865 }
1866 if (q) {
1867 ok = query_notify_and_destroy(q, ok, error);
1868 }
1869
1870 SecSignpostStop(SecSignpostSecItemUpdate);
1871
1872 CFReleaseNull(accessGroups);
1873 return ok;
1874 }
1875
1876
1877 /* AUDIT[securityd](done):
1878 query (ok) is a caller provided dictionary, only its cf type has been checked.
1879 */
1880 bool
1881 _SecItemDelete(CFDictionaryRef query, SecurityClient *client, CFErrorRef *error)
1882 {
1883 CFArrayRef accessGroups = SecurityClientCopyWritableAccessGroups(client);
1884
1885 CFIndex ag_count;
1886 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
1887 CFReleaseNull(accessGroups);
1888 return SecEntitlementError(errSecMissingEntitlement, error);
1889 }
1890
1891 SecSignpostStart(SecSignpostSecItemDelete);
1892
1893 if (SecPLShouldLogRegisteredEvent(CFSTR("SecItem"))) {
1894 CFTypeRef agrp = CFArrayGetValueAtIndex(accessGroups, 0);
1895 CFDictionaryRef dict = CFDictionaryCreateForCFTypes(NULL, CFSTR("operation"), CFSTR("delete"), CFSTR("AccessGroup"), agrp, NULL);
1896 if (dict) {
1897 SecPLLogRegisteredEvent(CFSTR("SecItem"), dict);
1898 CFRelease(dict);
1899 }
1900 }
1901
1902 if (CFArrayContainsValue(accessGroups, CFRangeMake(0, ag_count), CFSTR("*"))) {
1903 /* Having the special accessGroup "*" allows access to all accessGroups. */
1904 CFReleaseNull(accessGroups);
1905 }
1906
1907 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1908 bool ok;
1909 if (q) {
1910 #if TARGET_OS_IPHONE
1911 if (q->q_system_keychain && client->inMultiUser) {
1912 CFReleaseNull(q->q_musrView);
1913 q->q_musrView = SecMUSRCopySystemKeychainUUID();
1914 } else {
1915 q->q_system_keychain = false;
1916 }
1917 #endif
1918
1919 query_set_caller_access_groups(q, accessGroups);
1920 /* Sanity check the query. */
1921 if (q->q_system_keychain && !client->allowSystemKeychain) {
1922 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1923 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1924 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1925 } else if (q->q_limit != kSecMatchUnlimited) {
1926 ok = SecError(errSecMatchLimitUnsupported, error, CFSTR("match limit not supported by delete"));
1927 } else if (query_match_count(q) != 0) {
1928 ok = SecError(errSecItemMatchUnsupported, error, CFSTR("match not supported by delete"));
1929 } else if (q->q_ref) {
1930 ok = SecError(errSecValueRefUnsupported, error, CFSTR("value ref not supported by delete"));
1931 } else if (q->q_row_id && query_attr_count(q)) {
1932 ok = SecError(errSecItemIllegalQuery, error, CFSTR("rowid and other attributes are mutually exclusive"));
1933 } else if (q->q_token_object_id && query_attr_count(q) != 1) {
1934 ok = SecError(errSecItemIllegalQuery, error, CFSTR("token persistent ref and other attributes are mutually exclusive"));
1935 } else {
1936 ok = kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
1937 return kc_transaction(dbt, error, ^{
1938 return s3dl_query_delete(dbt, q, accessGroups, error);
1939 });
1940 });
1941 }
1942 ok = query_notify_and_destroy(q, ok, error);
1943 } else {
1944 ok = false;
1945 }
1946
1947 SecSignpostStop(SecSignpostSecItemDelete);
1948
1949 CFReleaseNull(accessGroups);
1950 return ok;
1951 }
1952
1953 static bool SecItemDeleteTokenItems(SecDbConnectionRef dbt, CFTypeRef classToDelete, CFTypeRef tokenID, CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error) {
1954 CFTypeRef keys[] = { kSecClass, kSecAttrTokenID };
1955 CFTypeRef values[] = { classToDelete, tokenID };
1956
1957 CFDictionaryRef query = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1958 Query *q = query_create_with_limit(query, client->musr, kSecMatchUnlimited, error);
1959 CFRelease(query);
1960 bool ok;
1961 if (q) {
1962 query_set_caller_access_groups(q, accessGroups);
1963 ok = s3dl_query_delete(dbt, q, accessGroups, error);
1964 ok = query_notify_and_destroy(q, ok, error);
1965 } else {
1966 ok = false;
1967 }
1968
1969 return ok;
1970 }
1971
1972 static bool SecItemAddTokenItem(SecDbConnectionRef dbt, CFDictionaryRef attributes, CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error) {
1973 bool ok = true;
1974 Query *q = query_create_with_limit(attributes, client->musr, 0, error);
1975 if (q) {
1976 CFStringRef agrp = kSecAttrAccessGroupToken;
1977 query_add_attribute(kSecAttrAccessGroup, agrp, q);
1978
1979 if (ok) {
1980 query_ensure_access_control(q, agrp);
1981 if (q->q_system_keychain && !client->allowSystemKeychain) {
1982 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for system keychain"));
1983 } else if (q->q_sync_bubble && !client->allowSyncBubbleKeychain) {
1984 ok = SecError(errSecMissingEntitlement, error, CFSTR("client doesn't have entitlement for syncbubble keychain"));
1985 } else if (q->q_row_id || q->q_token_object_id) {
1986 ok = SecError(errSecValuePersistentRefUnsupported, error, CFSTR("q_row_id")); // TODO: better error string
1987 } else if (!q->q_error) {
1988 query_pre_add(q, true);
1989 ok = s3dl_query_add(dbt, q, NULL, error);
1990 }
1991 }
1992 ok = query_notify_and_destroy(q, ok, error);
1993 } else {
1994 return false;
1995 }
1996 return ok;
1997 }
1998
1999 bool _SecItemUpdateTokenItems(CFStringRef tokenID, CFArrayRef items, SecurityClient *client, CFErrorRef *error) {
2000 bool ok = true;
2001 CFArrayRef accessGroups = client->accessGroups;
2002 CFIndex ag_count;
2003 if (!accessGroups || 0 == (ag_count = CFArrayGetCount(accessGroups))) {
2004 return SecEntitlementError(errSecMissingEntitlement, error);
2005 }
2006
2007 ok = kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
2008 return kc_transaction(dbt, error, ^bool {
2009 if (items) {
2010 const CFTypeRef classToDelete[] = { kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey };
2011 for (size_t i = 0; i < sizeof(classToDelete) / sizeof(classToDelete[0]); ++i) {
2012 SecItemDeleteTokenItems(dbt, classToDelete[i], tokenID, accessGroups, client, NULL);
2013 }
2014
2015 for (CFIndex i = 0; i < CFArrayGetCount(items); ++i) {
2016 if (!SecItemAddTokenItem(dbt, CFArrayGetValueAtIndex(items, i), accessGroups, client, error))
2017 return false;
2018 }
2019 return true;
2020 }
2021 else {
2022 const CFTypeRef classToDelete[] = { kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey };
2023 bool deleted = true;
2024 for (size_t i = 0; i < sizeof(classToDelete) / sizeof(classToDelete[0]); ++i) {
2025 if (!SecItemDeleteTokenItems(dbt, classToDelete[i], tokenID, accessGroups, client, error) && error && CFErrorGetCode(*error) != errSecItemNotFound) {
2026 deleted = false;
2027 break;
2028 }
2029 else if (error && *error) {
2030 CFReleaseNull(*error);
2031 }
2032 }
2033 return deleted;
2034 }
2035 });
2036 });
2037
2038 return ok;
2039 }
2040
2041 /* AUDIT[securityd](done):
2042 No caller provided inputs.
2043 */
2044 static bool
2045 SecItemServerDeleteAll(CFErrorRef *error) {
2046 secerror("SecItemServerDeleteAll");
2047 return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
2048 return (kc_transaction(dbt, error, ^bool {
2049 return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
2050 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
2051 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
2052 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
2053 }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
2054 });
2055 }
2056
2057 bool
2058 _SecItemDeleteAll(CFErrorRef *error) {
2059 return SecItemServerDeleteAll(error);
2060 }
2061
2062 bool
2063 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error)
2064 {
2065 __block bool ok = true;
2066 static dispatch_once_t onceToken;
2067 static CFSetRef illegalAccessGroups = NULL;
2068
2069 dispatch_once(&onceToken, ^{
2070 const CFStringRef values[] = {
2071 CFSTR("*"),
2072 CFSTR("apple"),
2073 CFSTR("com.apple.security.sos"),
2074 CFSTR("lockdown-identities"),
2075 };
2076 illegalAccessGroups = CFSetCreate(NULL, (const void **)values, sizeof(values)/sizeof(values[0]), &kCFTypeSetCallBacks);
2077 });
2078
2079 static CFTypeRef qclasses[] = {
2080 NULL,
2081 NULL,
2082 NULL,
2083 NULL
2084 };
2085 // strange construction needed for schema indirection
2086 static dispatch_once_t qclassesOnceToken;
2087 dispatch_once(&qclassesOnceToken, ^{
2088 qclasses[0] = inet_class();
2089 qclasses[1] = genp_class();
2090 qclasses[2] = keys_class();
2091 qclasses[3] = cert_class();
2092 });
2093
2094 require_action_quiet(isArray(accessGroups), fail,
2095 ok = false;
2096 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("accessGroups not CFArray, got %@"), accessGroups));
2097
2098 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2099
2100 require_action(CFArrayGetCount(accessGroups) != 0, fail,
2101 ok = false;
2102 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("accessGroups e empty")));
2103
2104
2105 // Pre-check accessGroups for prohibited values
2106 CFArrayForEach(accessGroups, ^(const void *value) {
2107 CFStringRef agrp = (CFStringRef)value;
2108
2109 if (!isString(agrp)) {
2110 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL,
2111 CFSTR("access not a string: %@"), agrp);
2112 ok &= false;
2113 } else if (CFSetContainsValue(illegalAccessGroups, agrp)) {
2114 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL,
2115 CFSTR("illegal access group: %@"), accessGroups);
2116 ok &= false;
2117 }
2118 });
2119 require(ok,fail);
2120
2121 ok = kc_with_dbt(true, error, ^bool(SecDbConnectionRef dbt) {
2122 return kc_transaction(dbt, error, ^bool {
2123 CFErrorRef localError = NULL;
2124 bool ok1 = true;
2125 size_t n;
2126
2127 for (n = 0; n < sizeof(qclasses)/sizeof(qclasses[0]) && ok1; n++) {
2128 Query *q;
2129
2130 q = query_create(qclasses[n], client->musr, NULL, error);
2131 require(q, fail2);
2132
2133 (void)s3dl_query_delete(dbt, q, accessGroups, &localError);
2134 fail2:
2135 query_destroy(q, error);
2136 CFReleaseNull(localError);
2137 }
2138 return ok1;
2139 }) && SecDbExec(dbt, CFSTR("VACUUM"), error);
2140 });
2141
2142 fail:
2143 return ok;
2144 }
2145
2146
2147 // MARK: -
2148 // MARK: Shared web credentials
2149
2150 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2151
2152 /* constants */
2153 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2154
2155 SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
2156 SEC_CONST_DECL (kSecSafariDefaultComment, "default");
2157 SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
2158 SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
2159 SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
2160
2161 #if !TARGET_IPHONE_SIMULATOR
2162 static SWCFlags
2163 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
2164 {
2165 __block SWCFlags flags = kSWCFlags_None;
2166 OSStatus status;
2167
2168 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2169 if (semaphore == NULL)
2170 return 0;
2171
2172 status = SWCCheckService(kSecSharedWebCredentialsService, appID, fqdn, ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails)
2173 {
2174 if (inStatus == 0) {
2175 flags = inFlags;
2176 } else {
2177 secerror("SWCCheckService failed with %d", (int)inStatus);
2178 }
2179 dispatch_semaphore_signal(semaphore);
2180 });
2181
2182 if (status == 0) {
2183 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
2184 } else {
2185 secerror("SWCCheckService: failed to queue");
2186 }
2187 dispatch_release(semaphore);
2188
2189 if (error) {
2190 if (!(flags & kSWCFlag_SiteApproved)) {
2191 if (flags & kSWCFlag_Pending) {
2192 SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
2193 } else {
2194 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
2195 }
2196 } else if (flags & kSWCFlag_UserDenied) {
2197 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
2198 }
2199 }
2200 return flags;
2201 }
2202
2203 static bool
2204 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
2205 {
2206 bool result = false;
2207 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
2208 if (!count || !domain || !service) {
2209 return result;
2210 }
2211 for (idx=0; idx < count; idx++) {
2212 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
2213 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
2214 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
2215 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
2216 CFRange range = { prefix_len, substr_len };
2217 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
2218 if (substr && CFEqual(substr, domain)) {
2219 result = true;
2220 }
2221 CFReleaseSafe(substr);
2222 if (result) {
2223 break;
2224 }
2225 }
2226 }
2227 return result;
2228 }
2229 #endif /* !TARGET_OS_SIMULATOR */
2230
2231 static bool
2232 _SecAddNegativeWebCredential(SecurityClient *client, CFStringRef fqdn, CFStringRef appID, bool forSafari)
2233 {
2234 #if !TARGET_IPHONE_SIMULATOR
2235 bool result = false;
2236 if (!fqdn) { return result; }
2237
2238 // update our database
2239 CFRetainSafe(appID);
2240 CFRetainSafe(fqdn);
2241 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService, appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
2242 ^void(OSStatus inStatus, SWCFlags inNewFlags){
2243 CFReleaseSafe(appID);
2244 CFReleaseSafe(fqdn);
2245 }))
2246 {
2247 result = true;
2248 }
2249 else // didn't queue the block
2250 {
2251 CFReleaseSafe(appID);
2252 CFReleaseSafe(fqdn);
2253 }
2254
2255 if (!forSafari) { return result; }
2256
2257 // below this point: create a negative Safari web credential item
2258
2259 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2260 if (!attrs) { return result; }
2261
2262 CFErrorRef error = NULL;
2263 CFStringRef accessGroup = CFSTR("*");
2264 SecurityClient swcclient = {
2265 .task = NULL,
2266 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
2267 .allowSystemKeychain = false,
2268 .allowSyncBubbleKeychain = false,
2269 .isNetworkExtension = false,
2270 .musr = client->musr,
2271 };
2272
2273 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
2274 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
2275 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2276 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2277 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
2278 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
2279
2280 (void)_SecItemDelete(attrs, &swcclient, &error);
2281 CFReleaseNull(error);
2282
2283 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
2284 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
2285
2286 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
2287 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
2288 if (label) {
2289 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
2290 CFReleaseSafe(label);
2291 }
2292
2293 UInt8 space = ' ';
2294 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
2295 if (data) {
2296 CFDictionarySetValue(attrs, kSecValueData, data);
2297 CFReleaseSafe(data);
2298 }
2299
2300 CFTypeRef addResult = NULL;
2301 result = _SecItemAdd(attrs, &swcclient, &addResult, &error);
2302
2303 CFReleaseSafe(addResult);
2304 CFReleaseSafe(error);
2305 CFReleaseSafe(attrs);
2306 CFReleaseSafe(swcclient.accessGroups);
2307
2308 return result;
2309 #else
2310 return true;
2311 #endif
2312 }
2313
2314 /* Specialized version of SecItemAdd for shared web credentials */
2315 bool
2316 _SecAddSharedWebCredential(CFDictionaryRef attributes,
2317 SecurityClient *client,
2318 const audit_token_t *clientAuditToken,
2319 CFStringRef appID,
2320 CFArrayRef domains,
2321 CFTypeRef *result,
2322 CFErrorRef *error)
2323 {
2324
2325 SecurityClient swcclient = {};
2326
2327 CFStringRef fqdn = CFRetainSafe(CFDictionaryGetValue(attributes, kSecAttrServer));
2328 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
2329 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2330 CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
2331 #else
2332 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
2333 #endif
2334 CFStringRef accessGroup = CFSTR("*");
2335 CFMutableDictionaryRef query = NULL, attrs = NULL;
2336 SInt32 port = -1;
2337 bool ok = false;
2338
2339 // check autofill enabled status
2340 if (!swca_autofill_enabled(clientAuditToken)) {
2341 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
2342 goto cleanup;
2343 }
2344
2345 // parse fqdn with CFURL here, since it could be specified as domain:port
2346 if (fqdn) {
2347 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
2348 if (urlStr) {
2349 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
2350 if (url) {
2351 CFStringRef hostname = CFURLCopyHostName(url);
2352 if (hostname) {
2353 CFReleaseSafe(fqdn);
2354 fqdn = hostname;
2355 port = CFURLGetPortNumber(url);
2356 }
2357 CFReleaseSafe(url);
2358 }
2359 CFReleaseSafe(urlStr);
2360 }
2361 }
2362
2363 if (!account) {
2364 SecError(errSecParam, error, CFSTR("No account provided"));
2365 goto cleanup;
2366 }
2367 if (!fqdn) {
2368 SecError(errSecParam, error, CFSTR("No domain provided"));
2369 goto cleanup;
2370 }
2371
2372 #if TARGET_IPHONE_SIMULATOR
2373 secerror("app/site association entitlements not checked in Simulator");
2374 #else
2375 OSStatus status = errSecMissingEntitlement;
2376 // validate that fqdn is part of caller's shared credential domains entitlement
2377 if (!appID) {
2378 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
2379 goto cleanup;
2380 }
2381 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
2382 status = errSecSuccess;
2383 }
2384 if (errSecSuccess != status) {
2385 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
2386 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
2387 if (!msg) {
2388 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
2389 }
2390 SecError(status, error, CFSTR("%@"), msg);
2391 CFReleaseSafe(msg);
2392 goto cleanup;
2393 }
2394 #endif
2395
2396 #if TARGET_IPHONE_SIMULATOR
2397 secerror("Ignoring app/site approval state in the Simulator.");
2398 #else
2399 // get approval status for this app/domain pair
2400 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
2401 if (!(flags & kSWCFlag_SiteApproved)) {
2402 goto cleanup;
2403 }
2404 #endif
2405
2406 // give ourselves access to see matching items for kSecSafariAccessGroup
2407 swcclient.task = NULL;
2408 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
2409 swcclient.allowSystemKeychain = false;
2410 swcclient.musr = client->musr;
2411 swcclient.allowSystemKeychain = false;
2412 swcclient.allowSyncBubbleKeychain = false;
2413 swcclient.isNetworkExtension = false;
2414
2415
2416 // create lookup query
2417 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2418 if (!query) {
2419 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
2420 goto cleanup;
2421 }
2422 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
2423 CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
2424 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2425 CFDictionaryAddValue(query, kSecAttrServer, fqdn);
2426 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
2427
2428 // check for presence of Safari's negative entry ('passwords not saved')
2429 CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
2430 ok = _SecItemCopyMatching(query, &swcclient, result, error);
2431 if(result) CFReleaseNull(*result);
2432 if (error) CFReleaseNull(*error);
2433 if (ok) {
2434 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
2435 goto cleanup;
2436 }
2437
2438 // now use the provided account (and optional port number, if one was present)
2439 CFDictionarySetValue(query, kSecAttrAccount, account);
2440 if (port < -1 || port > 0) {
2441 SInt16 portValueShort = (port & 0xFFFF);
2442 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
2443 CFDictionaryAddValue(query, kSecAttrPort, portNumber);
2444 CFReleaseSafe(portNumber);
2445 }
2446
2447 // look up existing password
2448 CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
2449 bool matched = _SecItemCopyMatching(query, &swcclient, result, error);
2450 CFDictionaryRemoveValue(query, kSecReturnData);
2451 if (matched) {
2452 // found it, so this becomes either an "update password" or "delete password" operation
2453 bool update = (password != NULL);
2454 if (update) {
2455 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2456 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
2457 CFDictionaryAddValue(attrs, kSecValueData, credential);
2458 bool samePassword = result && *result && CFEqual(*result, credential);
2459 CFReleaseSafe(credential);
2460 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
2461
2462 ok = samePassword || swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
2463 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2464 if (ok) {
2465 ok = _SecItemUpdate(query, attrs, &swcclient, error);
2466 }
2467 }
2468 else {
2469 // confirm the delete
2470 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2471 ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
2472 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2473 if (ok) {
2474 ok = _SecItemDelete(query, &swcclient, error);
2475 }
2476 }
2477
2478 if(result) CFReleaseNull(*result);
2479 if(error) CFReleaseNull(*error);
2480
2481 goto cleanup;
2482 }
2483 if (result) CFReleaseNull(*result);
2484 if (error) CFReleaseNull(*error);
2485
2486 // password does not exist, so prepare to add it
2487 if (!password) {
2488 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2489 ok = true;
2490 goto cleanup;
2491 }
2492 else {
2493 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
2494 if (label) {
2495 CFDictionaryAddValue(query, kSecAttrLabel, label);
2496 CFReleaseSafe(label);
2497 }
2498 // NOTE: we always expect to use HTTPS for web forms.
2499 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2500
2501 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
2502 CFDictionarySetValue(query, kSecValueData, credential);
2503 CFReleaseSafe(credential);
2504 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
2505
2506 CFReleaseSafe(swcclient.accessGroups);
2507 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
2508
2509 // mark the item as created by this function
2510 const int32_t creator_value = 'swca';
2511 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
2512 if (creator) {
2513 CFDictionarySetValue(query, kSecAttrCreator, creator);
2514 CFReleaseSafe(creator);
2515 }
2516
2517 // confirm the add
2518 ok = swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error, ^void (CFStringRef fqdn) {
2519 _SecAddNegativeWebCredential(client, fqdn, appID, false);
2520 });
2521 }
2522 if (ok) {
2523 ok = _SecItemAdd(query, &swcclient, result, error);
2524 }
2525
2526 cleanup:
2527 CFReleaseSafe(attrs);
2528 CFReleaseSafe(query);
2529 CFReleaseSafe(swcclient.accessGroups);
2530 CFReleaseSafe(fqdn);
2531 return ok;
2532 }
2533
2534 /* Specialized version of SecItemCopyMatching for shared web credentials */
2535 bool
2536 _SecCopySharedWebCredential(CFDictionaryRef query,
2537 SecurityClient *client,
2538 const audit_token_t *clientAuditToken,
2539 CFStringRef appID,
2540 CFArrayRef domains,
2541 CFTypeRef *result,
2542 CFErrorRef *error)
2543 {
2544 CFMutableArrayRef credentials = NULL;
2545 CFMutableArrayRef foundItems = NULL;
2546 CFMutableArrayRef fqdns = NULL;
2547 CFStringRef fqdn = NULL;
2548 CFStringRef account = NULL;
2549 CFIndex idx, count;
2550 SInt32 port = -1;
2551 bool ok = false;
2552
2553 require_quiet(result, cleanup);
2554 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2555 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2556 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2557
2558 // give ourselves access to see matching items for kSecSafariAccessGroup
2559 CFStringRef accessGroup = CFSTR("*");
2560 SecurityClient swcclient = {
2561 .task = NULL,
2562 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
2563 .allowSystemKeychain = false,
2564 .allowSyncBubbleKeychain = false,
2565 .isNetworkExtension = false,
2566 .musr = client->musr,
2567 };
2568
2569 // On input, the query dictionary contains optional fqdn and account entries.
2570 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
2571 account = CFDictionaryGetValue(query, kSecAttrAccount);
2572
2573 // Check autofill enabled status
2574 if (!swca_autofill_enabled(clientAuditToken)) {
2575 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
2576 goto cleanup;
2577 }
2578
2579 // Check fqdn; if NULL, add domains from caller's entitlement.
2580 if (fqdn) {
2581 CFArrayAppendValue(fqdns, fqdn);
2582 }
2583 else if (domains) {
2584 CFIndex idx, count = CFArrayGetCount(domains);
2585 for (idx=0; idx < count; idx++) {
2586 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
2587 // Parse the entry for our service label prefix
2588 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
2589 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
2590 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
2591 CFRange range = { prefix_len, substr_len };
2592 fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
2593 if (fqdn) {
2594 CFArrayAppendValue(fqdns, fqdn);
2595 CFRelease(fqdn);
2596 }
2597 }
2598 }
2599 }
2600 count = CFArrayGetCount(fqdns);
2601 if (count < 1) {
2602 SecError(errSecParam, error, CFSTR("No domain provided"));
2603 goto cleanup;
2604 }
2605
2606 // Aggregate search results for each domain
2607 for (idx = 0; idx < count; idx++) {
2608 CFMutableArrayRef items = NULL;
2609 CFMutableDictionaryRef attrs = NULL;
2610 fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
2611 CFRetainSafe(fqdn);
2612 port = -1;
2613
2614 // Parse the fqdn for a possible port specifier.
2615 if (fqdn) {
2616 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
2617 if (urlStr) {
2618 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
2619 if (url) {
2620 CFStringRef hostname = CFURLCopyHostName(url);
2621 if (hostname) {
2622 CFReleaseSafe(fqdn);
2623 fqdn = hostname;
2624 port = CFURLGetPortNumber(url);
2625 }
2626 CFReleaseSafe(url);
2627 }
2628 CFReleaseSafe(urlStr);
2629 }
2630 }
2631
2632 #if TARGET_IPHONE_SIMULATOR
2633 secerror("app/site association entitlements not checked in Simulator");
2634 #else
2635 OSStatus status = errSecMissingEntitlement;
2636 if (!appID) {
2637 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
2638 CFReleaseSafe(fqdn);
2639 goto cleanup;
2640 }
2641 // validate that fqdn is part of caller's entitlement
2642 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
2643 status = errSecSuccess;
2644 }
2645 if (errSecSuccess != status) {
2646 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
2647 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
2648 if (!msg) {
2649 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
2650 }
2651 SecError(status, error, CFSTR("%@"), msg);
2652 CFReleaseSafe(msg);
2653 CFReleaseSafe(fqdn);
2654 goto cleanup;
2655 }
2656 #endif
2657
2658 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2659 if (!attrs) {
2660 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
2661 CFReleaseSafe(fqdn);
2662 goto cleanup;
2663 }
2664 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
2665 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
2666 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2667 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2668 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
2669 if (account) {
2670 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
2671 }
2672 if (port < -1 || port > 0) {
2673 SInt16 portValueShort = (port & 0xFFFF);
2674 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
2675 CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
2676 CFReleaseSafe(portNumber);
2677 }
2678 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
2679 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
2680 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
2681 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
2682
2683 ok = _SecItemCopyMatching(attrs, &swcclient, (CFTypeRef*)&items, error);
2684 if (count > 1) {
2685 // ignore interim error since we have multiple domains to search
2686 CFReleaseNull(*error);
2687 }
2688 if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
2689 #if TARGET_IPHONE_SIMULATOR
2690 secerror("Ignoring app/site approval state in the Simulator.");
2691 bool approved = true;
2692 #else
2693 // get approval status for this app/domain pair
2694 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
2695 if (count > 1) {
2696 // ignore interim error since we have multiple domains to check
2697 CFReleaseNull(*error);
2698 }
2699 bool approved = (flags & kSWCFlag_SiteApproved);
2700 #endif
2701 if (approved) {
2702 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
2703 }
2704 }
2705 CFReleaseSafe(items);
2706 CFReleaseSafe(attrs);
2707 CFReleaseSafe(fqdn);
2708 }
2709
2710 // If matching credentials are found, the credentials provided to the completionHandler
2711 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2712 // contain the following pairs (see Security/SecItem.h):
2713 // key: kSecAttrServer value: CFStringRef (the website)
2714 // key: kSecAttrAccount value: CFStringRef (the account)
2715 // key: kSecSharedPassword value: CFStringRef (the password)
2716 // Optional keys:
2717 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2718
2719 count = CFArrayGetCount(foundItems);
2720 for (idx = 0; idx < count; idx++) {
2721 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
2722 CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2723 if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
2724 CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
2725 CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
2726 CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
2727 CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
2728 CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
2729 if (srvr) {
2730 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
2731 }
2732 if (acct) {
2733 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
2734 }
2735 if (pnum) {
2736 SInt16 pval = -1;
2737 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
2738 (pval < -1 || pval > 0)) {
2739 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
2740 }
2741 }
2742 if (data) {
2743 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
2744 if (password) {
2745 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2746 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
2747 #else
2748 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
2749 #endif
2750 CFReleaseSafe(password);
2751 }
2752 }
2753 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
2754 CFArrayInsertValueAtIndex(credentials, 0, newdict);
2755 } else {
2756 CFArrayAppendValue(credentials, newdict);
2757 }
2758 }
2759 CFReleaseSafe(newdict);
2760 }
2761
2762 if (count) {
2763
2764 ok = false;
2765
2766 // create a new array of dictionaries (without the actual password) for picker UI
2767 count = CFArrayGetCount(credentials);
2768 CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2769 for (idx = 0; idx < count; idx++) {
2770 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
2771 CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
2772 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2773 CFDictionaryRemoveValue(newdict, kSecSharedPassword);
2774 #else
2775 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
2776 #endif
2777 CFArrayAppendValue(items, newdict);
2778 CFReleaseSafe(newdict);
2779 }
2780
2781 // prompt user to select one of the dictionary items
2782 CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
2783 clientAuditToken, items, error);
2784 if (selected) {
2785 // find the matching item in our credentials array
2786 CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
2787 CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
2788 CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
2789 for (idx = 0; idx < count; idx++) {
2790 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
2791 CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
2792 CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
2793 CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
2794
2795 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
2796 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
2797 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
2798
2799 // we have a match!
2800 CFReleaseSafe(selected);
2801 CFRetainSafe(dict);
2802 selected = dict;
2803 ok = true;
2804 break;
2805 }
2806 }
2807 CFReleaseSafe(items);
2808 CFArrayRemoveAllValues(credentials);
2809 if (selected && ok) {
2810 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2811 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
2812 #endif
2813 CFArrayAppendValue(credentials, selected);
2814 }
2815
2816 if (ok) {
2817 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2818 // register confirmation with database
2819 CFRetainSafe(appID);
2820 CFRetainSafe(fqdn);
2821 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService,
2822 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
2823 ^void(OSStatus inStatus, SWCFlags inNewFlags){
2824 CFReleaseSafe(appID);
2825 CFReleaseSafe(fqdn);
2826 }))
2827 {
2828 // we didn't queue the block
2829 CFReleaseSafe(appID);
2830 CFReleaseSafe(fqdn);
2831 }
2832 #endif
2833 }
2834 CFReleaseSafe(selected);
2835 }
2836 else if (NULL == *error) {
2837 // found no items, and we haven't already filled in the error
2838 SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
2839 }
2840
2841 cleanup:
2842 if (!ok) {
2843 CFArrayRemoveAllValues(credentials);
2844 CFReleaseNull(credentials);
2845 }
2846 CFReleaseSafe(foundItems);
2847 *result = credentials;
2848 CFReleaseSafe(swcclient.accessGroups);
2849 CFReleaseSafe(fqdns);
2850
2851 return ok;
2852 }
2853
2854 #endif /* TARGET_OS_IOS */
2855
2856
2857 // MARK: -
2858 // MARK: Keychain backup
2859
2860 CF_RETURNS_RETAINED CFDataRef
2861 _SecServerKeychainCreateBackup(SecurityClient *client, CFDataRef keybag, CFDataRef passcode, bool emcs, CFErrorRef *error) {
2862 __block CFDataRef backup;
2863 kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
2864 if (!dbt)
2865 return NULL;
2866
2867 if (keybag == NULL && passcode == NULL) {
2868 #if USE_KEYSTORE
2869 backup = SecServerExportBackupableKeychain(dbt, client, KEYBAG_DEVICE, backup_keybag_handle, error);
2870 #else /* !USE_KEYSTORE */
2871 (void)client;
2872 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
2873 backup = NULL;
2874 #endif /* USE_KEYSTORE */
2875 } else {
2876 backup = SecServerKeychainCreateBackup(dbt, client, keybag, passcode, emcs, error);
2877 }
2878 return (backup != NULL);
2879 });
2880
2881 return backup;
2882 }
2883
2884 bool
2885 _SecServerKeychainRestore(CFDataRef backup, SecurityClient *client, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
2886 if (backup == NULL || keybag == NULL)
2887 return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
2888
2889 __block bool ok = true;
2890 ok &= kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbconn) {
2891 return SecServerKeychainRestore(dbconn, client, backup, keybag, passcode, error);
2892 });
2893
2894 if (ok) {
2895 SecKeychainChanged();
2896 }
2897
2898 return ok;
2899 }
2900
2901 CFStringRef
2902 _SecServerBackupCopyUUID(CFDataRef data, CFErrorRef *error)
2903 {
2904 CFStringRef uuid = NULL;
2905 CFDictionaryRef backup;
2906
2907 backup = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
2908 kCFPropertyListImmutable, NULL,
2909 error);
2910 if (isDictionary(backup)) {
2911 uuid = SecServerBackupGetKeybagUUID(backup, error);
2912 if (uuid)
2913 CFRetain(uuid);
2914 }
2915 CFReleaseNull(backup);
2916
2917 return uuid;
2918 }
2919
2920
2921
2922 // MARK: -
2923 // MARK: SecItemDataSource
2924
2925 // Make sure to call this before any writes to the keychain, so that we fire
2926 // up the engines to monitor manifest changes.
2927 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
2928 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL));
2929 }
2930
2931 /* AUDIT[securityd]:
2932 args_in (ok) is a caller provided, CFDictionaryRef.
2933 */
2934
2935 CF_RETURNS_RETAINED CFArrayRef
2936 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
2937 // This never fails, trust us!
2938 return SOSCCHandleUpdateMessage(updates);
2939 }
2940
2941 //
2942 // Truthiness in the cloud backup/restore support.
2943 //
2944
2945 static CFDictionaryRef
2946 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
2947 CFDictionaryRef backup, CFErrorRef *error)
2948 {
2949 SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
2950 __block CFMutableDictionaryRef backup_new = NULL;
2951 keybag_handle_t bag_handle;
2952 if (!ks_open_keybag(keybag, password, &bag_handle, error))
2953 return backup_new;
2954
2955 // We need to have a datasource singleton for protection domain
2956 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2957 // instance around which we create in the datasource constructor as well.
2958 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
2959 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
2960 if (ds) {
2961 backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2962 mold = SOSCreateManifestWithBackup(backup, error);
2963 SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
2964 mnow = SOSEngineCopyManifest(engine, NULL);
2965 if (!mnow) {
2966 mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0ViewSet(), error);
2967 }
2968 if (!mnow) {
2969 CFReleaseNull(backup_new);
2970 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
2971 } else {
2972 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
2973 }
2974
2975 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2976 SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
2977 CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
2978 CFDictionaryRemoveValue(backup_new, deleted_item_key);
2979 CFRelease(deleted_item_key);
2980 });
2981
2982 CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2983 SOSDataSourceForEachObject(ds, NULL, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
2984 CFErrorRef localError = NULL;
2985 CFDataRef digest_data = NULL;
2986 CFTypeRef value = NULL;
2987 if (!object) {
2988 // Key in our manifest can't be found in db, remove it from our manifest
2989 SOSChangesAppendDelete(changes, digest);
2990 } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
2991 || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
2992 if (SecErrorGetOSStatus(localError) == errSecDecode) {
2993 // Ignore decode errors, pretend the objects aren't there
2994 CFRelease(localError);
2995 // Object undecodable, remove it from our manifest
2996 SOSChangesAppendDelete(changes, digest);
2997 } else {
2998 // Stop iterating and propagate out all other errors.
2999 *stop = true;
3000 *error = localError;
3001 CFReleaseNull(backup_new);
3002 }
3003 } else {
3004 // TODO: Should we skip tombstones here?
3005 CFStringRef key = CFDataCopyHexString(digest_data);
3006 CFDictionarySetValue(backup_new, key, value);
3007 CFReleaseSafe(key);
3008 }
3009 CFReleaseSafe(digest_data);
3010 CFReleaseSafe(value);
3011 }) || CFReleaseNull(backup_new);
3012
3013 if (CFArrayGetCount(changes)) {
3014 if (!SOSEngineUpdateChanges(engine, kSOSDataSourceSOSTransaction, changes, error)) {
3015 CFReleaseNull(backup_new);
3016 }
3017 }
3018 CFReleaseSafe(changes);
3019
3020 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
3021 }
3022
3023 CFReleaseSafe(mold);
3024 CFReleaseSafe(mnow);
3025 CFReleaseSafe(madd);
3026 CFReleaseSafe(mdelete);
3027 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
3028
3029 return backup_new;
3030 }
3031
3032 static bool
3033 _SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
3034 __block bool ok = true;
3035 keybag_handle_t bag_handle;
3036 if (!ks_open_keybag(keybag, password, &bag_handle, error))
3037 return false;
3038
3039 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
3040 if (mbackup) {
3041 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
3042 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
3043 ok &= ds && SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
3044 SOSManifestRef mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0BackupViewSet(), error);
3045 SOSManifestRef mdelete = NULL, madd = NULL;
3046 SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
3047
3048 // Don't delete everything in datasource not in backup.
3049
3050 // Add items from the backup
3051 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
3052 CFDictionaryRef item = NULL;
3053 CFStringRef sha1 = CFDataCopyHexString(e);
3054 if (sha1) {
3055 item = CFDictionaryGetValue(backup_in, sha1);
3056 CFRelease(sha1);
3057 }
3058 if (item) {
3059 CFErrorRef localError = NULL;
3060
3061 if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
3062 OSStatus status = SecErrorGetOSStatus(localError);
3063 if (status == errSecDuplicateItem) {
3064 // Log and ignore duplicate item errors during restore
3065 secnotice("titc", "restore %@ not replacing existing item", item);
3066 } else if (status == errSecDecode) {
3067 // Log and ignore corrupted item errors during restore
3068 secnotice("titc", "restore %@ skipping corrupted item %@", item, localError);
3069 } else {
3070 if (status == errSecInteractionNotAllowed)
3071 *stop = true;
3072 // Propagate the first other error upwards (causing the restore to fail).
3073 secerror("restore %@ failed %@", item, localError);
3074 ok = false;
3075 if (error && !*error) {
3076 *error = localError;
3077 localError = NULL;
3078 }
3079 }
3080 CFReleaseSafe(localError);
3081 }
3082 }
3083 });
3084 ok &= SOSDataSourceRelease(ds, error);
3085 CFReleaseNull(mdelete);
3086 CFReleaseNull(madd);
3087 CFReleaseNull(mnow);
3088 });
3089 CFRelease(mbackup);
3090 }
3091
3092 ok &= ks_close_keybag(bag_handle, error);
3093
3094 return ok;
3095 }
3096
3097
3098 CF_RETURNS_RETAINED CFDictionaryRef
3099 _SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
3100 require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
3101 require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
3102 require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
3103
3104 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
3105
3106 errOut:
3107 return NULL;
3108 }
3109
3110 bool
3111 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
3112 bool ok;
3113 require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
3114 require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
3115 if (password) {
3116
3117 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
3118 }
3119
3120 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
3121
3122 errOut:
3123 return ok;
3124 }
3125
3126 bool _SecServerRollKeysGlue(bool force, CFErrorRef *error) {
3127 return _SecServerRollKeys(force, NULL, error);
3128 }
3129
3130
3131 bool _SecServerRollKeys(bool force, SecurityClient *client, CFErrorRef *error) {
3132 #if USE_KEYSTORE
3133 uint32_t keystore_generation_status = 0;
3134 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
3135 return false;
3136 uint32_t current_generation = keystore_generation_status & generation_current;
3137
3138 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3139 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
3140
3141 if (force && !up_to_date) {
3142 up_to_date = s3dl_dbt_update_keys(dbt, client, error);
3143 if (up_to_date) {
3144 secerror("Completed roll keys.");
3145 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
3146 }
3147 if (!up_to_date)
3148 secerror("Failed to roll keys.");
3149 }
3150 return up_to_date;
3151 });
3152 #else
3153 return true;
3154 #endif
3155 }
3156
3157 static bool
3158 InitialSyncItems(CFMutableArrayRef items, bool limitToCurrent, CFStringRef agrp, CFStringRef svce, const SecDbClass *qclass, CFErrorRef *error)
3159 {
3160 bool result = false;
3161 Query *q = NULL;
3162
3163 q = query_create(qclass, NULL, NULL, error);
3164 require(q, fail);
3165
3166 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3167 q->q_limit = kSecMatchUnlimited;
3168 q->q_keybag = KEYBAG_DEVICE;
3169
3170 query_add_attribute(kSecAttrAccessGroup, agrp, q);
3171 query_add_attribute(kSecAttrSynchronizable, kCFBooleanTrue, q);
3172 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
3173 if (svce)
3174 query_add_attribute(kSecAttrService, svce, q);
3175
3176 result = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
3177 return kc_transaction(dbt, error, ^{
3178 CFErrorRef error2 = NULL;
3179
3180 SecDbItemSelect(q, dbt, &error2, NULL, ^bool(const SecDbAttr *attr) {
3181 return CFDictionaryGetValue(q->q_item, attr->name);
3182 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3183 CFErrorRef error3 = NULL;
3184 secinfo("InitialSyncItems", "Copy item");
3185
3186 CFMutableDictionaryRef attrs = SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &error3);
3187 if (attrs) {
3188 int match = true;
3189 CFStringRef itemvwht = CFDictionaryGetValue(attrs, kSecAttrSyncViewHint);
3190 /*
3191 * Saying its a SOS viewhint is really not the right answer post Triangle
3192 */
3193 if (isString(itemvwht) && !SOSViewInSOSSystem(itemvwht)) {
3194 match = false;
3195 }
3196 /*
3197 * Here we encode how PCS stores identities so that we only copy the
3198 * current identites for performance reasons.
3199 */
3200 if (limitToCurrent) {
3201 enum { PCS_CURRENT_IDENTITY_OFFSET = 0x10000 };
3202 int32_t s32;
3203
3204 CFNumberRef type = CFDictionaryGetValue(attrs, kSecAttrType);
3205 if (!isNumber(type)) {
3206 // still allow this case since its not a service identity ??
3207 } else if (!CFNumberGetValue(type, kCFNumberSInt32Type, &s32)) {
3208 match = false;
3209 } else if ((s32 & PCS_CURRENT_IDENTITY_OFFSET) == 0) {
3210 match = false;
3211 }
3212 }
3213 if (match) {
3214 CFDictionaryAddValue(attrs, kSecClass, SecDbItemGetClass(item)->name);
3215 CFArrayAppendValue(items, attrs);
3216 }
3217
3218 CFReleaseNull(attrs);
3219 }
3220 CFReleaseNull(error3);
3221 });
3222 CFReleaseNull(error2);
3223
3224 return (bool)true;
3225 });
3226 });
3227
3228 fail:
3229 if (q)
3230 query_destroy(q, NULL);
3231 return result;
3232 }
3233
3234 CFArrayRef
3235 _SecServerCopyInitialSyncCredentials(uint32_t flags, CFErrorRef *error)
3236 {
3237 CFMutableArrayRef items = CFArrayCreateMutableForCFTypes(NULL);
3238
3239 if (flags & SecServerInitialSyncCredentialFlagTLK) {
3240 require_action(InitialSyncItems(items, false, CFSTR("com.apple.security.ckks"), NULL, inet_class(), error), fail,
3241 secerror("failed to collect CKKS-inet keys: %@", error ? *error : NULL));
3242 }
3243 if (flags & SecServerInitialSyncCredentialFlagPCS) {
3244 bool onlyCurrent = !(flags & SecServerInitialSyncCredentialFlagPCSNonCurrent);
3245
3246 require_action(InitialSyncItems(items, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL, genp_class(), error), fail,
3247 secerror("failed to collect PCS-genp keys: %@", error ? *error : NULL));
3248 require_action(InitialSyncItems(items, onlyCurrent, CFSTR("com.apple.ProtectedCloudStorage"), NULL, inet_class(), error), fail,
3249 secerror("failed to collect PCS-inet keys: %@", error ? *error : NULL));
3250 }
3251 if (flags & SecServerInitialSyncCredentialFlagBluetoothMigration) {
3252 require_action(InitialSyncItems(items, false, CFSTR("com.apple.nanoregistry.migration"), NULL, genp_class(), error), fail,
3253 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error ? *error : NULL));
3254 require_action(InitialSyncItems(items, false, CFSTR("com.apple.nanoregistry.migration2"), NULL, genp_class(), error), fail,
3255 secerror("failed to collect com.apple.nanoregistry.migration2-genp item: %@", error ? *error : NULL));
3256 require_action(InitialSyncItems(items, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error), fail,
3257 secerror("failed to collect com.apple.bluetooth-genp item: %@", error ? *error : NULL));
3258
3259 }
3260
3261 fail:
3262 return items;
3263 }
3264
3265 bool
3266 _SecServerImportInitialSyncCredentials(CFArrayRef array, CFErrorRef *error)
3267 {
3268 return kc_with_dbt(true, error, ^bool(SecDbConnectionRef dbt) {
3269 return kc_transaction(dbt, error, ^bool(void){
3270 CFIndex n, count = CFArrayGetCount(array);
3271
3272 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count);
3273
3274 for (n = 0; n < count; n++) {
3275 CFErrorRef cferror = NULL;
3276
3277 CFDictionaryRef item = CFArrayGetValueAtIndex(array, n);
3278 if (!isDictionary(item))
3279 continue;
3280
3281 CFStringRef className = CFDictionaryGetValue(item, kSecClass);
3282 if (className == NULL) {
3283 secinfo("ImportInitialSyncItems", "Item w/o class");
3284 continue;
3285 }
3286
3287 const SecDbClass *cls = kc_class_with_name(className);
3288 if (cls == NULL) {
3289 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className);
3290 continue;
3291 }
3292
3293 SecDbItemRef dbi = SecDbItemCreateWithAttributes(NULL, cls, item, KEYBAG_DEVICE, &cferror);
3294 if (dbi == NULL) {
3295 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror);
3296 CFReleaseNull(cferror);
3297 continue;
3298 }
3299
3300 if (!SecDbItemSetSyncable(dbi, true, &cferror)) {
3301 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror, dbi);
3302 CFReleaseNull(cferror);
3303 CFReleaseNull(dbi);
3304 continue;
3305 }
3306
3307 if (!SecDbItemInsert(dbi, dbt, &cferror)) {
3308 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror, dbi);
3309 CFReleaseNull(cferror);
3310 }
3311 CFReleaseNull(dbi);
3312 }
3313 return true;
3314 });
3315 });
3316 }
3317
3318
3319
3320 #if TARGET_OS_IOS
3321
3322 /*
3323 * Sync bubble migration code
3324 */
3325
3326 struct SyncBubbleRule {
3327 CFStringRef attribute;
3328 CFTypeRef value;
3329 };
3330
3331 static bool
3332 TransmogrifyItemsToSyncBubble(SecurityClient *client, uid_t uid,
3333 bool onlyDelete,
3334 bool copyToo,
3335 const SecDbClass *qclass,
3336 struct SyncBubbleRule *items, CFIndex nItems,
3337 CFErrorRef *error)
3338 {
3339 CFMutableDictionaryRef updateAttributes = NULL;
3340 CFDataRef syncBubbleView = NULL;
3341 CFDataRef activeUserView = NULL;
3342 bool res = false;
3343 Query *q = NULL;
3344 CFIndex n;
3345
3346 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
3347 require(syncBubbleView, fail);
3348
3349 activeUserView = SecMUSRCreateActiveUserUUID(uid);
3350 require(activeUserView, fail);
3351
3352
3353 if ((onlyDelete && !copyToo) || !onlyDelete) {
3354
3355 /*
3356 * Clean out items first
3357 */
3358
3359 secnotice("syncbubble", "cleaning out old items");
3360
3361 q = query_create(qclass, NULL, NULL, error);
3362 require(q, fail);
3363
3364 q->q_limit = kSecMatchUnlimited;
3365 q->q_keybag = device_keybag_handle;
3366
3367 for (n = 0; n < nItems; n++) {
3368 query_add_attribute(items[n].attribute, items[n].value, q);
3369 }
3370 q->q_musrView = CFRetain(syncBubbleView);
3371 require(q->q_musrView, fail);
3372
3373 kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
3374 return kc_transaction(dbt, error, ^{
3375 return s3dl_query_delete(dbt, q, NULL, error);
3376 });
3377 });
3378
3379 query_destroy(q, NULL);
3380 q = NULL;
3381 }
3382
3383
3384 if (onlyDelete || !copyToo) {
3385 secnotice("syncbubble", "skip migration of items");
3386 } else {
3387 /*
3388 * Copy over items from EMCS to sync bubble
3389 */
3390
3391 secnotice("syncbubble", "migrating sync bubble items");
3392
3393 q = query_create(qclass, NULL, NULL, error);
3394 require(q, fail);
3395
3396 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3397 q->q_limit = kSecMatchUnlimited;
3398 q->q_keybag = device_keybag_handle; /* XXX change to session key bag when it exists */
3399
3400 for (n = 0; n < nItems; n++) {
3401 query_add_or_attribute(items[n].attribute, items[n].value, q);
3402 }
3403 query_add_or_attribute(CFSTR("musr"), activeUserView, q);
3404 q->q_musrView = CFRetain(activeUserView);
3405
3406 updateAttributes = CFDictionaryCreateMutableForCFTypes(NULL);
3407 require(updateAttributes, fail);
3408
3409 CFDictionarySetValue(updateAttributes, CFSTR("musr"), syncBubbleView); /* XXX should use kSecAttrMultiUser */
3410
3411
3412 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3413 return kc_transaction(dbt, error, ^{
3414 CFErrorRef error2 = NULL;
3415
3416 SecDbItemSelect(q, dbt, &error2, NULL, ^bool(const SecDbAttr *attr) {
3417 return CFDictionaryGetValue(q->q_item, attr->name);
3418 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3419 CFErrorRef error3 = NULL;
3420 secinfo("syncbubble", "migrating item");
3421
3422 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, updateAttributes, NULL);
3423 if (new_item == NULL)
3424 return;
3425
3426 SecDbItemClearRowId(new_item, NULL);
3427
3428 if (!SecDbItemSetKeybag(new_item, device_keybag_handle, NULL)) {
3429 CFRelease(new_item);
3430 return;
3431 }
3432
3433 if (!SecDbItemInsert(new_item, dbt, &error3)) {
3434 secnotice("syncbubble", "migration failed with %@ for item %@", error3, new_item);
3435 }
3436 CFRelease(new_item);
3437 CFReleaseNull(error3);
3438 });
3439 CFReleaseNull(error2);
3440
3441 return (bool)true;
3442 });
3443 });
3444 }
3445 res = true;
3446
3447 fail:
3448 CFReleaseNull(syncBubbleView);
3449 CFReleaseNull(activeUserView);
3450 CFReleaseNull(updateAttributes);
3451 if (q)
3452 query_destroy(q, NULL);
3453
3454 return res;
3455 }
3456
3457 static struct SyncBubbleRule PCSItems[] = {
3458 {
3459 .attribute = CFSTR("agrp"),
3460 .value = CFSTR("com.apple.ProtectedCloudStorage"),
3461 }
3462 };
3463 static struct SyncBubbleRule NSURLSesssiond[] = {
3464 {
3465 .attribute = CFSTR("agrp"),
3466 .value = CFSTR("com.apple.nsurlsessiond"),
3467 }
3468 };
3469 static struct SyncBubbleRule AccountsdItems[] = {
3470 {
3471 .attribute = CFSTR("svce"),
3472 .value = CFSTR("com.apple.account.AppleAccount.token"),
3473 },
3474 {
3475 .attribute = CFSTR("svce"),
3476 .value = CFSTR("com.apple.account.AppleAccount.password"),
3477 },
3478 {
3479 .attribute = CFSTR("svce"),
3480 .value = CFSTR("com.apple.account.AppleAccount.rpassword"),
3481 },
3482 {
3483 .attribute = CFSTR("svce"),
3484 .value = CFSTR("com.apple.account.idms.token"),
3485 },
3486 {
3487 .attribute = CFSTR("svce"),
3488 .value = CFSTR("com.apple.account.idms.continuation-key"),
3489 },
3490 {
3491 .attribute = CFSTR("svce"),
3492 .value = CFSTR("com.apple.account.CloudKit.token"),
3493 },
3494 };
3495
3496 static struct SyncBubbleRule MobileMailItems[] = {
3497 {
3498 .attribute = CFSTR("svce"),
3499 .value = CFSTR("com.apple.account.IMAP.password"),
3500 },
3501 {
3502 .attribute = CFSTR("svce"),
3503 .value = CFSTR("com.apple.account.SMTP.password"),
3504 },
3505 {
3506 .attribute = CFSTR("svce"),
3507 .value = CFSTR("com.apple.account.Exchange.password"),
3508 },
3509 {
3510 .attribute = CFSTR("svce"),
3511 .value = CFSTR("com.apple.account.Hotmail.password"),
3512 },
3513 {
3514 .attribute = CFSTR("svce"),
3515 .value = CFSTR("com.apple.account.Google.password"),
3516 },
3517 {
3518 .attribute = CFSTR("svce"),
3519 .value = CFSTR("com.apple.account.Google.oauth-token"),
3520 },
3521 {
3522 .attribute = CFSTR("svce"),
3523 .value = CFSTR("com.apple.account.Google.oath-refresh-token"),
3524 },
3525 {
3526 .attribute = CFSTR("svce"),
3527 .value = CFSTR("com.apple.account.Yahoo.password"),
3528 },
3529 {
3530 .attribute = CFSTR("svce"),
3531 .value = CFSTR("com.apple.account.Yahoo.oauth-token"),
3532 },
3533 {
3534 .attribute = CFSTR("svce"),
3535 .value = CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3536 },
3537 {
3538 .attribute = CFSTR("svce"),
3539 .value = CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3540 },
3541 {
3542 .attribute = CFSTR("svce"),
3543 .value = CFSTR("com.apple.account.IMAPNotes.password"),
3544 },
3545 {
3546 .attribute = CFSTR("svce"),
3547 .value = CFSTR("com.apple.account.IMAPMail.password"),
3548 },
3549 {
3550 .attribute = CFSTR("svce"),
3551 .value = CFSTR("com.apple.account.126.password"),
3552 },
3553 {
3554 .attribute = CFSTR("svce"),
3555 .value = CFSTR("com.apple.account.163.password"),
3556 },
3557 {
3558 .attribute = CFSTR("svce"),
3559 .value = CFSTR("com.apple.account.aol.password"),
3560 },
3561 };
3562
3563 static bool
3564 ArrayContains(CFArrayRef array, CFStringRef service)
3565 {
3566 return CFArrayContainsValue(array, CFRangeMake(0, CFArrayGetCount(array)), service);
3567 }
3568
3569 bool
3570 _SecServerTransmogrifyToSyncBubble(CFArrayRef services, uid_t uid, SecurityClient *client, CFErrorRef *error)
3571 {
3572 bool copyCloudAuthToken = false;
3573 bool copyMobileMail = false;
3574 bool res = true;
3575 bool copyPCS = false;
3576 bool onlyDelete = false;
3577 bool copyNSURLSesssion = false;
3578
3579 if (!client->inMultiUser)
3580 return false;
3581
3582 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid, services);
3583
3584 #if TARGET_OS_SIMULATOR
3585 // no delete in sim
3586 #elif TARGET_OS_IOS
3587 if (uid != (uid_t)client->activeUser)
3588 onlyDelete = true;
3589 #else
3590 #error "no sync bubble on other platforms"
3591 #endif
3592
3593 /*
3594 * First select that services to copy/delete
3595 */
3596
3597 if (ArrayContains(services, CFSTR("com.apple.bird.usermanager.sync"))
3598 || ArrayContains(services, CFSTR("com.apple.cloudphotod.sync"))
3599 || ArrayContains(services, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3600 || ArrayContains(services, CFSTR("com.apple.cloudd.usermanager.sync")))
3601 {
3602 copyCloudAuthToken = true;
3603 copyPCS = true;
3604 }
3605
3606 if (ArrayContains(services, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3607 {
3608 copyCloudAuthToken = true;
3609 copyNSURLSesssion = true;
3610 }
3611
3612 if (ArrayContains(services, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3613 copyCloudAuthToken = true;
3614 }
3615 if (ArrayContains(services, CFSTR("com.apple.mailq.sync")) || ArrayContains(services, CFSTR("com.apple.mailq.sync.xpc"))) {
3616 copyCloudAuthToken = true;
3617 copyMobileMail = true;
3618 copyPCS = true;
3619 }
3620
3621 /*
3622 * The actually copy/delete the items selected
3623 */
3624
3625 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, inet_class(), PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
3626 require(res, fail);
3627 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, genp_class(), PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
3628 require(res, fail);
3629
3630 /* mail */
3631 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyMobileMail, genp_class(), MobileMailItems, sizeof(MobileMailItems)/sizeof(MobileMailItems[0]), error);
3632 require(res, fail);
3633
3634 /* accountsd */
3635 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyCloudAuthToken, genp_class(), AccountsdItems, sizeof(AccountsdItems)/sizeof(AccountsdItems[0]), error);
3636 require(res, fail);
3637
3638 /* nsurlsessiond */
3639 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyNSURLSesssion, inet_class(), NSURLSesssiond, sizeof(NSURLSesssiond)/sizeof(NSURLSesssiond[0]), error);
3640 require(res, fail);
3641
3642 fail:
3643 return res;
3644 }
3645
3646 /*
3647 * Migrate from user keychain to system keychain when switching to edu mode
3648 */
3649
3650 bool
3651 _SecServerTransmogrifyToSystemKeychain(SecurityClient *client, CFErrorRef *error)
3652 {
3653 __block bool ok = true;
3654
3655 /*
3656 * we are not in multi user yet, about to switch, otherwise we would
3657 * check that for client->inMultiuser here
3658 */
3659
3660 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3661 return kc_transaction(dbt, error, ^{
3662 CFDataRef systemUUID = SecMUSRGetSystemKeychainUUID();
3663
3664 const SecDbSchema *newSchema = current_schema();
3665 SecDbClass const *const *kcClass;
3666
3667 for (kcClass = newSchema->classes; *kcClass != NULL; kcClass++) {
3668 CFErrorRef localError = NULL;
3669 Query *q = NULL;
3670
3671 if (!((*kcClass)->itemclass)) {
3672 continue;
3673 }
3674
3675 q = query_create(*kcClass, SecMUSRGetSingleUserKeychainUUID(), NULL, error);
3676 if (q == NULL)
3677 continue;
3678
3679 ok &= SecDbItemSelect(q, dbt, error, ^bool(const SecDbAttr *attr) {
3680 return (attr->flags & kSecDbInFlag) != 0;
3681 }, ^bool(const SecDbAttr *attr) {
3682 // No filtering please.
3683 return false;
3684 }, ^bool(CFMutableStringRef sql, bool *needWhere) {
3685 SecDbAppendWhereOrAnd(sql, needWhere);
3686 CFStringAppendFormat(sql, NULL, CFSTR("musr = ?"));
3687 return true;
3688 }, ^bool(sqlite3_stmt *stmt, int col) {
3689 return SecDbBindObject(stmt, col++, SecMUSRGetSingleUserKeychainUUID(), error);
3690 }, ^(SecDbItemRef item, bool *stop) {
3691 CFErrorRef localError = NULL;
3692
3693 if (!SecDbItemSetValueWithName(item, kSecAttrMultiUser, systemUUID, &localError)) {
3694 secerror("item: %@ update musr to system failed: %@", item, localError);
3695 ok = false;
3696 goto out;
3697 }
3698
3699 if (!SecDbItemDoUpdate(item, item, dbt, &localError, ^bool (const SecDbAttr *attr) {
3700 return attr->kind == kSecDbRowIdAttr;
3701 })) {
3702 secerror("item: %@ insert during UPDATE: %@", item, localError);
3703 ok = false;
3704 goto out;
3705 }
3706
3707 out:
3708 SecErrorPropagate(localError, error);
3709 });
3710
3711 if (q)
3712 query_destroy(q, &localError);
3713
3714 }
3715 return (bool)true;
3716 });
3717 });
3718
3719 return ok;
3720 }
3721
3722 /*
3723 * Delete account from local usage
3724 */
3725
3726 bool
3727 _SecServerDeleteMUSERViews(SecurityClient *client, uid_t uid, CFErrorRef *error)
3728 {
3729 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3730 CFDataRef musrView = NULL, syncBubbleView = NULL;
3731 bool ok = false;
3732
3733 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
3734 require(syncBubbleView, fail);
3735
3736 musrView = SecMUSRCreateActiveUserUUID(uid);
3737 require(musrView, fail);
3738
3739 require(ok = SecServerDeleteAllForUser(dbt, syncBubbleView, false, error), fail);
3740 require(ok = SecServerDeleteAllForUser(dbt, musrView, false, error), fail);
3741
3742 fail:
3743 CFReleaseNull(syncBubbleView);
3744 CFReleaseNull(musrView);
3745 return ok;
3746 });
3747 }
3748
3749
3750 #endif /* TARGET_OS_IOS */
3751
3752 bool
3753 _SecServerGetKeyStats(const SecDbClass *qclass,
3754 struct _SecServerKeyStats *stats)
3755 {
3756 __block CFErrorRef error = NULL;
3757 bool res = false;
3758
3759 Query *q = query_create(qclass, NULL, NULL, &error);
3760 require(q, fail);
3761
3762 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3763 q->q_limit = kSecMatchUnlimited;
3764 q->q_keybag = KEYBAG_DEVICE;
3765 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
3766 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
3767 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlways, q);
3768 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, q);
3769 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, q);
3770 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysThisDeviceOnly, q);
3771 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
3772
3773 kc_with_dbt(false, &error, ^(SecDbConnectionRef dbconn) {
3774 CFErrorRef error2 = NULL;
3775 __block CFIndex totalSize = 0;
3776 stats->maxDataSize = 0;
3777
3778 SecDbItemSelect(q, dbconn, &error2, NULL, ^bool(const SecDbAttr *attr) {
3779 return CFDictionaryContainsKey(q->q_item, attr->name);
3780 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3781 CFErrorRef error3 = NULL;
3782 CFDataRef data = SecDbItemGetValue(item, &v6v_Data, &error3);
3783 if (isData(data)) {
3784 CFIndex size = CFDataGetLength(data);
3785 if (size > stats->maxDataSize)
3786 stats->maxDataSize = size;
3787 totalSize += size;
3788 stats->items++;
3789 }
3790 CFReleaseNull(error3);
3791 });
3792 CFReleaseNull(error2);
3793 if (stats->items)
3794 stats->averageSize = totalSize / stats->items;
3795
3796 return (bool)true;
3797 });
3798
3799
3800 res = true;
3801
3802 fail:
3803 CFReleaseNull(error);
3804 if (q)
3805 query_destroy(q, NULL);
3806 return res;
3807 }
3808
3809 CFArrayRef _SecItemCopyParentCertificates(CFDataRef normalizedIssuer, CFArrayRef accessGroups, CFErrorRef *error) {
3810 const void *keys[] = {
3811 kSecClass,
3812 kSecReturnData,
3813 kSecMatchLimit,
3814 kSecAttrSubject
3815 },
3816 *values[] = {
3817 kSecClassCertificate,
3818 kCFBooleanTrue,
3819 kSecMatchLimitAll,
3820 normalizedIssuer
3821 };
3822 CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 4,
3823 NULL, NULL);
3824 CFTypeRef results = NULL;
3825 SecurityClient client = {
3826 .task = NULL,
3827 .accessGroups = accessGroups,
3828 .allowSystemKeychain = true,
3829 .allowSyncBubbleKeychain = false,
3830 .isNetworkExtension = false,
3831 };
3832
3833 (void)_SecItemCopyMatching(query, &client, &results, error);
3834 CFRelease(query);
3835 return results;
3836 }
3837
3838 bool _SecItemCertificateExists(CFDataRef normalizedIssuer, CFDataRef serialNumber, CFArrayRef accessGroups, CFErrorRef *error) {
3839 const void *keys[] = {
3840 kSecClass,
3841 kSecMatchLimit,
3842 kSecAttrIssuer,
3843 kSecAttrSerialNumber
3844 },
3845 *values[] = {
3846 kSecClassCertificate,
3847 kSecMatchLimitOne,
3848 normalizedIssuer,
3849 serialNumber
3850 };
3851 SecurityClient client = {
3852 .task = NULL,
3853 .accessGroups = accessGroups,
3854 .allowSystemKeychain = true,
3855 .allowSyncBubbleKeychain = false,
3856 .isNetworkExtension = false,
3857 };
3858 CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 4, NULL, NULL);
3859 CFTypeRef results = NULL;
3860 bool ok = _SecItemCopyMatching(query, &client, &results, error);
3861 CFReleaseSafe(query);
3862 CFReleaseSafe(results);
3863 if (!ok) {
3864 return false;
3865 }
3866 return true;
3867 }