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