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