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