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