]> git.saurik.com Git - apple/security.git/blob - OSX/sec/securityd/SecItemServer.c
bb61f4d5518782fe0fb2691364d5dcd18e799372
[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 return kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
2021 return (kc_transaction(dbt, error, ^bool {
2022 return (SecDbExec(dbt, CFSTR("DELETE from genp;"), error) &&
2023 SecDbExec(dbt, CFSTR("DELETE from inet;"), error) &&
2024 SecDbExec(dbt, CFSTR("DELETE from cert;"), error) &&
2025 SecDbExec(dbt, CFSTR("DELETE from keys;"), error));
2026 }) && SecDbExec(dbt, CFSTR("VACUUM;"), error));
2027 });
2028 }
2029
2030 bool
2031 _SecItemDeleteAll(CFErrorRef *error) {
2032 return SecItemServerDeleteAll(error);
2033 }
2034
2035 bool
2036 _SecItemServerDeleteAllWithAccessGroups(CFArrayRef accessGroups, SecurityClient *client, CFErrorRef *error)
2037 {
2038 __block bool ok = true;
2039 static dispatch_once_t onceToken;
2040 static CFSetRef illegalAccessGroups = NULL;
2041
2042 dispatch_once(&onceToken, ^{
2043 const CFStringRef values[] = {
2044 CFSTR("*"),
2045 CFSTR("apple"),
2046 CFSTR("com.apple.security.sos"),
2047 CFSTR("lockdown-identities"),
2048 };
2049 illegalAccessGroups = CFSetCreate(NULL, (const void **)values, sizeof(values)/sizeof(values[0]), &kCFTypeSetCallBacks);
2050 });
2051
2052 static CFTypeRef qclasses[] = {
2053 NULL,
2054 NULL,
2055 NULL,
2056 NULL
2057 };
2058 // strange construction needed for schema indirection
2059 static dispatch_once_t qclassesOnceToken;
2060 dispatch_once(&qclassesOnceToken, ^{
2061 qclasses[0] = inet_class();
2062 qclasses[1] = genp_class();
2063 qclasses[2] = keys_class();
2064 qclasses[3] = cert_class();
2065 });
2066
2067 require_action_quiet(isArray(accessGroups), fail,
2068 ok = false;
2069 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("accessGroups not CFArray, got %@"), accessGroups));
2070
2071 // TODO: whitelist instead? look for dev IDs like 7123498YQX.com.somedev.app
2072
2073 require_action(CFArrayGetCount(accessGroups) != 0, fail,
2074 ok = false;
2075 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("accessGroups e empty")));
2076
2077
2078 // Pre-check accessGroups for prohibited values
2079 CFArrayForEach(accessGroups, ^(const void *value) {
2080 CFStringRef agrp = (CFStringRef)value;
2081
2082 if (!isString(agrp)) {
2083 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL,
2084 CFSTR("access not a string: %@"), agrp);
2085 ok &= false;
2086 } else if (CFSetContainsValue(illegalAccessGroups, agrp)) {
2087 SecCFCreateErrorWithFormat(kSecXPCErrorUnexpectedType, sSecXPCErrorDomain, NULL, error, NULL,
2088 CFSTR("illegal access group: %@"), accessGroups);
2089 ok &= false;
2090 }
2091 });
2092 require(ok,fail);
2093
2094 ok = kc_with_dbt(true, error, ^bool(SecDbConnectionRef dbt) {
2095 return kc_transaction(dbt, error, ^bool {
2096 CFErrorRef localError = NULL;
2097 bool ok1 = true;
2098 size_t n;
2099
2100 for (n = 0; n < sizeof(qclasses)/sizeof(qclasses[0]) && ok1; n++) {
2101 Query *q;
2102
2103 q = query_create(qclasses[n], client->musr, NULL, error);
2104 require(q, fail2);
2105
2106 (void)s3dl_query_delete(dbt, q, accessGroups, &localError);
2107 fail2:
2108 query_destroy(q, error);
2109 CFReleaseNull(localError);
2110 }
2111 return ok1;
2112 }) && SecDbExec(dbt, CFSTR("VACUUM"), error);
2113 });
2114
2115 fail:
2116 return ok;
2117 }
2118
2119
2120 // MARK: -
2121 // MARK: Shared web credentials
2122
2123 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2124
2125 /* constants */
2126 #define SEC_CONST_DECL(k,v) const CFStringRef k = CFSTR(v);
2127
2128 SEC_CONST_DECL (kSecSafariAccessGroup, "com.apple.cfnetwork");
2129 SEC_CONST_DECL (kSecSafariDefaultComment, "default");
2130 SEC_CONST_DECL (kSecSafariPasswordsNotSaved, "Passwords not saved");
2131 SEC_CONST_DECL (kSecSharedCredentialUrlScheme, "https://");
2132 SEC_CONST_DECL (kSecSharedWebCredentialsService, "webcredentials");
2133
2134 #if !TARGET_IPHONE_SIMULATOR
2135 static SWCFlags
2136 _SecAppDomainApprovalStatus(CFStringRef appID, CFStringRef fqdn, CFErrorRef *error)
2137 {
2138 __block SWCFlags flags = kSWCFlags_None;
2139 OSStatus status;
2140
2141 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
2142 if (semaphore == NULL)
2143 return 0;
2144
2145 status = SWCCheckService(kSecSharedWebCredentialsService, appID, fqdn, ^void (OSStatus inStatus, SWCFlags inFlags, CFDictionaryRef inDetails)
2146 {
2147 if (inStatus == 0) {
2148 flags = inFlags;
2149 } else {
2150 secerror("SWCCheckService failed with %d", (int)inStatus);
2151 }
2152 dispatch_semaphore_signal(semaphore);
2153 });
2154
2155 if (status == 0) {
2156 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
2157 } else {
2158 secerror("SWCCheckService: failed to queue");
2159 }
2160 dispatch_release(semaphore);
2161
2162 if (error) {
2163 if (!(flags & kSWCFlag_SiteApproved)) {
2164 if (flags & kSWCFlag_Pending) {
2165 SecError(errSecAuthFailed, error, CFSTR("Approval is pending for \"%@\", try later"), fqdn);
2166 } else {
2167 SecError(errSecAuthFailed, error, CFSTR("\"%@\" failed to approve \"%@\""), fqdn, appID);
2168 }
2169 } else if (flags & kSWCFlag_UserDenied) {
2170 SecError(errSecAuthFailed, error, CFSTR("User denied access to \"%@\" by \"%@\""), fqdn, appID);
2171 }
2172 }
2173 return flags;
2174 }
2175
2176 static bool
2177 _SecEntitlementContainsDomainForService(CFArrayRef domains, CFStringRef domain, CFStringRef service)
2178 {
2179 bool result = false;
2180 CFIndex idx, count = (domains) ? CFArrayGetCount(domains) : (CFIndex) 0;
2181 if (!count || !domain || !service) {
2182 return result;
2183 }
2184 for (idx=0; idx < count; idx++) {
2185 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
2186 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
2187 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
2188 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
2189 CFRange range = { prefix_len, substr_len };
2190 CFStringRef substr = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
2191 if (substr && CFEqual(substr, domain)) {
2192 result = true;
2193 }
2194 CFReleaseSafe(substr);
2195 if (result) {
2196 break;
2197 }
2198 }
2199 }
2200 return result;
2201 }
2202 #endif /* !TARGET_OS_SIMULATOR */
2203
2204 static bool
2205 _SecAddNegativeWebCredential(SecurityClient *client, CFStringRef fqdn, CFStringRef appID, bool forSafari)
2206 {
2207 #if !TARGET_IPHONE_SIMULATOR
2208 bool result = false;
2209 if (!fqdn) { return result; }
2210
2211 // update our database
2212 CFRetainSafe(appID);
2213 CFRetainSafe(fqdn);
2214 if (0 == SWCSetServiceFlags(kSecSharedWebCredentialsService, appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserDenied,
2215 ^void(OSStatus inStatus, SWCFlags inNewFlags){
2216 CFReleaseSafe(appID);
2217 CFReleaseSafe(fqdn);
2218 }))
2219 {
2220 result = true;
2221 }
2222 else // didn't queue the block
2223 {
2224 CFReleaseSafe(appID);
2225 CFReleaseSafe(fqdn);
2226 }
2227
2228 if (!forSafari) { return result; }
2229
2230 // below this point: create a negative Safari web credential item
2231
2232 CFMutableDictionaryRef attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2233 if (!attrs) { return result; }
2234
2235 CFErrorRef error = NULL;
2236 CFStringRef accessGroup = CFSTR("*");
2237 SecurityClient swcclient = {
2238 .task = NULL,
2239 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
2240 .allowSystemKeychain = false,
2241 .allowSyncBubbleKeychain = false,
2242 .isNetworkExtension = false,
2243 .musr = client->musr,
2244 };
2245
2246 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
2247 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
2248 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2249 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2250 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
2251 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
2252
2253 (void)_SecItemDelete(attrs, &swcclient, &error);
2254 CFReleaseNull(error);
2255
2256 CFDictionaryAddValue(attrs, kSecAttrAccount, kSecSafariPasswordsNotSaved);
2257 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
2258
2259 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault,
2260 NULL, CFSTR("%@ (%@)"), fqdn, kSecSafariPasswordsNotSaved);
2261 if (label) {
2262 CFDictionaryAddValue(attrs, kSecAttrLabel, label);
2263 CFReleaseSafe(label);
2264 }
2265
2266 UInt8 space = ' ';
2267 CFDataRef data = CFDataCreate(kCFAllocatorDefault, &space, 1);
2268 if (data) {
2269 CFDictionarySetValue(attrs, kSecValueData, data);
2270 CFReleaseSafe(data);
2271 }
2272
2273 CFTypeRef addResult = NULL;
2274 result = _SecItemAdd(attrs, &swcclient, &addResult, &error);
2275
2276 CFReleaseSafe(addResult);
2277 CFReleaseSafe(error);
2278 CFReleaseSafe(attrs);
2279 CFReleaseSafe(swcclient.accessGroups);
2280
2281 return result;
2282 #else
2283 return true;
2284 #endif
2285 }
2286
2287 /* Specialized version of SecItemAdd for shared web credentials */
2288 bool
2289 _SecAddSharedWebCredential(CFDictionaryRef attributes,
2290 SecurityClient *client,
2291 const audit_token_t *clientAuditToken,
2292 CFStringRef appID,
2293 CFArrayRef domains,
2294 CFTypeRef *result,
2295 CFErrorRef *error)
2296 {
2297
2298 SecurityClient swcclient = {};
2299
2300 CFStringRef fqdn = CFRetainSafe(CFDictionaryGetValue(attributes, kSecAttrServer));
2301 CFStringRef account = CFDictionaryGetValue(attributes, kSecAttrAccount);
2302 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE
2303 CFStringRef password = CFDictionaryGetValue(attributes, kSecSharedPassword);
2304 #else
2305 CFStringRef password = CFDictionaryGetValue(attributes, CFSTR("spwd"));
2306 #endif
2307 CFStringRef accessGroup = CFSTR("*");
2308 CFMutableDictionaryRef query = NULL, attrs = NULL;
2309 SInt32 port = -1;
2310 bool ok = false;
2311
2312 // check autofill enabled status
2313 if (!swca_autofill_enabled(clientAuditToken)) {
2314 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
2315 goto cleanup;
2316 }
2317
2318 // parse fqdn with CFURL here, since it could be specified as domain:port
2319 if (fqdn) {
2320 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
2321 if (urlStr) {
2322 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
2323 if (url) {
2324 CFStringRef hostname = CFURLCopyHostName(url);
2325 if (hostname) {
2326 CFReleaseSafe(fqdn);
2327 fqdn = hostname;
2328 port = CFURLGetPortNumber(url);
2329 }
2330 CFReleaseSafe(url);
2331 }
2332 CFReleaseSafe(urlStr);
2333 }
2334 }
2335
2336 if (!account) {
2337 SecError(errSecParam, error, CFSTR("No account provided"));
2338 goto cleanup;
2339 }
2340 if (!fqdn) {
2341 SecError(errSecParam, error, CFSTR("No domain provided"));
2342 goto cleanup;
2343 }
2344
2345 #if TARGET_IPHONE_SIMULATOR
2346 secerror("app/site association entitlements not checked in Simulator");
2347 #else
2348 OSStatus status = errSecMissingEntitlement;
2349 // validate that fqdn is part of caller's shared credential domains entitlement
2350 if (!appID) {
2351 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
2352 goto cleanup;
2353 }
2354 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
2355 status = errSecSuccess;
2356 }
2357 if (errSecSuccess != status) {
2358 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
2359 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
2360 if (!msg) {
2361 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
2362 }
2363 SecError(status, error, CFSTR("%@"), msg);
2364 CFReleaseSafe(msg);
2365 goto cleanup;
2366 }
2367 #endif
2368
2369 #if TARGET_IPHONE_SIMULATOR
2370 secerror("Ignoring app/site approval state in the Simulator.");
2371 #else
2372 // get approval status for this app/domain pair
2373 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
2374 if (!(flags & kSWCFlag_SiteApproved)) {
2375 goto cleanup;
2376 }
2377 #endif
2378
2379 // give ourselves access to see matching items for kSecSafariAccessGroup
2380 swcclient.task = NULL;
2381 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks);
2382 swcclient.allowSystemKeychain = false;
2383 swcclient.musr = client->musr;
2384 swcclient.allowSystemKeychain = false;
2385 swcclient.allowSyncBubbleKeychain = false;
2386 swcclient.isNetworkExtension = false;
2387
2388
2389 // create lookup query
2390 query = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2391 if (!query) {
2392 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
2393 goto cleanup;
2394 }
2395 CFDictionaryAddValue(query, kSecClass, kSecClassInternetPassword);
2396 CFDictionaryAddValue(query, kSecAttrAccessGroup, kSecSafariAccessGroup);
2397 CFDictionaryAddValue(query, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2398 CFDictionaryAddValue(query, kSecAttrServer, fqdn);
2399 CFDictionaryAddValue(query, kSecAttrSynchronizable, kCFBooleanTrue);
2400
2401 // check for presence of Safari's negative entry ('passwords not saved')
2402 CFDictionarySetValue(query, kSecAttrAccount, kSecSafariPasswordsNotSaved);
2403 ok = _SecItemCopyMatching(query, &swcclient, result, error);
2404 if(result) CFReleaseNull(*result);
2405 if (error) CFReleaseNull(*error);
2406 if (ok) {
2407 SecError(errSecDuplicateItem, error, CFSTR("Item already exists for this server"));
2408 goto cleanup;
2409 }
2410
2411 // now use the provided account (and optional port number, if one was present)
2412 CFDictionarySetValue(query, kSecAttrAccount, account);
2413 if (port < -1 || port > 0) {
2414 SInt16 portValueShort = (port & 0xFFFF);
2415 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
2416 CFDictionaryAddValue(query, kSecAttrPort, portNumber);
2417 CFReleaseSafe(portNumber);
2418 }
2419
2420 // look up existing password
2421 CFDictionaryAddValue(query, kSecReturnData, kCFBooleanTrue);
2422 bool matched = _SecItemCopyMatching(query, &swcclient, result, error);
2423 CFDictionaryRemoveValue(query, kSecReturnData);
2424 if (matched) {
2425 // found it, so this becomes either an "update password" or "delete password" operation
2426 bool update = (password != NULL);
2427 if (update) {
2428 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2429 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
2430 CFDictionaryAddValue(attrs, kSecValueData, credential);
2431 bool samePassword = result && *result && CFEqual(*result, credential);
2432 CFReleaseSafe(credential);
2433 CFDictionaryAddValue(attrs, kSecAttrComment, kSecSafariDefaultComment);
2434
2435 ok = samePassword || swca_confirm_operation(swca_update_request_id, clientAuditToken, query, error,
2436 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2437 if (ok) {
2438 ok = _SecItemUpdate(query, attrs, &swcclient, error);
2439 }
2440 }
2441 else {
2442 // confirm the delete
2443 // (per rdar://16676288 we always prompt, even if there was prior user approval)
2444 ok = /*approved ||*/ swca_confirm_operation(swca_delete_request_id, clientAuditToken, query, error,
2445 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2446 if (ok) {
2447 ok = _SecItemDelete(query, &swcclient, error);
2448 }
2449 }
2450
2451 if(result) CFReleaseNull(*result);
2452 if(error) CFReleaseNull(*error);
2453
2454 goto cleanup;
2455 }
2456 if (result) CFReleaseNull(*result);
2457 if (error) CFReleaseNull(*error);
2458
2459 // password does not exist, so prepare to add it
2460 if (!password) {
2461 // a NULL password value removes the existing credential. Since we didn't find it, this is a no-op.
2462 ok = true;
2463 goto cleanup;
2464 }
2465 else {
2466 CFStringRef label = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@ (%@)"), fqdn, account);
2467 if (label) {
2468 CFDictionaryAddValue(query, kSecAttrLabel, label);
2469 CFReleaseSafe(label);
2470 }
2471 // NOTE: we always expect to use HTTPS for web forms.
2472 CFDictionaryAddValue(query, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2473
2474 CFDataRef credential = CFStringCreateExternalRepresentation(kCFAllocatorDefault, password, kCFStringEncodingUTF8, 0);
2475 CFDictionarySetValue(query, kSecValueData, credential);
2476 CFReleaseSafe(credential);
2477 CFDictionarySetValue(query, kSecAttrComment, kSecSafariDefaultComment);
2478
2479 CFReleaseSafe(swcclient.accessGroups);
2480 swcclient.accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&kSecSafariAccessGroup, 1, &kCFTypeArrayCallBacks);
2481
2482 // mark the item as created by this function
2483 const int32_t creator_value = 'swca';
2484 CFNumberRef creator = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &creator_value);
2485 if (creator) {
2486 CFDictionarySetValue(query, kSecAttrCreator, creator);
2487 CFReleaseSafe(creator);
2488 ok = true;
2489 }
2490 else {
2491 // confirm the add
2492 // (per rdar://16680019, we won't prompt here in the normal case)
2493 ok = /*approved ||*/ swca_confirm_operation(swca_add_request_id, clientAuditToken, query, error,
2494 ^void (CFStringRef fqdn) { _SecAddNegativeWebCredential(client, fqdn, appID, false); });
2495 }
2496 }
2497 if (ok) {
2498 ok = _SecItemAdd(query, &swcclient, result, error);
2499 }
2500
2501 cleanup:
2502 CFReleaseSafe(attrs);
2503 CFReleaseSafe(query);
2504 CFReleaseSafe(swcclient.accessGroups);
2505 CFReleaseSafe(fqdn);
2506 return ok;
2507 }
2508
2509 /* Specialized version of SecItemCopyMatching for shared web credentials */
2510 bool
2511 _SecCopySharedWebCredential(CFDictionaryRef query,
2512 SecurityClient *client,
2513 const audit_token_t *clientAuditToken,
2514 CFStringRef appID,
2515 CFArrayRef domains,
2516 CFTypeRef *result,
2517 CFErrorRef *error)
2518 {
2519 CFMutableArrayRef credentials = NULL;
2520 CFMutableArrayRef foundItems = NULL;
2521 CFMutableArrayRef fqdns = NULL;
2522 CFStringRef fqdn = NULL;
2523 CFStringRef account = NULL;
2524 CFIndex idx, count;
2525 SInt32 port = -1;
2526 bool ok = false;
2527
2528 require_quiet(result, cleanup);
2529 credentials = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2530 foundItems = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2531 fqdns = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2532
2533 // give ourselves access to see matching items for kSecSafariAccessGroup
2534 CFStringRef accessGroup = CFSTR("*");
2535 SecurityClient swcclient = {
2536 .task = NULL,
2537 .accessGroups = CFArrayCreate(kCFAllocatorDefault, (const void **)&accessGroup, 1, &kCFTypeArrayCallBacks),
2538 .allowSystemKeychain = false,
2539 .allowSyncBubbleKeychain = false,
2540 .isNetworkExtension = false,
2541 .musr = client->musr,
2542 };
2543
2544 // On input, the query dictionary contains optional fqdn and account entries.
2545 fqdn = CFDictionaryGetValue(query, kSecAttrServer);
2546 account = CFDictionaryGetValue(query, kSecAttrAccount);
2547
2548 // Check autofill enabled status
2549 if (!swca_autofill_enabled(clientAuditToken)) {
2550 SecError(errSecBadReq, error, CFSTR("Autofill is not enabled in Safari settings"));
2551 goto cleanup;
2552 }
2553
2554 // Check fqdn; if NULL, add domains from caller's entitlement.
2555 if (fqdn) {
2556 CFArrayAppendValue(fqdns, fqdn);
2557 }
2558 else if (domains) {
2559 CFIndex idx, count = CFArrayGetCount(domains);
2560 for (idx=0; idx < count; idx++) {
2561 CFStringRef str = (CFStringRef) CFArrayGetValueAtIndex(domains, idx);
2562 // Parse the entry for our service label prefix
2563 if (str && CFStringHasPrefix(str, kSecSharedWebCredentialsService)) {
2564 CFIndex prefix_len = CFStringGetLength(kSecSharedWebCredentialsService)+1;
2565 CFIndex substr_len = CFStringGetLength(str) - prefix_len;
2566 CFRange range = { prefix_len, substr_len };
2567 fqdn = CFStringCreateWithSubstring(kCFAllocatorDefault, str, range);
2568 if (fqdn) {
2569 CFArrayAppendValue(fqdns, fqdn);
2570 CFRelease(fqdn);
2571 }
2572 }
2573 }
2574 }
2575 count = CFArrayGetCount(fqdns);
2576 if (count < 1) {
2577 SecError(errSecParam, error, CFSTR("No domain provided"));
2578 goto cleanup;
2579 }
2580
2581 // Aggregate search results for each domain
2582 for (idx = 0; idx < count; idx++) {
2583 CFMutableArrayRef items = NULL;
2584 CFMutableDictionaryRef attrs = NULL;
2585 fqdn = (CFStringRef) CFArrayGetValueAtIndex(fqdns, idx);
2586 CFRetainSafe(fqdn);
2587 port = -1;
2588
2589 // Parse the fqdn for a possible port specifier.
2590 if (fqdn) {
2591 CFStringRef urlStr = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%@%@"), kSecSharedCredentialUrlScheme, fqdn);
2592 if (urlStr) {
2593 CFURLRef url = CFURLCreateWithString(kCFAllocatorDefault, urlStr, nil);
2594 if (url) {
2595 CFStringRef hostname = CFURLCopyHostName(url);
2596 if (hostname) {
2597 CFReleaseSafe(fqdn);
2598 fqdn = hostname;
2599 port = CFURLGetPortNumber(url);
2600 }
2601 CFReleaseSafe(url);
2602 }
2603 CFReleaseSafe(urlStr);
2604 }
2605 }
2606
2607 #if TARGET_IPHONE_SIMULATOR
2608 secerror("app/site association entitlements not checked in Simulator");
2609 #else
2610 OSStatus status = errSecMissingEntitlement;
2611 if (!appID) {
2612 SecError(status, error, CFSTR("Missing application-identifier entitlement"));
2613 CFReleaseSafe(fqdn);
2614 goto cleanup;
2615 }
2616 // validate that fqdn is part of caller's entitlement
2617 if (_SecEntitlementContainsDomainForService(domains, fqdn, kSecSharedWebCredentialsService)) {
2618 status = errSecSuccess;
2619 }
2620 if (errSecSuccess != status) {
2621 CFStringRef msg = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
2622 CFSTR("%@ not found in %@ entitlement"), fqdn, kSecEntitlementAssociatedDomains);
2623 if (!msg) {
2624 msg = CFRetain(CFSTR("Requested domain not found in entitlement"));
2625 }
2626 SecError(status, error, CFSTR("%@"), msg);
2627 CFReleaseSafe(msg);
2628 CFReleaseSafe(fqdn);
2629 goto cleanup;
2630 }
2631 #endif
2632
2633 attrs = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2634 if (!attrs) {
2635 SecError(errSecAllocate, error, CFSTR("Unable to create query dictionary"));
2636 CFReleaseSafe(fqdn);
2637 goto cleanup;
2638 }
2639 CFDictionaryAddValue(attrs, kSecClass, kSecClassInternetPassword);
2640 CFDictionaryAddValue(attrs, kSecAttrAccessGroup, kSecSafariAccessGroup);
2641 CFDictionaryAddValue(attrs, kSecAttrProtocol, kSecAttrProtocolHTTPS);
2642 CFDictionaryAddValue(attrs, kSecAttrAuthenticationType, kSecAttrAuthenticationTypeHTMLForm);
2643 CFDictionaryAddValue(attrs, kSecAttrServer, fqdn);
2644 if (account) {
2645 CFDictionaryAddValue(attrs, kSecAttrAccount, account);
2646 }
2647 if (port < -1 || port > 0) {
2648 SInt16 portValueShort = (port & 0xFFFF);
2649 CFNumberRef portNumber = CFNumberCreate(NULL, kCFNumberSInt16Type, &portValueShort);
2650 CFDictionaryAddValue(attrs, kSecAttrPort, portNumber);
2651 CFReleaseSafe(portNumber);
2652 }
2653 CFDictionaryAddValue(attrs, kSecAttrSynchronizable, kCFBooleanTrue);
2654 CFDictionaryAddValue(attrs, kSecMatchLimit, kSecMatchLimitAll);
2655 CFDictionaryAddValue(attrs, kSecReturnAttributes, kCFBooleanTrue);
2656 CFDictionaryAddValue(attrs, kSecReturnData, kCFBooleanTrue);
2657
2658 ok = _SecItemCopyMatching(attrs, &swcclient, (CFTypeRef*)&items, error);
2659 if (count > 1) {
2660 // ignore interim error since we have multiple domains to search
2661 CFReleaseNull(*error);
2662 }
2663 if (ok && items && CFGetTypeID(items) == CFArrayGetTypeID()) {
2664 #if TARGET_IPHONE_SIMULATOR
2665 secerror("Ignoring app/site approval state in the Simulator.");
2666 bool approved = true;
2667 #else
2668 // get approval status for this app/domain pair
2669 SWCFlags flags = _SecAppDomainApprovalStatus(appID, fqdn, error);
2670 if (count > 1) {
2671 // ignore interim error since we have multiple domains to check
2672 CFReleaseNull(*error);
2673 }
2674 bool approved = (flags & kSWCFlag_SiteApproved);
2675 #endif
2676 if (approved) {
2677 CFArrayAppendArray(foundItems, items, CFRangeMake(0, CFArrayGetCount(items)));
2678 }
2679 }
2680 CFReleaseSafe(items);
2681 CFReleaseSafe(attrs);
2682 CFReleaseSafe(fqdn);
2683 }
2684
2685 // If matching credentials are found, the credentials provided to the completionHandler
2686 // will be a CFArrayRef containing CFDictionaryRef entries. Each dictionary entry will
2687 // contain the following pairs (see Security/SecItem.h):
2688 // key: kSecAttrServer value: CFStringRef (the website)
2689 // key: kSecAttrAccount value: CFStringRef (the account)
2690 // key: kSecSharedPassword value: CFStringRef (the password)
2691 // Optional keys:
2692 // key: kSecAttrPort value: CFNumberRef (the port number, if non-standard for https)
2693
2694 count = CFArrayGetCount(foundItems);
2695 for (idx = 0; idx < count; idx++) {
2696 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(foundItems, idx);
2697 CFMutableDictionaryRef newdict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2698 if (newdict && dict && CFGetTypeID(dict) == CFDictionaryGetTypeID()) {
2699 CFStringRef srvr = CFDictionaryGetValue(dict, kSecAttrServer);
2700 CFStringRef acct = CFDictionaryGetValue(dict, kSecAttrAccount);
2701 CFNumberRef pnum = CFDictionaryGetValue(dict, kSecAttrPort);
2702 CFStringRef icmt = CFDictionaryGetValue(dict, kSecAttrComment);
2703 CFDataRef data = CFDictionaryGetValue(dict, kSecValueData);
2704 if (srvr) {
2705 CFDictionaryAddValue(newdict, kSecAttrServer, srvr);
2706 }
2707 if (acct) {
2708 CFDictionaryAddValue(newdict, kSecAttrAccount, acct);
2709 }
2710 if (pnum) {
2711 SInt16 pval = -1;
2712 if (CFNumberGetValue(pnum, kCFNumberSInt16Type, &pval) &&
2713 (pval < -1 || pval > 0)) {
2714 CFDictionaryAddValue(newdict, kSecAttrPort, pnum);
2715 }
2716 }
2717 if (data) {
2718 CFStringRef password = CFStringCreateFromExternalRepresentation(kCFAllocatorDefault, data, kCFStringEncodingUTF8);
2719 if (password) {
2720 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2721 CFDictionaryAddValue(newdict, kSecSharedPassword, password);
2722 #else
2723 CFDictionaryAddValue(newdict, CFSTR("spwd"), password);
2724 #endif
2725 CFReleaseSafe(password);
2726 }
2727 }
2728 if (icmt && CFEqual(icmt, kSecSafariDefaultComment)) {
2729 CFArrayInsertValueAtIndex(credentials, 0, newdict);
2730 } else {
2731 CFArrayAppendValue(credentials, newdict);
2732 }
2733 }
2734 CFReleaseSafe(newdict);
2735 }
2736
2737 if (count) {
2738
2739 ok = false;
2740
2741 // create a new array of dictionaries (without the actual password) for picker UI
2742 count = CFArrayGetCount(credentials);
2743 CFMutableArrayRef items = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
2744 for (idx = 0; idx < count; idx++) {
2745 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
2746 CFMutableDictionaryRef newdict = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict);
2747 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
2748 CFDictionaryRemoveValue(newdict, kSecSharedPassword);
2749 #else
2750 CFDictionaryRemoveValue(newdict, CFSTR("spwd"));
2751 #endif
2752 CFArrayAppendValue(items, newdict);
2753 CFReleaseSafe(newdict);
2754 }
2755
2756 // prompt user to select one of the dictionary items
2757 CFDictionaryRef selected = swca_copy_selected_dictionary(swca_select_request_id,
2758 clientAuditToken, items, error);
2759 if (selected) {
2760 // find the matching item in our credentials array
2761 CFStringRef srvr = CFDictionaryGetValue(selected, kSecAttrServer);
2762 CFStringRef acct = CFDictionaryGetValue(selected, kSecAttrAccount);
2763 CFNumberRef pnum = CFDictionaryGetValue(selected, kSecAttrPort);
2764 for (idx = 0; idx < count; idx++) {
2765 CFDictionaryRef dict = (CFDictionaryRef) CFArrayGetValueAtIndex(credentials, idx);
2766 CFStringRef srvr1 = CFDictionaryGetValue(dict, kSecAttrServer);
2767 CFStringRef acct1 = CFDictionaryGetValue(dict, kSecAttrAccount);
2768 CFNumberRef pnum1 = CFDictionaryGetValue(dict, kSecAttrPort);
2769
2770 if (!srvr || !srvr1 || !CFEqual(srvr, srvr1)) continue;
2771 if (!acct || !acct1 || !CFEqual(acct, acct1)) continue;
2772 if ((pnum && pnum1) && !CFEqual(pnum, pnum1)) continue;
2773
2774 // we have a match!
2775 CFReleaseSafe(selected);
2776 CFRetainSafe(dict);
2777 selected = dict;
2778 ok = true;
2779 break;
2780 }
2781 }
2782 CFReleaseSafe(items);
2783 CFArrayRemoveAllValues(credentials);
2784 if (selected && ok) {
2785 #if TARGET_OS_IPHONE && !TARGET_OS_WATCH
2786 fqdn = CFDictionaryGetValue(selected, kSecAttrServer);
2787 #endif
2788 CFArrayAppendValue(credentials, selected);
2789 }
2790
2791 if (ok) {
2792 #if TARGET_OS_IOS && !TARGET_OS_BRIDGE && !TARGET_IPHONE_SIMULATOR
2793 // register confirmation with database
2794 CFRetainSafe(appID);
2795 CFRetainSafe(fqdn);
2796 if (0 != SWCSetServiceFlags(kSecSharedWebCredentialsService,
2797 appID, fqdn, kSWCFlag_ExternalMask, kSWCFlag_UserApproved,
2798 ^void(OSStatus inStatus, SWCFlags inNewFlags){
2799 CFReleaseSafe(appID);
2800 CFReleaseSafe(fqdn);
2801 }))
2802 {
2803 // we didn't queue the block
2804 CFReleaseSafe(appID);
2805 CFReleaseSafe(fqdn);
2806 }
2807 #endif
2808 }
2809 CFReleaseSafe(selected);
2810 }
2811 else if (NULL == *error) {
2812 // found no items, and we haven't already filled in the error
2813 SecError(errSecItemNotFound, error, CFSTR("no matching items found"));
2814 }
2815
2816 cleanup:
2817 if (!ok) {
2818 CFArrayRemoveAllValues(credentials);
2819 CFReleaseNull(credentials);
2820 }
2821 CFReleaseSafe(foundItems);
2822 *result = credentials;
2823 CFReleaseSafe(swcclient.accessGroups);
2824 CFReleaseSafe(fqdns);
2825
2826 return ok;
2827 }
2828
2829 #endif /* TARGET_OS_IOS */
2830
2831
2832 // MARK: -
2833 // MARK: Keychain backup
2834
2835 CF_RETURNS_RETAINED CFDataRef
2836 _SecServerKeychainCreateBackup(SecurityClient *client, CFDataRef keybag, CFDataRef passcode, bool emcs, CFErrorRef *error) {
2837 __block CFDataRef backup;
2838 kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbt) {
2839 if (!dbt)
2840 return NULL;
2841
2842 if (keybag == NULL && passcode == NULL) {
2843 #if USE_KEYSTORE
2844 backup = SecServerExportBackupableKeychain(dbt, client, KEYBAG_DEVICE, backup_keybag_handle, error);
2845 #else /* !USE_KEYSTORE */
2846 (void)client;
2847 SecError(errSecParam, error, CFSTR("Why are you doing this?"));
2848 backup = NULL;
2849 #endif /* USE_KEYSTORE */
2850 } else {
2851 backup = SecServerKeychainCreateBackup(dbt, client, keybag, passcode, emcs, error);
2852 }
2853 return (backup != NULL);
2854 });
2855
2856 return backup;
2857 }
2858
2859 bool
2860 _SecServerKeychainRestore(CFDataRef backup, SecurityClient *client, CFDataRef keybag, CFDataRef passcode, CFErrorRef *error) {
2861 if (backup == NULL || keybag == NULL)
2862 return SecError(errSecParam, error, CFSTR("backup or keybag missing"));
2863
2864 __block bool ok = true;
2865 ok &= kc_with_dbt(true, error, ^bool (SecDbConnectionRef dbconn) {
2866 return SecServerKeychainRestore(dbconn, client, backup, keybag, passcode, error);
2867 });
2868
2869 if (ok) {
2870 SecKeychainChanged();
2871 }
2872
2873 return ok;
2874 }
2875
2876 CFStringRef
2877 _SecServerBackupCopyUUID(CFDataRef data, CFErrorRef *error)
2878 {
2879 CFStringRef uuid = NULL;
2880 CFDictionaryRef backup;
2881
2882 backup = CFPropertyListCreateWithData(kCFAllocatorDefault, data,
2883 kCFPropertyListImmutable, NULL,
2884 error);
2885 if (isDictionary(backup)) {
2886 uuid = SecServerBackupGetKeybagUUID(backup, error);
2887 if (uuid)
2888 CFRetain(uuid);
2889 }
2890 CFReleaseNull(backup);
2891
2892 return uuid;
2893 }
2894
2895
2896
2897 // MARK: -
2898 // MARK: SecItemDataSource
2899
2900 // Make sure to call this before any writes to the keychain, so that we fire
2901 // up the engines to monitor manifest changes.
2902 SOSDataSourceFactoryRef SecItemDataSourceFactoryGetDefault(void) {
2903 return SecItemDataSourceFactoryGetShared(kc_dbhandle(NULL));
2904 }
2905
2906 /* AUDIT[securityd]:
2907 args_in (ok) is a caller provided, CFDictionaryRef.
2908 */
2909
2910 CF_RETURNS_RETAINED CFArrayRef
2911 _SecServerKeychainSyncUpdateMessage(CFDictionaryRef updates, CFErrorRef *error) {
2912 // This never fails, trust us!
2913 return SOSCCHandleUpdateMessage(updates);
2914 }
2915
2916 //
2917 // Truthiness in the cloud backup/restore support.
2918 //
2919
2920 static CFDictionaryRef
2921 _SecServerCopyTruthInTheCloud(CFDataRef keybag, CFDataRef password,
2922 CFDictionaryRef backup, CFErrorRef *error)
2923 {
2924 SOSManifestRef mold = NULL, mnow = NULL, mdelete = NULL, madd = NULL;
2925 __block CFMutableDictionaryRef backup_new = NULL;
2926 keybag_handle_t bag_handle;
2927 if (!ks_open_keybag(keybag, password, &bag_handle, error))
2928 return backup_new;
2929
2930 // We need to have a datasource singleton for protection domain
2931 // kSecAttrAccessibleWhenUnlocked and keep a single shared engine
2932 // instance around which we create in the datasource constructor as well.
2933 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
2934 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
2935 if (ds) {
2936 backup_new = backup ? CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, backup) : CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2937 mold = SOSCreateManifestWithBackup(backup, error);
2938 SOSEngineRef engine = SOSDataSourceGetSharedEngine(ds, error);
2939 mnow = SOSEngineCopyManifest(engine, NULL);
2940 if (!mnow) {
2941 mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0ViewSet(), error);
2942 }
2943 if (!mnow) {
2944 CFReleaseNull(backup_new);
2945 secerror("failed to obtain manifest for keychain: %@", error ? *error : NULL);
2946 } else {
2947 SOSManifestDiff(mold, mnow, &mdelete, &madd, error);
2948 }
2949
2950 // Delete everything from the new_backup that is no longer in the datasource according to the datasources manifest.
2951 SOSManifestForEach(mdelete, ^(CFDataRef digest_data, bool *stop) {
2952 CFStringRef deleted_item_key = CFDataCopyHexString(digest_data);
2953 CFDictionaryRemoveValue(backup_new, deleted_item_key);
2954 CFRelease(deleted_item_key);
2955 });
2956
2957 CFMutableArrayRef changes = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
2958 SOSDataSourceForEachObject(ds, NULL, madd, error, ^void(CFDataRef digest, SOSObjectRef object, bool *stop) {
2959 CFErrorRef localError = NULL;
2960 CFDataRef digest_data = NULL;
2961 CFTypeRef value = NULL;
2962 if (!object) {
2963 // Key in our manifest can't be found in db, remove it from our manifest
2964 SOSChangesAppendDelete(changes, digest);
2965 } else if (!(digest_data = SOSObjectCopyDigest(ds, object, &localError))
2966 || !(value = SOSObjectCopyBackup(ds, object, bag_handle, &localError))) {
2967 if (SecErrorGetOSStatus(localError) == errSecDecode) {
2968 // Ignore decode errors, pretend the objects aren't there
2969 CFRelease(localError);
2970 // Object undecodable, remove it from our manifest
2971 SOSChangesAppendDelete(changes, digest);
2972 } else {
2973 // Stop iterating and propagate out all other errors.
2974 *stop = true;
2975 *error = localError;
2976 CFReleaseNull(backup_new);
2977 }
2978 } else {
2979 // TODO: Should we skip tombstones here?
2980 CFStringRef key = CFDataCopyHexString(digest_data);
2981 CFDictionarySetValue(backup_new, key, value);
2982 CFReleaseSafe(key);
2983 }
2984 CFReleaseSafe(digest_data);
2985 CFReleaseSafe(value);
2986 }) || CFReleaseNull(backup_new);
2987
2988 if (CFArrayGetCount(changes)) {
2989 if (!SOSEngineUpdateChanges(engine, kSOSDataSourceSOSTransaction, changes, error)) {
2990 CFReleaseNull(backup_new);
2991 }
2992 }
2993 CFReleaseSafe(changes);
2994
2995 SOSDataSourceRelease(ds, error) || CFReleaseNull(backup_new);
2996 }
2997
2998 CFReleaseSafe(mold);
2999 CFReleaseSafe(mnow);
3000 CFReleaseSafe(madd);
3001 CFReleaseSafe(mdelete);
3002 ks_close_keybag(bag_handle, error) || CFReleaseNull(backup_new);
3003
3004 return backup_new;
3005 }
3006
3007 static bool
3008 _SecServerRestoreTruthInTheCloud(CFDataRef keybag, CFDataRef password, CFDictionaryRef backup_in, CFErrorRef *error) {
3009 __block bool ok = true;
3010 keybag_handle_t bag_handle;
3011 if (!ks_open_keybag(keybag, password, &bag_handle, error))
3012 return false;
3013
3014 SOSManifestRef mbackup = SOSCreateManifestWithBackup(backup_in, error);
3015 if (mbackup) {
3016 SOSDataSourceFactoryRef dsf = SecItemDataSourceFactoryGetDefault();
3017 SOSDataSourceRef ds = SOSDataSourceFactoryCreateDataSource(dsf, kSecAttrAccessibleWhenUnlocked, error);
3018 ok &= ds && SOSDataSourceWith(ds, error, ^(SOSTransactionRef txn, bool *commit) {
3019 SOSManifestRef mnow = SOSDataSourceCopyManifestWithViewNameSet(ds, SOSViewsGetV0BackupViewSet(), error);
3020 SOSManifestRef mdelete = NULL, madd = NULL;
3021 SOSManifestDiff(mnow, mbackup, &mdelete, &madd, error);
3022
3023 // Don't delete everything in datasource not in backup.
3024
3025 // Add items from the backup
3026 SOSManifestForEach(madd, ^void(CFDataRef e, bool *stop) {
3027 CFDictionaryRef item = NULL;
3028 CFStringRef sha1 = CFDataCopyHexString(e);
3029 if (sha1) {
3030 item = CFDictionaryGetValue(backup_in, sha1);
3031 CFRelease(sha1);
3032 }
3033 if (item) {
3034 CFErrorRef localError = NULL;
3035
3036 if (!SOSObjectRestoreObject(ds, txn, bag_handle, item, &localError)) {
3037 OSStatus status = SecErrorGetOSStatus(localError);
3038 if (status == errSecDuplicateItem) {
3039 // Log and ignore duplicate item errors during restore
3040 secnotice("titc", "restore %@ not replacing existing item", item);
3041 } else if (status == errSecDecode) {
3042 // Log and ignore corrupted item errors during restore
3043 secnotice("titc", "restore %@ skipping corrupted item %@", item, localError);
3044 } else {
3045 if (status == errSecInteractionNotAllowed)
3046 *stop = true;
3047 // Propagate the first other error upwards (causing the restore to fail).
3048 secerror("restore %@ failed %@", item, localError);
3049 ok = false;
3050 if (error && !*error) {
3051 *error = localError;
3052 localError = NULL;
3053 }
3054 }
3055 CFReleaseSafe(localError);
3056 }
3057 }
3058 });
3059 ok &= SOSDataSourceRelease(ds, error);
3060 CFReleaseNull(mdelete);
3061 CFReleaseNull(madd);
3062 CFReleaseNull(mnow);
3063 });
3064 CFRelease(mbackup);
3065 }
3066
3067 ok &= ks_close_keybag(bag_handle, error);
3068
3069 return ok;
3070 }
3071
3072
3073 CF_RETURNS_RETAINED CFDictionaryRef
3074 _SecServerBackupSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
3075 require_action_quiet(isData(keybag), errOut, SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
3076 require_action_quiet(!backup || isDictionary(backup), errOut, SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
3077 require_action_quiet(!password || isData(password), errOut, SecError(errSecParam, error, CFSTR("password %@ not a data"), password));
3078
3079 return _SecServerCopyTruthInTheCloud(keybag, password, backup, error);
3080
3081 errOut:
3082 return NULL;
3083 }
3084
3085 bool
3086 _SecServerRestoreSyncable(CFDictionaryRef backup, CFDataRef keybag, CFDataRef password, CFErrorRef *error) {
3087 bool ok;
3088 require_action_quiet(isData(keybag), errOut, ok = SecError(errSecParam, error, CFSTR("keybag %@ not a data"), keybag));
3089 require_action_quiet(isDictionary(backup), errOut, ok = SecError(errSecParam, error, CFSTR("backup %@ not a dictionary"), backup));
3090 if (password) {
3091
3092 require_action_quiet(isData(password), errOut, ok = SecError(errSecParam, error, CFSTR("password not a data")));
3093 }
3094
3095 ok = _SecServerRestoreTruthInTheCloud(keybag, password, backup, error);
3096
3097 errOut:
3098 return ok;
3099 }
3100
3101 bool _SecServerRollKeysGlue(bool force, CFErrorRef *error) {
3102 return _SecServerRollKeys(force, NULL, error);
3103 }
3104
3105
3106 bool _SecServerRollKeys(bool force, SecurityClient *client, CFErrorRef *error) {
3107 #if USE_KEYSTORE
3108 uint32_t keystore_generation_status = 0;
3109 if (aks_generation(KEYBAG_DEVICE, generation_noop, &keystore_generation_status))
3110 return false;
3111 uint32_t current_generation = keystore_generation_status & generation_current;
3112
3113 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3114 bool up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
3115
3116 if (force && !up_to_date) {
3117 up_to_date = s3dl_dbt_update_keys(dbt, client, error);
3118 if (up_to_date) {
3119 secerror("Completed roll keys.");
3120 up_to_date = s3dl_dbt_keys_current(dbt, current_generation, NULL);
3121 }
3122 if (!up_to_date)
3123 secerror("Failed to roll keys.");
3124 }
3125 return up_to_date;
3126 });
3127 #else
3128 return true;
3129 #endif
3130 }
3131
3132 static bool
3133 InitialSyncItems(CFMutableArrayRef items, bool limitToCurrent, CFStringRef agrp, CFStringRef svce, const SecDbClass *qclass, CFErrorRef *error)
3134 {
3135 bool result = false;
3136 Query *q = NULL;
3137
3138 q = query_create(qclass, NULL, NULL, error);
3139 require(q, fail);
3140
3141 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3142 q->q_limit = kSecMatchUnlimited;
3143 q->q_keybag = KEYBAG_DEVICE;
3144
3145 query_add_attribute(kSecAttrAccessGroup, agrp, q);
3146 query_add_attribute(kSecAttrSynchronizable, kCFBooleanTrue, q);
3147 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
3148 if (svce)
3149 query_add_attribute(kSecAttrService, svce, q);
3150
3151 result = kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
3152 return kc_transaction(dbt, error, ^{
3153 CFErrorRef error2 = NULL;
3154
3155 SecDbItemSelect(q, dbt, &error2, NULL, ^bool(const SecDbAttr *attr) {
3156 return CFDictionaryGetValue(q->q_item, attr->name);
3157 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3158 CFErrorRef error3 = NULL;
3159 secinfo("InitialSyncItems", "Copy item");
3160
3161 CFMutableDictionaryRef attrs = SecDbItemCopyPListWithMask(item, kSecDbSyncFlag, &error3);
3162 if (attrs) {
3163 int match = true;
3164 CFStringRef itemvwht = CFDictionaryGetValue(attrs, kSecAttrSyncViewHint);
3165 /*
3166 * Saying its a SOS viewhint is really not the right answer post Triangle
3167 */
3168 if (isString(itemvwht) && !SOSViewInSOSSystem(itemvwht)) {
3169 match = false;
3170 }
3171 /*
3172 * Here we encode how PCS stores identities so that we only copy the
3173 * current identites for performance reasons.
3174 */
3175 if (limitToCurrent) {
3176 enum { PCS_CURRENT_IDENTITY_OFFSET = 0x10000 };
3177 int32_t s32;
3178
3179 CFNumberRef type = CFDictionaryGetValue(attrs, kSecAttrType);
3180 if (!isNumber(type)) {
3181 // still allow this case since its not a service identity ??
3182 } else if (!CFNumberGetValue(type, kCFNumberSInt32Type, &s32)) {
3183 match = false;
3184 } else if ((s32 & PCS_CURRENT_IDENTITY_OFFSET) == 0) {
3185 match = false;
3186 }
3187 }
3188 if (match) {
3189 CFDictionaryAddValue(attrs, kSecClass, SecDbItemGetClass(item)->name);
3190 CFArrayAppendValue(items, attrs);
3191 }
3192
3193 CFReleaseNull(attrs);
3194 }
3195 CFReleaseNull(error3);
3196 });
3197 CFReleaseNull(error2);
3198
3199 return (bool)true;
3200 });
3201 });
3202
3203 fail:
3204 if (q)
3205 query_destroy(q, NULL);
3206 return result;
3207 }
3208
3209 CFArrayRef
3210 _SecServerCopyInitialSyncCredentials(uint32_t flags, CFErrorRef *error)
3211 {
3212 CFMutableArrayRef items = CFArrayCreateMutableForCFTypes(NULL);
3213
3214 if (flags & SecServerInitialSyncCredentialFlagTLK) {
3215 require_action(InitialSyncItems(items, false, CFSTR("com.apple.security.ckks"), NULL, inet_class(), error), fail,
3216 secerror("failed to collect CKKS-inet keys: %@", error ? *error : NULL));
3217 }
3218 if (flags & SecServerInitialSyncCredentialFlagPCS) {
3219 bool onlyCurrent = !(flags & SecServerInitialSyncCredentialFlagPCSNonCurrent);
3220
3221 require_action(InitialSyncItems(items, false, CFSTR("com.apple.ProtectedCloudStorage"), NULL, genp_class(), error), fail,
3222 secerror("failed to collect PCS-genp keys: %@", error ? *error : NULL));
3223 require_action(InitialSyncItems(items, onlyCurrent, CFSTR("com.apple.ProtectedCloudStorage"), NULL, inet_class(), error), fail,
3224 secerror("failed to collect PCS-inet keys: %@", error ? *error : NULL));
3225 }
3226 if (flags & SecServerInitialSyncCredentialFlagBluetoothMigration) {
3227 require_action(InitialSyncItems(items, false, CFSTR("com.apple.nanoregistry.migration"), NULL, genp_class(), error), fail,
3228 secerror("failed to collect com.apple.nanoregistry.migration-genp item: %@", error ? *error : NULL));
3229 require_action(InitialSyncItems(items, false, CFSTR("com.apple.bluetooth"), CFSTR("BluetoothLESync"), genp_class(), error), fail,
3230 secerror("failed to collect com.apple.bluetooth-genp item: %@", error ? *error : NULL));
3231
3232 }
3233
3234 fail:
3235 return items;
3236 }
3237
3238 bool
3239 _SecServerImportInitialSyncCredentials(CFArrayRef array, CFErrorRef *error)
3240 {
3241 return kc_with_dbt(true, error, ^bool(SecDbConnectionRef dbt) {
3242 return kc_transaction(dbt, error, ^bool(void){
3243 CFIndex n, count = CFArrayGetCount(array);
3244
3245 secinfo("ImportInitialSyncItems", "Importing %d items", (int)count);
3246
3247 for (n = 0; n < count; n++) {
3248 CFErrorRef cferror = NULL;
3249
3250 CFDictionaryRef item = CFArrayGetValueAtIndex(array, n);
3251 if (!isDictionary(item))
3252 continue;
3253
3254 CFStringRef className = CFDictionaryGetValue(item, kSecClass);
3255 if (className == NULL) {
3256 secinfo("ImportInitialSyncItems", "Item w/o class");
3257 continue;
3258 }
3259
3260 const SecDbClass *cls = kc_class_with_name(className);
3261 if (cls == NULL) {
3262 secinfo("ImportInitialSyncItems", "Item with unknown class: %@", className);
3263 continue;
3264 }
3265
3266 SecDbItemRef dbi = SecDbItemCreateWithAttributes(NULL, cls, item, KEYBAG_DEVICE, &cferror);
3267 if (dbi == NULL) {
3268 secinfo("ImportInitialSyncItems", "Item creation failed with: %@", cferror);
3269 CFReleaseNull(cferror);
3270 continue;
3271 }
3272
3273 if (!SecDbItemSetSyncable(dbi, true, &cferror)) {
3274 secinfo("ImportInitialSyncItems", "Failed to set sync=1: %@ for item %@", cferror, dbi);
3275 CFReleaseNull(cferror);
3276 CFReleaseNull(dbi);
3277 continue;
3278 }
3279
3280 if (!SecDbItemInsert(dbi, dbt, &cferror)) {
3281 secinfo("ImportInitialSyncItems", "Item store failed with: %@: %@", cferror, dbi);
3282 CFReleaseNull(cferror);
3283 }
3284 CFReleaseNull(dbi);
3285 }
3286 return true;
3287 });
3288 });
3289 }
3290
3291
3292
3293 #if TARGET_OS_IOS
3294
3295 /*
3296 * Sync bubble migration code
3297 */
3298
3299 struct SyncBubbleRule {
3300 CFStringRef attribute;
3301 CFTypeRef value;
3302 };
3303
3304 static bool
3305 TransmogrifyItemsToSyncBubble(SecurityClient *client, uid_t uid,
3306 bool onlyDelete,
3307 bool copyToo,
3308 const SecDbClass *qclass,
3309 struct SyncBubbleRule *items, CFIndex nItems,
3310 CFErrorRef *error)
3311 {
3312 CFMutableDictionaryRef updateAttributes = NULL;
3313 CFDataRef syncBubbleView = NULL;
3314 CFDataRef activeUserView = NULL;
3315 bool res = false;
3316 Query *q = NULL;
3317 CFIndex n;
3318
3319 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
3320 require(syncBubbleView, fail);
3321
3322 activeUserView = SecMUSRCreateActiveUserUUID(uid);
3323 require(activeUserView, fail);
3324
3325
3326 if ((onlyDelete && !copyToo) || !onlyDelete) {
3327
3328 /*
3329 * Clean out items first
3330 */
3331
3332 secnotice("syncbubble", "cleaning out old items");
3333
3334 q = query_create(qclass, NULL, NULL, error);
3335 require(q, fail);
3336
3337 q->q_limit = kSecMatchUnlimited;
3338 q->q_keybag = device_keybag_handle;
3339
3340 for (n = 0; n < nItems; n++) {
3341 query_add_attribute(items[n].attribute, items[n].value, q);
3342 }
3343 q->q_musrView = CFRetain(syncBubbleView);
3344 require(q->q_musrView, fail);
3345
3346 kc_with_dbt(false, error, ^(SecDbConnectionRef dbt) {
3347 return kc_transaction(dbt, error, ^{
3348 return s3dl_query_delete(dbt, q, NULL, error);
3349 });
3350 });
3351
3352 query_destroy(q, NULL);
3353 q = NULL;
3354 }
3355
3356
3357 if (onlyDelete || !copyToo) {
3358 secnotice("syncbubble", "skip migration of items");
3359 } else {
3360 /*
3361 * Copy over items from EMCS to sync bubble
3362 */
3363
3364 secnotice("syncbubble", "migrating sync bubble items");
3365
3366 q = query_create(qclass, NULL, NULL, error);
3367 require(q, fail);
3368
3369 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3370 q->q_limit = kSecMatchUnlimited;
3371 q->q_keybag = device_keybag_handle; /* XXX change to session key bag when it exists */
3372
3373 for (n = 0; n < nItems; n++) {
3374 query_add_or_attribute(items[n].attribute, items[n].value, q);
3375 }
3376 query_add_or_attribute(CFSTR("musr"), activeUserView, q);
3377 q->q_musrView = CFRetain(activeUserView);
3378
3379 updateAttributes = CFDictionaryCreateMutableForCFTypes(NULL);
3380 require(updateAttributes, fail);
3381
3382 CFDictionarySetValue(updateAttributes, CFSTR("musr"), syncBubbleView); /* XXX should use kSecAttrMultiUser */
3383
3384
3385 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3386 return kc_transaction(dbt, error, ^{
3387 CFErrorRef error2 = NULL;
3388
3389 SecDbItemSelect(q, dbt, &error2, NULL, ^bool(const SecDbAttr *attr) {
3390 return CFDictionaryGetValue(q->q_item, attr->name);
3391 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3392 CFErrorRef error3 = NULL;
3393 secinfo("syncbubble", "migrating item");
3394
3395 SecDbItemRef new_item = SecDbItemCopyWithUpdates(item, updateAttributes, NULL);
3396 if (new_item == NULL)
3397 return;
3398
3399 SecDbItemClearRowId(new_item, NULL);
3400
3401 if (!SecDbItemSetKeybag(new_item, device_keybag_handle, NULL)) {
3402 CFRelease(new_item);
3403 return;
3404 }
3405
3406 if (!SecDbItemInsert(new_item, dbt, &error3)) {
3407 secnotice("syncbubble", "migration failed with %@ for item %@", error3, new_item);
3408 }
3409 CFRelease(new_item);
3410 CFReleaseNull(error3);
3411 });
3412 CFReleaseNull(error2);
3413
3414 return (bool)true;
3415 });
3416 });
3417 }
3418 res = true;
3419
3420 fail:
3421 CFReleaseNull(syncBubbleView);
3422 CFReleaseNull(activeUserView);
3423 CFReleaseNull(updateAttributes);
3424 if (q)
3425 query_destroy(q, NULL);
3426
3427 return res;
3428 }
3429
3430 static struct SyncBubbleRule PCSItems[] = {
3431 {
3432 .attribute = CFSTR("agrp"),
3433 .value = CFSTR("com.apple.ProtectedCloudStorage"),
3434 }
3435 };
3436 static struct SyncBubbleRule NSURLSesssiond[] = {
3437 {
3438 .attribute = CFSTR("agrp"),
3439 .value = CFSTR("com.apple.nsurlsessiond"),
3440 }
3441 };
3442 static struct SyncBubbleRule AccountsdItems[] = {
3443 {
3444 .attribute = CFSTR("svce"),
3445 .value = CFSTR("com.apple.account.AppleAccount.token"),
3446 },
3447 {
3448 .attribute = CFSTR("svce"),
3449 .value = CFSTR("com.apple.account.AppleAccount.password"),
3450 },
3451 {
3452 .attribute = CFSTR("svce"),
3453 .value = CFSTR("com.apple.account.AppleAccount.rpassword"),
3454 },
3455 {
3456 .attribute = CFSTR("svce"),
3457 .value = CFSTR("com.apple.account.idms.token"),
3458 },
3459 {
3460 .attribute = CFSTR("svce"),
3461 .value = CFSTR("com.apple.account.idms.continuation-key"),
3462 },
3463 {
3464 .attribute = CFSTR("svce"),
3465 .value = CFSTR("com.apple.account.CloudKit.token"),
3466 },
3467 };
3468
3469 static struct SyncBubbleRule MobileMailItems[] = {
3470 {
3471 .attribute = CFSTR("svce"),
3472 .value = CFSTR("com.apple.account.IMAP.password"),
3473 },
3474 {
3475 .attribute = CFSTR("svce"),
3476 .value = CFSTR("com.apple.account.SMTP.password"),
3477 },
3478 {
3479 .attribute = CFSTR("svce"),
3480 .value = CFSTR("com.apple.account.Exchange.password"),
3481 },
3482 {
3483 .attribute = CFSTR("svce"),
3484 .value = CFSTR("com.apple.account.Hotmail.password"),
3485 },
3486 {
3487 .attribute = CFSTR("svce"),
3488 .value = CFSTR("com.apple.account.Google.password"),
3489 },
3490 {
3491 .attribute = CFSTR("svce"),
3492 .value = CFSTR("com.apple.account.Google.oauth-token"),
3493 },
3494 {
3495 .attribute = CFSTR("svce"),
3496 .value = CFSTR("com.apple.account.Google.oath-refresh-token"),
3497 },
3498 {
3499 .attribute = CFSTR("svce"),
3500 .value = CFSTR("com.apple.account.Yahoo.password"),
3501 },
3502 {
3503 .attribute = CFSTR("svce"),
3504 .value = CFSTR("com.apple.account.Yahoo.oauth-token"),
3505 },
3506 {
3507 .attribute = CFSTR("svce"),
3508 .value = CFSTR("com.apple.account.Yahoo.oauth-token-nosync"),
3509 },
3510 {
3511 .attribute = CFSTR("svce"),
3512 .value = CFSTR("com.apple.account.Yahoo.oath-refresh-token"),
3513 },
3514 {
3515 .attribute = CFSTR("svce"),
3516 .value = CFSTR("com.apple.account.IMAPNotes.password"),
3517 },
3518 {
3519 .attribute = CFSTR("svce"),
3520 .value = CFSTR("com.apple.account.IMAPMail.password"),
3521 },
3522 {
3523 .attribute = CFSTR("svce"),
3524 .value = CFSTR("com.apple.account.126.password"),
3525 },
3526 {
3527 .attribute = CFSTR("svce"),
3528 .value = CFSTR("com.apple.account.163.password"),
3529 },
3530 {
3531 .attribute = CFSTR("svce"),
3532 .value = CFSTR("com.apple.account.aol.password"),
3533 },
3534 };
3535
3536 static bool
3537 ArrayContains(CFArrayRef array, CFStringRef service)
3538 {
3539 return CFArrayContainsValue(array, CFRangeMake(0, CFArrayGetCount(array)), service);
3540 }
3541
3542 bool
3543 _SecServerTransmogrifyToSyncBubble(CFArrayRef services, uid_t uid, SecurityClient *client, CFErrorRef *error)
3544 {
3545 bool copyCloudAuthToken = false;
3546 bool copyMobileMail = false;
3547 bool res = true;
3548 bool copyPCS = false;
3549 bool onlyDelete = false;
3550 bool copyNSURLSesssion = false;
3551
3552 if (!client->inMultiUser)
3553 return false;
3554
3555 secnotice("syncbubble", "migration for uid %d uid for services %@", (int)uid, services);
3556
3557 #if TARGET_OS_SIMULATOR
3558 // no delete in sim
3559 #elif TARGET_OS_IOS
3560 if (uid != (uid_t)client->activeUser)
3561 onlyDelete = true;
3562 #else
3563 #error "no sync bubble on other platforms"
3564 #endif
3565
3566 /*
3567 * First select that services to copy/delete
3568 */
3569
3570 if (ArrayContains(services, CFSTR("com.apple.bird.usermanager.sync"))
3571 || ArrayContains(services, CFSTR("com.apple.cloudphotod.sync"))
3572 || ArrayContains(services, CFSTR("com.apple.cloudphotod.syncstakeholder"))
3573 || ArrayContains(services, CFSTR("com.apple.cloudd.usermanager.sync")))
3574 {
3575 copyCloudAuthToken = true;
3576 copyPCS = true;
3577 }
3578
3579 if (ArrayContains(services, CFSTR("com.apple.nsurlsessiond.usermanager.sync")))
3580 {
3581 copyCloudAuthToken = true;
3582 copyNSURLSesssion = true;
3583 }
3584
3585 if (ArrayContains(services, CFSTR("com.apple.syncdefaultsd.usermanager.sync"))) {
3586 copyCloudAuthToken = true;
3587 }
3588 if (ArrayContains(services, CFSTR("com.apple.mailq.sync")) || ArrayContains(services, CFSTR("com.apple.mailq.sync.xpc"))) {
3589 copyCloudAuthToken = true;
3590 copyMobileMail = true;
3591 copyPCS = true;
3592 }
3593
3594 /*
3595 * The actually copy/delete the items selected
3596 */
3597
3598 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, inet_class(), PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
3599 require(res, fail);
3600 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyPCS, genp_class(), PCSItems, sizeof(PCSItems)/sizeof(PCSItems[0]), error);
3601 require(res, fail);
3602
3603 /* mail */
3604 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyMobileMail, genp_class(), MobileMailItems, sizeof(MobileMailItems)/sizeof(MobileMailItems[0]), error);
3605 require(res, fail);
3606
3607 /* accountsd */
3608 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyCloudAuthToken, genp_class(), AccountsdItems, sizeof(AccountsdItems)/sizeof(AccountsdItems[0]), error);
3609 require(res, fail);
3610
3611 /* nsurlsessiond */
3612 res = TransmogrifyItemsToSyncBubble(client, uid, onlyDelete, copyNSURLSesssion, inet_class(), NSURLSesssiond, sizeof(NSURLSesssiond)/sizeof(NSURLSesssiond[0]), error);
3613 require(res, fail);
3614
3615 fail:
3616 return res;
3617 }
3618
3619 /*
3620 * Migrate from user keychain to system keychain when switching to edu mode
3621 */
3622
3623 bool
3624 _SecServerTransmogrifyToSystemKeychain(SecurityClient *client, CFErrorRef *error)
3625 {
3626 __block bool ok = true;
3627
3628 /*
3629 * we are not in multi user yet, about to switch, otherwise we would
3630 * check that for client->inMultiuser here
3631 */
3632
3633 kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3634 return kc_transaction(dbt, error, ^{
3635 CFDataRef systemUUID = SecMUSRGetSystemKeychainUUID();
3636
3637 const SecDbSchema *newSchema = current_schema();
3638 SecDbClass const *const *kcClass;
3639
3640 for (kcClass = newSchema->classes; *kcClass != NULL; kcClass++) {
3641 CFErrorRef localError = NULL;
3642 Query *q = NULL;
3643
3644 if (!((*kcClass)->itemclass)) {
3645 continue;
3646 }
3647
3648 q = query_create(*kcClass, SecMUSRGetSingleUserKeychainUUID(), NULL, error);
3649 if (q == NULL)
3650 continue;
3651
3652 ok &= SecDbItemSelect(q, dbt, error, ^bool(const SecDbAttr *attr) {
3653 return (attr->flags & kSecDbInFlag) != 0;
3654 }, ^bool(const SecDbAttr *attr) {
3655 // No filtering please.
3656 return false;
3657 }, ^bool(CFMutableStringRef sql, bool *needWhere) {
3658 SecDbAppendWhereOrAnd(sql, needWhere);
3659 CFStringAppendFormat(sql, NULL, CFSTR("musr = ?"));
3660 return true;
3661 }, ^bool(sqlite3_stmt *stmt, int col) {
3662 return SecDbBindObject(stmt, col++, SecMUSRGetSingleUserKeychainUUID(), error);
3663 }, ^(SecDbItemRef item, bool *stop) {
3664 CFErrorRef localError = NULL;
3665
3666 if (!SecDbItemSetValueWithName(item, kSecAttrMultiUser, systemUUID, &localError)) {
3667 secerror("item: %@ update musr to system failed: %@", item, localError);
3668 ok = false;
3669 goto out;
3670 }
3671
3672 if (!SecDbItemDoUpdate(item, item, dbt, &localError, ^bool (const SecDbAttr *attr) {
3673 return attr->kind == kSecDbRowIdAttr;
3674 })) {
3675 secerror("item: %@ insert during UPDATE: %@", item, localError);
3676 ok = false;
3677 goto out;
3678 }
3679
3680 out:
3681 SecErrorPropagate(localError, error);
3682 });
3683
3684 if (q)
3685 query_destroy(q, &localError);
3686
3687 }
3688 return (bool)true;
3689 });
3690 });
3691
3692 return ok;
3693 }
3694
3695 /*
3696 * Delete account from local usage
3697 */
3698
3699 bool
3700 _SecServerDeleteMUSERViews(SecurityClient *client, uid_t uid, CFErrorRef *error)
3701 {
3702 return kc_with_dbt(true, error, ^(SecDbConnectionRef dbt) {
3703 CFDataRef musrView = NULL, syncBubbleView = NULL;
3704 bool ok = false;
3705
3706 syncBubbleView = SecMUSRCreateSyncBubbleUserUUID(uid);
3707 require(syncBubbleView, fail);
3708
3709 musrView = SecMUSRCreateActiveUserUUID(uid);
3710 require(musrView, fail);
3711
3712 require(ok = SecServerDeleteAllForUser(dbt, syncBubbleView, false, error), fail);
3713 require(ok = SecServerDeleteAllForUser(dbt, musrView, false, error), fail);
3714
3715 fail:
3716 CFReleaseNull(syncBubbleView);
3717 CFReleaseNull(musrView);
3718 return ok;
3719 });
3720 }
3721
3722
3723 #endif /* TARGET_OS_IOS */
3724
3725 bool
3726 _SecServerGetKeyStats(const SecDbClass *qclass,
3727 struct _SecServerKeyStats *stats)
3728 {
3729 __block CFErrorRef error = NULL;
3730 bool res = false;
3731
3732 Query *q = query_create(qclass, NULL, NULL, &error);
3733 require(q, fail);
3734
3735 q->q_return_type = kSecReturnDataMask | kSecReturnAttributesMask;
3736 q->q_limit = kSecMatchUnlimited;
3737 q->q_keybag = KEYBAG_DEVICE;
3738 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlocked, q);
3739 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlock, q);
3740 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlways, q);
3741 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, q);
3742 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, q);
3743 query_add_or_attribute(kSecAttrAccessible, kSecAttrAccessibleAlwaysThisDeviceOnly, q);
3744 query_add_attribute(kSecAttrTombstone, kCFBooleanFalse, q);
3745
3746 kc_with_dbt(false, &error, ^(SecDbConnectionRef dbconn) {
3747 CFErrorRef error2 = NULL;
3748 __block CFIndex totalSize = 0;
3749 stats->maxDataSize = 0;
3750
3751 SecDbItemSelect(q, dbconn, &error2, NULL, ^bool(const SecDbAttr *attr) {
3752 return CFDictionaryContainsKey(q->q_item, attr->name);
3753 }, NULL, NULL, ^(SecDbItemRef item, bool *stop) {
3754 CFErrorRef error3 = NULL;
3755 CFDataRef data = SecDbItemGetValue(item, &v6v_Data, &error3);
3756 if (isData(data)) {
3757 CFIndex size = CFDataGetLength(data);
3758 if (size > stats->maxDataSize)
3759 stats->maxDataSize = size;
3760 totalSize += size;
3761 stats->items++;
3762 }
3763 CFReleaseNull(error3);
3764 });
3765 CFReleaseNull(error2);
3766 if (stats->items)
3767 stats->averageSize = totalSize / stats->items;
3768
3769 return (bool)true;
3770 });
3771
3772
3773 res = true;
3774
3775 fail:
3776 CFReleaseNull(error);
3777 if (q)
3778 query_destroy(q, NULL);
3779 return res;
3780 }
3781
3782 CFArrayRef _SecItemCopyParentCertificates(CFDataRef normalizedIssuer, CFArrayRef accessGroups, CFErrorRef *error) {
3783 const void *keys[] = {
3784 kSecClass,
3785 kSecReturnData,
3786 kSecMatchLimit,
3787 kSecAttrSubject
3788 },
3789 *values[] = {
3790 kSecClassCertificate,
3791 kCFBooleanTrue,
3792 kSecMatchLimitAll,
3793 normalizedIssuer
3794 };
3795 CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 4,
3796 NULL, NULL);
3797 CFTypeRef results = NULL;
3798 SecurityClient client = {
3799 .task = NULL,
3800 .accessGroups = accessGroups,
3801 .allowSystemKeychain = true,
3802 .allowSyncBubbleKeychain = false,
3803 .isNetworkExtension = false,
3804 };
3805
3806 (void)_SecItemCopyMatching(query, &client, &results, error);
3807 CFRelease(query);
3808 return results;
3809 }
3810
3811 bool _SecItemCertificateExists(CFDataRef normalizedIssuer, CFDataRef serialNumber, CFArrayRef accessGroups, CFErrorRef *error) {
3812 const void *keys[] = {
3813 kSecClass,
3814 kSecMatchLimit,
3815 kSecAttrIssuer,
3816 kSecAttrSerialNumber
3817 },
3818 *values[] = {
3819 kSecClassCertificate,
3820 kSecMatchLimitOne,
3821 normalizedIssuer,
3822 serialNumber
3823 };
3824 SecurityClient client = {
3825 .task = NULL,
3826 .accessGroups = accessGroups,
3827 .allowSystemKeychain = true,
3828 .allowSyncBubbleKeychain = false,
3829 .isNetworkExtension = false,
3830 };
3831 CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 4, NULL, NULL);
3832 CFTypeRef results = NULL;
3833 bool ok = _SecItemCopyMatching(query, &client, &results, error);
3834 CFReleaseSafe(query);
3835 CFReleaseSafe(results);
3836 if (!ok) {
3837 return false;
3838 }
3839 return true;
3840 }