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